首页 文章

Angular2 http重试逻辑

提问于
浏览
7

我有一个基于令牌的身份验证机制的API . 成功登录后,我在浏览器的本地存储中存储了两个令牌 - 访问和刷新令牌 . 访问令牌包含在服务器端授权用户所需的所有必要信息,并且它具有到期日期 . 当访问令牌过期时,客户端可以使用刷新令牌请求新的访问令牌,并且在响应中它将获得一对新令牌 .

在角度1.x中,实现非常简单明了 . 例如,我们可以使用拦截器:

httpInterceptor.$inject = ['$httpProvider'];
function httpInterceptor($httpProvider) {
  $httpProvider.interceptors.push(handleStaleAccessToken);

  handleStaleAccessToken.$inject = ['$q', '$injector', 'session'];
  function handleStaleAccessToken($q, $injector, session) {

    function logoutAndRedirect() {
      var authenticationRedirect = $injector.get('authenticationRedirect');
      session.destroy();
      authenticationRedirect.toLoginPage();
    }

    return {
      responseError: function(rejection) {
        // Do nothing for non 403 errors
        if (rejection.status !== 403) {
          return $q.reject(rejection);
        }

        var errorCode = rejection.data.error && rejection.data.error.code;
        if (errorCode === 'access_token_expired') {
          var $http = $injector.get('$http');

          // Refresh token
          var params = { refreshToken: session.getRefreshToken() };
          return $http.post('/api/auth/refresh', params).then(function(response) {
            session.setTokens(response.data);
            // Re try failed http request
            return $http(rejection.config);
          }).catch(function(error) {
            logoutAndRedirect();
            return $q.reject(error);
          });
        } else {
          logoutAndRedirect();
        }

        return $q.reject(rejection);
      }
    };
  }
}

但是如何在angular 2 / rxjs应用程序中实现类似的逻辑?

2 回答

  • 7

    这可以通过扩展 Http 类并利用 flatMap 之类的可观察运算符在Angular2中透明地完成 .

    以下是一些示例代码:

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

    此代码必须集成到 Http 的自定义子类中:

    一种方法可以是扩展HTTP对象以拦截错误:

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

    并按如下所述注册:

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

    有关详细信息,请查看以下问题:

  • 1

    在我最近的项目中,我不得不做类似的事情shafihuzaib/cdp-ng-boilerplate并在这个问题上找到答案 . 我无法寻求上述建议的解决方案,因为它感觉很复杂而且不可取 . 所以我在实施之后回来留下我的解决方案 . 然而,不同的是,在我的情况下,我有两个这样的令牌 .

    因此,每个需要检查令牌有效性的请求都会在此函数中调用 .

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

    这里最重要的是 func() 应该返回 Observable ,以便可以相应地使用它 .

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

    对于一个新人来说,这似乎有点复杂,但我相信它会更有效率 .

    在以下链接中,请忽略与此问题无关的详细信息,并关注建议的解决方案的使用 .

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

相关问题