我有一个HttpInterceptor
,可以监听可能在工作流程的不同时间发生的特定JWT令牌事件(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()
方法完成处理队列后将永远不会触发警报。
所以问题是确保原始承诺与HTTP请求一起排队并在队列完成处理时解决的最佳方法是什么?
答案 0 :(得分:0)
所以我遇到了同样的问题,我们最终通过将handle()
返回的Observable包装到我们自己的Observable中,然后从intercept()
返回的Observable来解决了这个问题。 >
在下面的示例中,this.requestQueue
只是对象的数组,我们可以在其中存储来自新Observable,原始HttpRequest和原始HttpHandler的订阅服务器。如果身份验证尚未完成,我们将对请求进行排队。
这是intercept
方法:
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()
中所做的那样,我们只是保存请求以供以后使用。
稍后,当我们准备处理所有这些排队的请求时,我们将pop()
从requestQueue
中移出并将它们传递给processRequest()
。这次,假设您的身份验证有效,则它们不会出错,并且成功响应将返回给上游订户。