简体   繁体   English

如何在角度http inteceptor中以异步样式缓存http请求?

[英]How to cache http requests in async style in angular http inteceptor?

I'm coding the angular 5 app. 我正在编写角度5应用程序。 There is refreshAccessToken in authentication service authentication servicerefreshAccessToken

refreshAccessToken(): Observable<ICredentials> {
     const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
     const httpParams = new HttpParams()
       .append('grant_type', 'refresh_token')
       .append('refresh_token', this.credentials.refresh_token)
       .append('client_id', Constants.CLIENT_ID)
       .toString();

     const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');

     return this.http.post(refreshTokenUrl, httpParams, { headers })
       .map((response: any) => {
         this.setCredentials(response);
         localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
         return response;
  });
}

I want to implement next alghorithm: 我想实现下一个alghorithm:

  1. Any http request failed because of unauthorized with status 401 由于状态为401的未授权,任何http请求都失败
  2. Try to get new access token from server 尝试从服务器获取新的访问令牌
  3. Repeat the request 重复请求

At the time while getting new access token, new http requests can be created, in this case I want to store them and repeat after new access token was recieved. 在获取新的访问令牌时,可以创建新的http请求,在这种情况下,我想存储它们并在收到新的访问令牌后重复。 To reach this purpose I've written the interceptor. 为了达到这个目的,我写了拦截器。

import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AuthenticationService } from '@app/core/authentication/authentication.service';
import { Urls, UrlsService } from '@app/shared/urls';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class UnauthorizedRequestInterceptor implements HttpInterceptor {
  newAccessToken$: Observable<ICredentials> = null;

  constructor(
    public authService: AuthenticationService,
    private router: Router,
    private urlsService: UrlsService) {
  }

  addAuthHeader(request: HttpRequest<any>) {
    if (this.authService.getCredentials()) {
      return request.clone({
        setHeaders: {
          'Authorization': 'Bearer ' + this.authService.getCredentials().access_token
        }
      });
    }
    return request;
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    request = this.addAuthHeader(request);

    return next.handle(request).catch((error: HttpErrorResponse) => {
      let handleRequests$ = null;

      if (this.isNeedNewAccessToken(error, request)) {
        handleRequests$ = this.handleRequestWithNewAccessToken(request, next);
      }

      return handleRequests$ ||
        (this.isUnathorizedError(error)
          ? Observable.empty()
          : Observable.throw(error));
    });
  }

  logout() {
    this.authService.logout();
    this.router.navigate(['login']);
  }

  private isNeedNewAccessToken(error: HttpErrorResponse, request: HttpRequest<any>): boolean {
    return this.isUnathorizedError(error)
      && this.authService.isAuthenticated()
      && this.isSignInRequest(request);
  }

  private getNewAccessToken(): Observable<ICredentials> {
    if (!this.newAccessToken$) {
      this.newAccessToken$ = this.authService.refreshAccessToken();
    }
    return this.newAccessToken$;
  }

  private isUnathorizedError(error: HttpErrorResponse) {
    return error.status === 401;
  }

  private handleRequestWithNewAccessToken(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    return this.getNewAccessToken()
      .mergeMap(() => {
        request = this.addAuthHeader(request);
        return next.handle(request);
      })
      .catch((err: HttpErrorResponse) => {
        if (err.error.error === 'invalid_grant') {
          this.logout();
        }
        return Observable.empty();
      });
  }

  private isNotSignInRequest(request: HttpRequest<any>): boolean {
    return request.url !== this.urlsService.getUrl(Urls.TOKEN);
  }
}

The behaviour of this interceptor is really strange. 这个拦截器的行为真的很奇怪。 On each mergeMap on the handleRequestWithNewAccessToken the angular starts new post httpRequest. handleRequestWithNewAccessToken上的每个mergeMap上,angular开始新的post httpRequest。 I've expected that the observable returned from refreshAccessToken (function from authenticationService , code at the top) would be resolved only once. 我期望从refreshAccessToken返回的refreshAccessToken (来自authenticationService函数,顶部的代码)只能解析一次。 I don't understand why it is fired for each merge map? 我不明白为什么每个合并地图被解雇? I expected the next: 我期待下一个:

  1. I have observable - http request for token 我有observable - 对token的http请求
  2. I use mergeMap - when http request finished, all callbacks that was added with mergeMap will be executed. 我使用mergeMap - 当http请求完成时,将执行随mergeMap添加的所有回调。

I was think to store requests that I need to handle in the global variable and invoke them in the subscribe() to http request, but there is problem, that each request should be resolved in the initial stream inside interceptor. 我想存储我需要在全局变量中处理的请求并在subscribe()调用它们到http请求,但是有问题,每个请求都应该在拦截器内的初始流中解析。 I can't do smth like this: .subscribe(token => this.httpClient.request(storedRequest) because this will create new request, so all actions should be happened inside the observable chain. 我不能这样做: .subscribe(token => this.httpClient.request(storedRequest)因为这将创建新请求,所以所有操作都应该发生在可观察链中。

Can you please help me to find solution? 能帮我找到解决方案吗?

PS This solution is working, but I want to get rid off unnecessary TOKEN requests, fe if page need to make 5 requests and token have expired - interceptor will make 5 requests for token. PS这个解决方案正在运行,但是我想摆脱不必要的TOKEN请求,如果页面需要发出5个请求并且令牌已经过期 - 拦截器将发出5个令牌请求。

I think your code is good and all you have to do is share the request for the new token. 我认为您的代码很好,您所要做的就是share新令牌的请求。

refreshAccessToken(): Observable<ICredentials> {
        const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
        const httpParams = new HttpParams()
            .append('grant_type', 'refresh_token')
            .append('refresh_token', this.credentials.refresh_token)
            .append('client_id', Constants.CLIENT_ID)
            .toString();

        const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');

        return this.http.post(refreshTokenUrl, httpParams, { headers })
            .map((response: any) => {
                this.setCredentials(response);
                localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
                return response;
            })
            .share(); // <- HERE
    }

Note share operator at the end of return 注意return结束时的share运算符

EDIT: 编辑:

I also think you don't ever set back this.newAccessToken$ to null . 我也认为你永远不会将this.newAccessToken$设置为null Maybe consider adding set to null to finally like this: 也许可以考虑将set添加到nullfinally这样:

private getNewAccessToken(): Observable<ICredentials> {
    if (!this.newAccessToken$) {
        this.newAccessToken$ = this.authService.refreshAccessToken()
            .finally(() => {
                this.newAccessToken$ = null;
            });
    }
    return this.newAccessToken$;
}

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

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