gpt4 book ai didi

javascript - 如何在 Angular http 拦截器中以异步方式缓存 http 请求?

转载 作者:太空狗 更新时间:2023-10-29 18:36:55 24 4
gpt4 key购买 nike

我正在编写 Angular 5 应用程序的代码。有 refreshAccessTokenauthentication service

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

我想实现下一个算法:

  1. 任何 http 请求都因未经授权而失败,状态为 401
  2. 尝试从服务器获取新的访问 token
  3. 重复请求

在获取新的访问 token 时,可以创建新的 http 请求,在这种情况下,我想存储它们并在收到新的访问 token 后重复。为了达到这个目的,我编写了拦截器。

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

这个拦截器的行为真的很奇怪。在每个 mergeMaphandleRequestWithNewAccessToken 上 Angular 开始新的帖子 httpRequest。我期望可观察到从 refreshAccessToken 返回(来自 authenticationService 的函数,顶部的代码)只会被解析一次。我不明白为什么每个合并 map 都会触发它?我期待下一个:

  1. 我有可观察的 - 对 token 的 http 请求
  2. 我使用 mergeMap - 当 http 请求完成时,将执行所有使用 mergeMap 添加的回调。

我想将我需要处理的请求存储在全局变量中并在 subscribe() 中调用它们到 http 请求,但存在问题,即每个请求都应在拦截器内的初始流中解析。我不能这样做:.subscribe(token => this.httpClient.request(storedRequest)因为这会创建新请求,所以所有操作都应该在可观察链内发生。

你能帮我找到解决办法吗?

PS 此解决方案有效,但我想摆脱不必要的 token 请求,例如如果页面需要发出 5 次请求并且 token 已过期 - 拦截器将发出 5 次 token 请求。

最佳答案

我认为您的代码很好,您所要做的就是分享新 token 的请求。

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
}

注意 return 末尾的 share 操作符

编辑:

我还认为您永远不会将 this.newAccessToken$ 设置回 null。也许考虑将 set to null 添加到 finally 中,如下所示:

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

关于javascript - 如何在 Angular http 拦截器中以异步方式缓存 http 请求?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51987189/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com