我有一个 HttpInterceptor
监听特定的 JWT token 事件(token_expired
、token_not_provided
和 token_invalid
)可以在工作流程的不同时间发生。
当用户导航到不同的路由或在同一路由中发送 AJAX 请求时(例如检索数据、保存表单等),这些事件可能会发生。
当拦截器检测到任何这些特定事件时,它会提示用户再次输入登录凭据(使用模式)并将请求排队等待稍后处理(在用户再次登录后)。这一点很重要,因为提交的数据不会丢失(例如,更新订单或客户时)。
我的拦截器代码的简化版本是:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private injector: Injector) {}
router: Router;
auth: AuthService;
api: APIService;
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.router = this.injector.get(Router);
this.auth = this.injector.get(AuthService);
let token = this.auth.getToken();
let headers = {
'Content-Type':'application/json',
};
if (token) {
(<any>headers).Authorization = `Bearer ${token}`;
}
request = request.clone({
setHeaders: headers
});
return next.handle(request).do((event: HttpEvent<any>) => {
}, (err: any) => {
if (err instanceof HttpErrorResponse) {
let msg = typeof(err.error) === 'string' ? JSON.parse(err.error) : err.error;
if (msg && msg.error && ['token_not_provided', 'token_expired','token_invalid'].indexOf(msg.error) > -1) {
this.auth.queueFailedRequest(request);
//set the intended route to current route so that after login the user will be shown the same screen
this.auth.setIntendedRoute(this.router.url);
//show the login popup
this.auth.promptLogin();
}
}
}
});
}
}
AuthService的相关部分是:
queue: Array<HttpRequest<any>> = [];
queueFailedRequest(request): void {
this.queue.push(request);
}
retryFailedRequests(): void {
this.queue.forEach(request => {
this.retryRequest(request);
});
this.queue = [];
}
retryRequest(request): void {
if (request.method === 'GET') {
this.apiService.get(request.urlWithParams);
}
else if (request.method === 'POST') {
this.apiService.post(request.urlWithParams, request.body || {});
}
}
当然,在成功登录后,我会调用 retryFailedRequests()
。
到目前为止一切顺利,实际上所有 HTTP 请求都在排队并在登录成功时发送。
现在来看问题 - 如果代码的结构如本例所示(取自 EditOrder
组件):
updateOrder() {
this.api.updateOrder(this.data).subscribe(res => {
if (res.status === 'success') {
alert('should be triggered even after login prompt');
}
});
}
然后,如果用户在此过程中需要重新登录,一旦retryFailedRequests()
方法处理完队列,将永远不会触发警报。
所以问题是确保原始 promise 与 HTTP 请求一起排队并在队列完成处理时解决的最佳方法是什么?
最佳答案
所以我遇到了同样的问题,我们最终通过将 handle()
返回的 Observable 包装在我们自己的 Observable 中,并从 intercept()
.
在下面的示例中,this.requestQueue
只是一个对象数组,我们可以在其中存储来自新 Observable 的订阅者、原始 HttpRequest 和原始 HttpHandler。如果身份验证尚未完成,我们这样做是为了对请求进行排队。
所以这是拦截
方法:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Return a new Observable
return new Observable<HttpEvent<any>>((observer) => {
if (this.authInProgress) {
this.requestQueue.push({eventObserver: observer, request: req, handler: next});
} else {
this.processRequest(observer, req, next);
}
});
}
因此,无论谁发出初始 HTTP 请求,都可以订阅一个在我们这样说之前不会完成的可观察对象,这是至关重要的。关键是我们保留对订阅者对象 (observer
) 的引用,以便我们稍后可以使用它来将响应返回到它们所属的位置。但是我们实际上并没有在这个方法中处理请求。
这是processRequest()
:
private processRequest(eventObserver, request, handler) {
// Handle the request
// - pass the response along on success
// - handle 401 errors and pass along all others
handler.handle(request).subscribe(
(event: HttpEvent<any>) => { eventObserver.next(event); },
(err: any) => {
if (err instanceof HttpErrorResponse && err.status === 401) {
if (!this.authInProgress) {
// If this is the first 401 then we kick off some
// auth processes and mark authInProgress as true.
this.authInProgress = true;
}
// Save this request for later
this.requestQueue.push({eventObserver, request, handler});
} else {
eventObserver.error(err);
}
},
() => { eventObserver.complete(); }
);
}
所以在processRequest()
中,我们实际上是通过调用handler.handle(request)
来发送请求的。我们立即订阅,如果请求成功,我们将通过调用 eventObserver.next(event)
发送响应事件。这会将响应发送回订阅我们在 intercept()
中返回的 Observable 的任何人。
如果我们收到一个错误并且是 401,我们只是保存请求以备后用,就像我们在 intercept()
中所做的那样。
稍后,当我们准备好处理所有这些排队的请求时,我们将它们从 requestQueue
中pop()
并将它们传递给 processRequest( )
。这一次,假设您的身份验证有效,他们不会出错并且成功响应将返回给上游订阅者。
关于Angular 4 - 拦截http请求并在登录后重新发送,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46554408/