2014-08-28 19 views
9

Sto implementando servizi basati su sessioni. Tutte le richieste devono essere sottoscritte con un parametro della sessione cookie, che a sua volta viene recuperato con un'app di riposo separata. Quindi il flusso di lavoro di base sarebbe quello di ottenere il cookie di sessione e procedere alla richiesta dei servizi. Occasionalmente il cookie scade e porta a un'altra richiesta di cookie di sessione.Retrofit/Rxjava e servizi basati su sessioni

Sto provando a rendere il codice client insensibile alla sessione, in modo che non debba preoccuparsi di mantenere la sessione, ma piuttosto voglio che sia nascosto nel livello dei servizi.

Puoi suggerire idee per implementarlo con Retrofit/RxJava? Penso che il SessionService deve essere incapsulato per tutti gli altri servizi, in modo che possano interrogare ogni volta che è necessario, ma non sono sicuro come farlo con del Retrofit RestAdapter.create

risposta

25

che ho fatto qualcosa di simile a questo prima, ma con autorizzazione OAuth. Fondamentalmente, hai un RestAdapter inizializzato con un RequestInterceptor che aggiunge il cookie di sessione ad ogni richiesta. RequestInterceptor riceve un nuovo cookie di sessione ogni volta che una sessione è autorizzata.

la seguente definizione di interfaccia Retrofit resto è usato nel codice di esempio riportato di seguito:

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

La richiesta intercettore ottiene uno sguardo a ogni richiesta REST e può aggiungere intestazioni, parametri di query o può modificare l'URL. Questo esempio assume che il cookie sia aggiunto come intestazione 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); 
    } 
} 

Questo è il SessionService a cui si è alluso. È responsabilità di effettuare la richiesta di rete che autorizza/aggiorna il cookie di sessione.

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

La classe RestService avvolge l'interfaccia retrofit in modo che la logica tentativo richiesta può essere aggiunto a ciascuna retrofit osservabili.

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

La logica dei tentativi di seguito utilizzerà il SessionService per aggiornare il cookie di sessione e riprovare le richieste REST fallite se il cookie di sessione inviato al server restituisce un non autorizzato (401) errore HTTP.

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

codice di inizializzazione client sarà simile a questo:

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

di riposare poi osservabile dal RestService e abbonarsi ad esso per dare il via alla richiesta di rete.

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

In realtà sembra abbastanza pulito. Grazie! Errore – midnight

+0

: tipi incompatibili: RetryWithSessionRefresh non può essere convertito in Func1 ,? estende Observable > in realtà funziona solo con la sovversione di RxJava da netflix – desgraci

+0

@desgraci L'API retryWhen() è stata modificata in RxJava 1.0. Ho aggiornato la risposta per la compatibilità 1.0+. – kjones