Angular 4 - 拦截http请求并在登录后重新发送

问题描述 投票:2回答:1

我有一个HttpInterceptor,它可以监听可能在工作流程的不同时间发生的特定JWT令牌事件(token_expiredtoken_not_providedtoken_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请求一起排队并在队列完成处理时解决的最佳方法是什么?

angular observable angular-http-interceptors
1个回答
0
投票

所以我遇到了同样的问题,我们最终通过包装handle()在我们自己的Observable中返回的Observable来解决它,并从intercept()返回。

在下面的示例中,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请求,都要订阅一个在我们这样说之前无法完成的观察,这是至关重要的。关键是我们保留对Subscriber对象(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。这一次,假设您的auth工作,他们将不会出错并且成功响应将返回到上游订户。

© www.soinside.com 2019 - 2024. All rights reserved.