2014-08-28 56 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