Angular2 http重试逻辑

Angular2 http retry logic

本文关键字:重试 http Angular2      更新时间:2023-09-26

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

在angular 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应用程序中实现类似的逻辑呢?

这可以在Angular2中通过扩展Http类和利用flatMap等可观察运算符透明地完成。

以下是一些示例代码:

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

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

  • 使用rxjs处理刷新令牌
  • Angular 2-如何在全球范围内获得Observable.throw

在我最近的项目shafihuzaib/cdp ng样板中,我不得不做一些类似的事情,并找到了这个问题的答案。我无法采用上述建议的解决方案,因为它感觉很复杂,有些不可取。所以在我实现了一个解决方案之后,我回来留下了我的解决方案。然而,不同的是,在我的情况下,我有两个这样的代币。

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

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

对于一个新人来说,这可能看起来有点复杂,但我相信它会更有效率。

在以下链接中,请忽略与此问题无关的细节,并专注于建议解决方案的使用。

tokenValidatedRequest()在我的项目中的实际实施。

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

它是如何在其他服务中使用的!

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

我终于订阅了!

    this.realTimeParkingService.getAllParkingSpaces().subscribe( r => {
  this.parkingSpaces = r;
});