简体   繁体   English

Angular2 http重试逻辑

[英]Angular2 http retry logic

I have an API with token based authentication mechanism. 我有一个基于令牌的身份验证机制的API。 After successful signin I store two tokens in the browser's local storage - access and refresh token. 成功登录后,我在浏览器的本地存储中存储了两个令牌 - 访问和刷新令牌。 The access token contains all necessary information required to authorize a user on the server side and it has expiration date. 访问令牌包含在服务器端授权用户所需的所有必要信息,并且它具有到期日期。 When the access token is expired the client could request a new access token using refresh token and in the response it will get a pair of new tokens. 当访问令牌过期时,客户端可以使用刷新令牌请求新的访问令牌,并且在响应中它将获得一对新令牌。

In angular 1.x the implementation is pretty simple and straightforward. 在角度1.x中,实现非常简单明了。 For instance we could use interceptors: 例如,我们可以使用拦截器:

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

But how to implement similar logic in angular 2 / rxjs app? 但是如何在angular 2 / rxjs应用程序中实现类似的逻辑?

This can be done transparently in Angular2 by extending the Http class and leveraging observable operators like flatMap . 这可以通过扩展Http类并利用像flatMap这样的可观察运算符在Angular2中透明地完成。

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: 此代码必须集成到Http one的自定义子类中:

An approach could be to extend the HTTP object to intercept errors: 一种方法可以是扩展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
    });
  }
}

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: 有关详细信息,请查看以下问题:

I had to do something similar in my recent project shafihuzaib/cdp-ng-boilerplate and landed on this question for my answer. 我不得不在我最近的项目shafihuzaib / cdp-ng-boilerplate中做类似的事情,并在这个问题上找到我的答案。 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. 这里最重要的是func()应该返回一个Observable ,以便可以相应地使用它。

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()实际实现

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM