2016-01-20 21 views
38

Poiché ho iniziato con angular2 ho impostato i miei servizi per restituire Observable of T. Nel servizio avrei la chiamata map(), e i componenti che utilizzano questi servizi utilizzerebbero semplicemente subscribe() per attendere la risposta. Per questi semplici scenari non avevo davvero bisogno di scavare in rxjs quindi tutto andava bene.Gestione dei token di aggiornamento utilizzando rxjs

Ora desidero ottenere quanto segue: utilizzo l'autenticazione Oauth2 con token di aggiornamento. Voglio creare un servizio API che tutti gli altri servizi utilizzeranno e che gestirà in modo trasparente il token di aggiornamento quando viene restituito un errore 401. Quindi, nel caso di un 401, prima prelevo un nuovo token dall'endpoint OAuth2, quindi ritento la mia richiesta con il nuovo token. Di seguito è riportato il codice che funziona bene, con promesse:

request(url: string, request: RequestOptionsArgs): Promise<Response> { 
    var me = this; 

    request.headers = request.headers || new Headers(); 
    var isSecureCall: boolean = true; //url.toLowerCase().startsWith('https://'); 
    if (isSecureCall === true) { 
     me.authService.setAuthorizationHeader(request.headers); 
    } 
    request.headers.append('Content-Type', 'application/json'); 
    request.headers.append('Accept', 'application/json'); 

    return this.http.request(url, request).toPromise() 
     .catch(initialError => { 
      if (initialError && initialError.status === 401 && isSecureCall === true) { 
       // token might be expired, try to refresh token. 
       return me.authService.refreshAuthentication().then((authenticationResult:AuthenticationResult) => { 
        if (authenticationResult.IsAuthenticated == true) { 
         // retry with new token 
         me.authService.setAuthorizationHeader(request.headers); 
         return this.http.request(url, request).toPromise(); 
        } 
        return <any>Promise.reject(initialError); 
       }); 
      } 
      else { 
       return <any>Promise.reject(initialError); 
      } 
     }); 
} 

Nel codice di cui sopra, authService.refreshAuthentication() si prendere il nuovo token e memorizzarlo in localStorage. authService.setAuthorizationHeader imposterà l'intestazione 'Autorizzazione' al token aggiornato in precedenza. Se osservi il metodo di cattura, vedrai che restituisce una promessa (per il token di aggiornamento) che alla fine restituirà un'altra promessa (per il 2 ° tentativo effettivo della richiesta).

Ho tentato di fare questo senza ricorrere a promesse:

request(url: string, request: RequestOptionsArgs): Observable<Response> { 
    var me = this; 

    request.headers = request.headers || new Headers(); 
    var isSecureCall: boolean = true; //url.toLowerCase().startsWith('https://'); 
    if (isSecureCall === true) { 
     me.authService.setAuthorizationHeader(request.headers); 
    } 
    request.headers.append('Content-Type', 'application/json'); 
    request.headers.append('Accept', 'application/json'); 

    return this.http.request(url, request) 
     .catch(initialError => { 
      if (initialError && initialError.status === 401 && isSecureCall === true) { 
       // token might be expired, try to refresh token 
       return me.authService.refreshAuthenticationObservable().map((authenticationResult:AuthenticationResult) => { 
        if (authenticationResult.IsAuthenticated == true) { 
         // retry with new token 
         me.authService.setAuthorizationHeader(request.headers); 
         return this.http.request(url, request); 
        } 
        return Observable.throw(initialError); 
       }); 
      } 
      else { 
       return Observable.throw(initialError); 
      } 
     }); 
} 

Il codice di cui sopra non fa quello che mi aspetto: nel caso di una risposta 200, restituisce correttamente la risposta. Tuttavia, se cattura il 401, recupererà con successo il nuovo token, ma il subscribe wil alla fine recupererà un osservabile invece della risposta. Immagino che questo sia l'Observable non eseguito che dovrebbe fare il tentativo.

Mi rendo conto che tradurre il modo promettente di lavorare sulla libreria rxjs non è probabilmente il modo migliore per andare, ma non sono stato in grado di cogliere la cosa "tutto è un flusso". Ho provato alcune altre soluzioni coinvolgendo flatmap, retryWhen etc ... ma non sono andato lontano, quindi un po 'di aiuto è apprezzato.

risposta

22

Da un rapido sguardo al tuo codice Direi che il tuo problema sembra essere che non stai appiattendo il Observable che viene restituito dal servizio refresh.

L'operatore catch si aspetta che si tornerà un Observable che concatenerà sull'estremità dell'osservabile fallito in modo che il flusso verso il basso Observer non conosce la differenza.

Nel caso non-401 si sta facendo correttamente questo restituendo un Osservabile che rilancia l'errore iniziale. Tuttavia nel caso di aggiornamento si restituisce un Observable produce piùObservables anziché singoli valori.

vorrei suggerire di cambiare la logica di aggiornamento per essere:

return me.authService 
      .refreshAuthenticationObservable() 
      //Use flatMap instead of map 
      .flatMap((authenticationResult:AuthenticationResult) => { 
        if (authenticationResult.IsAuthenticated == true) { 
        // retry with new token 
        me.authService.setAuthorizationHeader(request.headers); 
        return this.http.request(url, request); 
        } 
        return Observable.throw(initialError); 
    }); 

flatMap convertirà l'intermedio Observables in un unico flusso.

+0

Ha, questo funziona :) Ho provato a utilizzare il metodo flatMap, ma apparentemente non nel modo corretto. Grazie Paul! Ps: quello che trovo abbastanza disordinato in rxjs è che tendono a nascondere questi metodi in file con nomi diversi. Perché il metodo flatMap funzioni, ho dovuto importare il file "mergeMap" ... – Davy

+0

@Davy Posso chiedere quale versione stai usando? Se è il progetto di ReactiveX, è ancora in fase beta, quindi la documentazione è ancora carente mentre tutte le funzionalità sono state completate. – paulpdaniels

+0

Questo: https://github.com/ReactiveX/RxJS (il pacchetto "rxjs" npm). – Davy

9

Nell'ultima versione di RxJs, l'operatore flatMap è stato rinominato in mergeMap.

Problemi correlati