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

时间:2017-10-03 22:33:01

标签: angular observable angular-http-interceptors

我有一个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请求一起排队并在队列完成处理时解决的最佳方法是什么?

1 个答案:

答案 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()。这次,假设您的身份验证有效,则它们不会出错,并且成功响应将返回给上游订户。