2014-08-28 59 views
9

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

我想让客户端代码会话不可知,以便它不必担心维护会话,而是我希望它隐藏在服务层内部。

你可以建议使用Retrofit/RxJava来实现吗?我认为SessionService必须由所有其他服务进行封装,这样,每当它的需要,他们可以查询,但我不知道如何与做改造的RestAdapter.create

回答

25

我已经做过类似的东西,在这之前,但与OAuth授权。基本上,您有一个使用RequestInterceptor初始化的RestAdapter,它将会话cookie添加到每个请求中。每次授权会话时,RequestInterceptor都会获取新的会话cookie。

以下改造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类包装改造接口,以便请求重试逻辑可以被添加到每个改造观察到。下面

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,然后重试失败的REST请求,如果发送到服务器的会话cookie返回一个HTTP未经授权(401)错误。

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<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. 
      } 
     }); 
+0

它实际上看起来很整齐。谢谢! – midnight 2014-08-29 06:58:16

+0

错误:不兼容的类型:RetryWithSessionRefresh无法转换为Func1 <?超级可观察<?扩展Throwable>,?扩展Observable >它实际上只适用于Netflix的RxJava版本 – desgraci 2015-06-25 20:12:00

+0

@desgraci RxJava 1.0中的retryWhen()API已更改。我已经为1.0+兼容性更新了答案。 – kjones 2015-06-26 20:36:07