【问题标题】:Angular2 http retry logicAngular2 http重试逻辑
【发布时间】:2016-04-15 16:35:12
【问题描述】:

我有一个基于令牌的身份验证机制的 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 app 中实现类似的逻辑呢?

【问题讨论】:

    标签: javascript http angular rxjs


    【解决方案1】:

    这可以通过扩展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]
      })
    ]);
    

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

    【讨论】:

    • 你的方法和我的一样……对我来说唯一的问题是并行请求。如果我订阅了 3 个不同的 http 请求……那么每个请求都将具有相同的令牌。第一个只会使令牌无效,其他 2 个 http 请求将失败。有什么建议吗?
    【解决方案2】:

    我必须在我最近的项目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;
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-26
      • 1970-01-01
      • 2015-11-11
      • 2017-03-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多