Retrofit/Rxjava and session-based services

后端 未结 1 1003
青春惊慌失措
青春惊慌失措 2020-12-23 13:01

I\'m implementing session-based services. All requests have to be subscribed with a cookie session param, which in turn is retrieved with separate rest api. So the basic wor

1条回答
  •  醉梦人生
    2020-12-23 13:17

    I've done something similar to this before but with OAuth authorization. Basically, you have a RestAdapter initialized with a RequestInterceptor that adds the session cookie to each request. The RequestInterceptor gets a new session cookie any time a session is authorized.

    The following Retrofit REST interface definition is used in the example code below:

    interface ApiService {
        @GET("/examples/v1/example")
        Observable getExample();
    }
    

    The Request interceptor gets a peek at each REST request and can add headers, query params or can modify the URL. This example assumes the cookie is added as an HTTP header.

    class CookieHeaderProvider implements RequestInterceptor {
        private String sessionCookie = "";
    
        public CookieHeaderProvider() {
        }
    
        public void setSesstionCookie(String sessionCookie) {
            this.sessionCookie = sessionCookie;
        }
    
        @Override
        public void intercept(RequestFacade requestFacade) {
            requestFacade.addHeader("Set-Cookie", sessionCookie);
        }
    }
    

    This is the SessionService you alluded to. It's responsibility is to make the network request that authorizes/refreshes the session cookie.

    class SessionService {
        // Modify contructor params to pass in anything needed 
        // to get the session cookie.
        SessionService(...) {
        }
    
        public Observable observeSessionCookie(...) {
            // Modify to return an Observable that when subscribed to
            // will make the network request to get the session cookie.
            return Observable.just("Fake Session Cookie");
        }
    }
    

    The RestService class wraps the Retrofit interface so that request retry logic can be added to each Retrofit Observable.

    class RestService {
        private final apiService;
        private final sessionSerivce;
        private final cookieHeaderProvider;
    
        RestService(ApiService apiService, 
                    SessionService sessionSerivce,
                    CookieHeaderProvider cookieHeaderProvider) {
            this.apiService = apiService;
            this.sessionSerivce = sessionSerivce;
            this.cookieHeaderProvider = cookieHeaderProvider;
        }
    
        Observable observeExamples() {
            // Return a Retrofit Observable modified with
            // session retry logic.
            return 
                apiService
                    .observeExamples()
                    .retryWhen(new RetryWithSessionRefresh(sessionSerivce, cookieHeaderProvider)); 
        }
    }
    

    The retry logic below will use the SessionService to update the session cookie and then retry failed REST requests if the session cookie sent to the server returns an HTTP Unauthorized (401) error.

    public class RetryWithSessionRefresh implements
            Func1, Observable> {
    
        private final SessionService sessionSerivce;
        private final CookieHeaderProvider cookieHeaderProvider;
    
        public RetryWithSessionRefresh(SessionService sessionSerivce,
                                       CookieHeaderProvider cookieHeaderProvider) {
            this.sessionSerivce = sessionSerivce;
            this.cookieHeaderProvider = cookieHeaderProvider;
        }
    
        @Override
        public Observable call(Observable attempts) {
            return attempts
                    .flatMap(new Func1>() {
                        public int retryCount = 0;
    
                        @Override
                        public Observable call(final Throwable throwable) {
                            // Modify retry conditions to suit your needs. The following
                            // will retry 1 time if the error returned was an
                            // HTTP Unauthoried (401) response.
                            retryCount++;
                            if (retryCount <= 1 && throwable instanceof RetrofitError) {
                                final RetrofitError retrofitError = (RetrofitError) throwable;
                                if (!retrofitError.isNetworkError()
                                        && retrofitError.getResponse().getStatus() == HttpStatus.SC_UNAUTHORIZED) {
                                    return sessionSerivce
                                            .observeSessionCookie()
                                            .doOnNext(new Action1() {
                                                @Override
                                                public void call(String sessionCookie) {
                                                    // Update session cookie so that next
                                                    // retrofit request will use it.
                                                    cookieHeaderProvider.setSesstionCookie(sessionCookie);
                                                }
                                            })
                                            .doOnError(new Action1() {
                                                @Override
                                                public void call(Throwable throwable) {
                                                    // Clear session cookie on error.
                                                    cookieHeaderProvider.setSesstionCookie("");
                                                }
                                            });
                                }
                            }
                            // No more retries. Pass the original
                            // Retrofit error through.
                            return Observable.error(throwable);
                        }
                    });
        }
    }
    

    Client initialization code will look similar to this:

    CookieHeaderProvider cookieHeaderProvider = new CookieHeaderProvider();
    SessionService sessionSerivce = new SessionService();
    
    ApiService apiService =
        new RestAdapter.Builder()
            .setEndpoint(...)
            .setClient(...)
            .setRequestInterceptor(cookieHeaderProvider)
            .build()
            .create(ApiService.class);
    
    RestService restService =
        new RestService(apiService, sessionSerivce, cookieHeaderProvider);
    

    Then get a REST observable from the RestService and subscribe to it to kick off the network request.

    Observable exampleObservable =
        restService
            .observeExamples();
    
    Subsctiption subscription =
        exampleObservable
            .subscribe(new Observer() {
                void onNext(Example example) {
                    // Do stuff with example
                }
                void onCompleted() {
                    // All done.
                }
                void onError(Throwalbe e) {
                    // All API errors will end up here.
                }
            });
    

    0 讨论(0)
提交回复
热议问题