Angular2 http retry logic

后端 未结 2 1976
别那么骄傲
别那么骄傲 2021-01-02 19:14

I have an API with token based authentication mechanism. After successful signin I store two tokens in the browser\'s local storage - access and refresh token. The access t

相关标签:
2条回答
  • 2021-01-02 19:51

    I had to do something similar in my recent project shafihuzaib/cdp-ng-boilerplate and landed on this question for my answer. I couldn't go for the above suggested solution, as it felt complicated and something not desirable. So I came back to leave my solution after I implemented one. However, the difference being that in my case, I had two such tokens.

    So, every request that needs to have tokens' validity checked, is called inside this function.

    tokenValidatedRequest(func): Observable<any>{
        let returnObservable = new Observable();
    
        /**
         * 1. check for auth token expiry - refresh it, if necessary
         */
        if( parseInt(localStorage.getItem('AUTH_TOKEN_EXPIRY')) < (new Date()).valueOf() ){
            //auth expired
            this.refresh().subscribe(res => {
                //refreshed
                //this.postAuthSuccess(res);
    
                 returnObservable = func();
    
            })
        }
        else{
            //auth not expired
    
           returnObservable = func();
    
        }
    
        return returnObservable;
    }
    

    The most important thing here is that func() should return an Observable, so that it can be consumed accordingly.

    makeSomeHttpCall(){
       this.tokenValidatedRequest(()=>{
           return this.http.post(...);
       }). subscribe();
    }
    

    It may seem a bit complicated for someone new, but I am sure it is a little more efficient.

    In the following links, please ignore the details that are irrelevant to this question and focus on the usage of the suggested solution.

    Actual implementation of tokenValidatedRequest() in my project.

     tokenValidatedRequest(func , tqlCheck = false): Observable<any>{
        /**
         * Delegate the actual task. However return an Observable, so as to execute 
         * the callback function only when subscribed to..
         */
        //return Observable.create(obs => obs = (this.__tokenValidatedRequest(func, tqlCheck)));
    
        return this.__tokenValidatedRequest(func, tqlCheck);
    }
    private __tokenValidatedRequest(func, tqlCheck = false): Observable<any>{
        let returnObservable = new Observable();
    
        /**
         * 1. check for auth token expiry - refresh it, if necessary
         * 2. after step 1 - check for TQL token expiry (if tqlCheck is true) - refresh it, if necessary
         * 3. 
         */
        if( parseInt(localStorage.getItem('AUTH_TOKEN_EXPIRY')) < (new Date()).valueOf() ){
            //auth expired
            this.refresh().subscribe(res => {
                //refreshed
                this.postAuthSuccess(res);
    
                if(tqlCheck &&  localStorage.getItem("TQL_TOKEN_EXPIRY") &&
                        parseInt(localStorage.getItem("TQL_TOKEN_EXPIRY")) < (new Date()).valueOf()
                   ){
    
                    this.activateUser().subscribe(res => {
                        //TQL token subscribed 
                        returnObservable = func();
                    })
                }
                else{
                    // Probably not a TQL request
                    returnObservable = func();
                }
            })
        }
        else{
            //auth not expired
    
            //check if tql token has expired
            if(tqlCheck &&  localStorage.getItem("TQL_TOKEN_EXPIRY") &&
                        parseInt(localStorage.getItem("TQL_TOKEN_EXPIRY")) < (new Date()).valueOf()
                   ){
    
                    this.activateUser().subscribe(res => {
                        //TQL token subscribed 
                        returnObservable = func();
                    })
                }
                else{
                    // Probably not a TQL request or none of the tokens expired
                    returnObservable = func();
                }
        }
    
        return returnObservable;
    }
    

    How it is used in other services!

    getAllParkingSpaces() : Observable<any> {
        let query = {
            Query: {
                ....
            }
        };
    
        return this.authService.tokenValidatedRequest(()=>{
            return this.api.post( CONFIG.api.engineUrl + 'devices/parking', query);
        }, true);
    }
    

    How I finally subscribe to it!

        this.realTimeParkingService.getAllParkingSpaces().subscribe( r => {
      this.parkingSpaces = r;
    });
    
    0 讨论(0)
  • 2021-01-02 19:55

    This can be done transparently in Angular2 by extending the Http class and leveraging observable operators like flatMap.

    Here is some sample code:

    if (hasTokenExpired()) {
      return this.authService
                 .refreshAuthenticationObservable()
                 .flatMap((authenticationResult:AuthenticationResult) => {
                    if (authenticationResult.IsAuthenticated == true) {
                         this.authService.setAuthorizationHeader(request.headers);
                      return this.http.request(url, request);
                    }
                    return Observable.throw(initialError);
        });
    }
    

    This code must be integrated into a custom sub class of the Http one:

    An approach could be to extend the HTTP object to intercept errors:

    @Injectable()
    export class CustomHttp extends Http {
      constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
        super(backend, defaultOptions);
      }
    
      request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
        console.log('request...');
        return super.request(url, options).catch(res => {
          // do something
        });        
      }
    
      get(url: string, options?: RequestOptionsArgs): Observable<Response> {
        console.log('get...');
        return super.get(url, options).catch(res => {
          // do something
        });
      }
    }
    

    and register it as described below:

    bootstrap(AppComponent, [HTTP_PROVIDERS,
        new Provider(Http, {
          useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => new CustomHttp(backend, defaultOptions),
          deps: [XHRBackend, RequestOptions]
      })
    ]);
    

    For more details have a look at these questions:

    • Handling refresh tokens using rxjs
    • Angular 2 - How to get Observable.throw globally
    0 讨论(0)
提交回复
热议问题