Angular - RxJS - 在身份验证错误的情况下重试Http

时间:2017-08-04 05:50:33

标签: angular http rxjs

我是使用Angular和Rxjs的新手。

基本上,应用程序使用Api并需要提供有效的身份验证令牌。 此令牌是短暂的,如果它已过期,则可以刷新。

我编写了一个特定的http服务,它为每个请求添加了正确的标头,如果验证失败,则尝试刷新令牌并重试。

以下是我实施它的方式:

export class HttpAService extends Http {

  constructor(
    backEnd: XHRBackend,
    options: RequestOptions,
    private authenticationService: AuthenticationService
  ) {
    super(backEnd, options);
    // get an authentication token from an authentication service
    let token = authenticationService.token;
    options.headers.set('Authorization', `Bearer ${token}`);
  }

  private request1(
    url: string | Request,
    options?: RequestOptionsArgs
  ): Observable<Response> {
    // get a token from an authentication service
    let token = this.authenticationService.token;
    // add it to the request headers
    if (typeof url === 'string') {
      if (!options) {
        options = { headers: new Headers };
      }
      options.headers.set('Authorization', `Bearer ${token}`);
    } else {
      url.headers.set('Authorization', `Bearer ${token}`);
    }
    // make the request
    return super.request(url, options);
  }

  public request(
    url: string | Request,
    options?: RequestOptionsArgs
  ): Observable<Response> {
    // init retry flag
    let retried: boolean = false;
    // make the request and retry one time in case of failure
    const obs = Observable.defer(() => this.request1(url, options));
    return obs.retryWhen(attempt => {
      // request failed
      return attempt.flatMap(res => {
        // if it's the first round and an authentication error try to refresh token
        if ((!retried) && (500 === res.status) && ('Unauthenticated.' == res.json().message)) {
          retried = true;
          // refresh attempts to create a new token that can be retrieved using authenticationService.getToken.
          return this.authenticationService.refresh();
        }
        return Observable.throw(res);
      })
    })
  }
}

我遇到的问题是让重试请求使用新令牌(换句话说,在重试期间重新运行request1函数)。看起来defer(...)就是这个伎俩。

我想知道这个实现是否正确,是否有更优雅的方式来实现这种行为。

感谢您的回复。

1 个答案:

答案 0 :(得分:0)

对于那些基于此问题的人,我认为推荐的运营商是retryWhen。这是一个例子。

get(url:string): Observable<Object> {
  return this.httpClient.get(url, {headers: this.authHeaders}).pipe(
    retryWhen(errors => errors.pipe(
      switchMap((e:HttpErrorResponse) => {
        if (e.status === 403) return this.refreshAuthToken();
        console.warn("HTTP GET error", url, e); //like 500 internal server error
        throw e;
      })
    ))
  )
}

但是有一个缺陷。虽然refreshAuthToken完美地工作,但原始的HTTP observable仍然停留在未经过身份验证的令牌上。不知道怎么解决这个问题。

所以现在我使用一个有效的解决方案,但它以递归方式调用我的get方法,而不是使用运算符。

get(url:string): Observable<Object> {
  return this.httpClient.get(url, {headers: this.authHeaders}).pipe(
    catchError((e:HttpErrorResponse) => {
      if (e.status === 403) {
        return this.refreshAuthToken().pipe(
          switchMap(success => success? this.get(url) : of(null)) //recursive call not cool :(
        )
      }
      console.warn("HTTP GET error", url, e); //like 500 internal server error
      return of(null);
    })
  )
}

希望有人提供更好的答案。