【问题标题】:Retrofit/Rxjava and session-based servicesRetrofit/Rxjava 和基于会话的服务
【发布时间】:2014-10-22 04:56:11
【问题描述】:

我正在实施基于会话的服务。所有请求都必须使用 cookie 会话参数进行订阅,然后使用单独的 rest api 进行检索。所以基本的工作流程是获取会话 cookie 并继续查询服务。有时 cookie 会过期,这会导致另一个会话 cookie 请求。

我正在尝试使客户端代码与会话无关,这样它就不必担心维护会话,而是希望它隐藏在服务层中。

您能否提出使用Retrofit/RxJava 实现它的想法?我认为 SessionService 必须被所有其他服务封装,以便他们可以在需要时查询它,但我不确定如何使用 Retrofit 的 RestAdapter.create

【问题讨论】:

    标签: android retrofit rx-java


    【解决方案1】:

    我以前做过类似的事情,但有 OAuth 授权。基本上,您有一个使用 RequestInterceptor 初始化的 RestAdapter,它将会话 cookie 添加到每个请求。每当会话被授权时,RequestInterceptor 都会获取一个新的会话 cookie。

    以下示例代码中使用了以下 Retrofit REST 接口定义:

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

    请求拦截器可以查看每个 REST 请求,并可以添加标头、查询参数或修改 URL。此示例假定 cookie 作为 HTTP 标头添加。

    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);
        }
    }
    

    这是您提到的 SessionService。它的职责是发出授权/刷新会话 cookie 的网络请求。

    class SessionService {
        // Modify contructor params to pass in anything needed 
        // to get the session cookie.
        SessionService(...) {
        }
    
        public Observable<String> 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");
        }
    }
    

    RestService 类包装了 Retrofit 接口,以便可以将请求重试逻辑添加到每个 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<Example> observeExamples() {
            // Return a Retrofit Observable modified with
            // session retry logic.
            return 
                apiService
                    .observeExamples()
                    .retryWhen(new RetryWithSessionRefresh(sessionSerivce, cookieHeaderProvider)); 
        }
    }
    

    下面的重试逻辑将使用 SessionService 更新会话 cookie,然后如果发送到服务器的会话 cookie 返回 HTTP 未授权 (401) 错误,则重试失败的 REST 请求。

    public class RetryWithSessionRefresh implements
            Func1<Observable<? extends Throwable>, 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<? extends Throwable> attempts) {
            return attempts
                    .flatMap(new Func1<Throwable, Observable<?>>() {
                        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<String>() {
                                                @Override
                                                public void call(String sessionCookie) {
                                                    // Update session cookie so that next
                                                    // retrofit request will use it.
                                                    cookieHeaderProvider.setSesstionCookie(sessionCookie);
                                                }
                                            })
                                            .doOnError(new Action1<Throwable>() {
                                                @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);
                        }
                    });
        }
    }
    

    客户端初始化代码将如下所示:

    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);
    

    然后从 RestService 获取一个 REST observable 并订阅它以启动网络请求。

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

    【讨论】:

    • 它实际上看起来很整洁。谢谢!
    • 错误:不兼容的类型:RetryWithSessionRefresh 无法转换为 Func1,? extends Observable>> 它实际上只适用于 netflix 的 RxJava 颠覆
    • @desgraci 在 RxJava 1.0 中更改了 retryWhen() API。我已经更新了 1.0+ 兼容性的答案。
    • @kjones,我之前做过一个非常相似的实现,效果很好,我认为你的解决方案看起来不错,所以你有我的支持,我的斧头(y)
    • 感谢这个答案,它工作得很好。我想知道是否可以将 OkHttp Authenticator 接口与 Rx 结合使用。我试过了,不是那么简单或不兼容......
    猜你喜欢
    • 2016-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-26
    • 1970-01-01
    • 1970-01-01
    • 2014-10-31
    相关资源
    最近更新 更多