Reputation: 144
I've been working to construct an Interceptor that will:
In this case, I must manage two types of retry (for token management):
I have trouble managing two nexted next.handle() The second one is never fired and the request is not retried...
Here's my code :
export class HttpInterceptorService implements HttpInterceptor {
constructor(private authService: AuthenticationService,
private pryvAccessesService: PryvAccessesService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const uuid = new Date().valueOf();
if (!req.url.includes('api/authentication/token/')) {
const authReq = this.addTokensRequest(req);
console.log(uuid + " : sending request with tokens : " + req.url);
return next.handle(authReq).pipe(
map(res => {console.log(uuid + ' : request succedeed ! ' + req.url); return res;}),
catchError(err => {
console.log(uuid + ' : request failed ! ' + req.url);
// Django invalid token
if (err.status === 401) {
console.log(uuid + " : sending django refresh request : " + req.url);
return this.authService.refresh().pipe(
map((res: any) => {
const authReqRefreshed = this.addTokensRequest(req);
console.log(uuid + " : sending refreshed request : " + req.url);
return next.handle(authReqRefreshed).pipe(
map(res => {console.log( uuid + ' : success refreshed request ! ' + req.url); return res;}),
catchError(err => {console.log(uuid + " failed refreshed request : " + req.url); return throwError(err);})
);
})
)
// Pryv invalid token
} else if(err.status === 400 && err.error['error'] == 'Invalid access token within Pryv usage') {
console.log(uuid + ' : bad pryv token : ' + req.url);
return this.getNewTokenForUrl(req).pipe(
map((res: string) => {
console.log(uuid + " : success get new token for URL : " + req.url);
const authReqNewPryv = this.addTokensRequest(req); // <= code goes up to here
return next.handle(authReqNewPryv).pipe(
map(res => {console.log(uuid + ' : success request with new pryv token ! ' + req.url); return res;}), // <= this is never triggered
catchError((err) => {console.log(uuid + " : error request with new pryv token : " + req.url); return throwError(err);}) // <= this also is never triggered
);
}), catchError((err) => {console.log(uuid + " fail get new pryv token : " + req.url); return throwError(err);})
);
// Unrelated error
} else {
console.log(uuid + " error not related to authentication : " + req.url); return throwError(err);
}
})
);
} else {
console.log(uuid + " authentication, passing thru " + req.url); return next.handle(req);
}
Here for example, the 'console.log(uuid + " : success get new token for URL : " + req.url);' log will never be shown and the retry request will never be sent. How to manage it ? I've tried sending another HttpRequest, but the interceptor is catching it...
Thanks !
Upvotes: 0
Views: 1660
Reputation: 2628
You should split the responsibles of this interceptor to multiple interceptor, something like below:
Inteceptors token in AppModule:
// Important: Order of Interceptors matters!
export const httpInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: RetryInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: AuthTokensManagementInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: TelemetryInterceptor, multi: true },
];
Auth token interceptor:
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return of(req).pipe(
mergeMap(() => getAcessToken() /*and add it to local storage*/)
mergeMap((accessToken: string) => {
if (accessToken) {
req = req.clone({
headers: req.headers.set(AuthorizationHeader, `${accessToken}`)
})
}
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401)
this.authTokensManager.invalidateAccessToken(error.url);
}
}
return throwError(error);
})
);
}));
}
Retry Interceptor:
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return of(req).pipe(
mergeMap((req: HttpRequest<any>) => {
return next.handle(req).pipe(
// Will continue the pipe if a request attempt has succeeded without errors, or exceeded max retries.
retryWhen((errors: Observable<any>) => errors.pipe(
// Enable only 2 retries, +1 for the original request.
take(this.maxRetries + 1),
skipWhile(() => disableRetry),
mergeMap((err: HttpErrorResponse, i: number) => {
if (i >= this.maxRetries) {
return throwError(err);
}
const errorCode = err.status;
if (errorCode == 401) {
return of(err);
}
return throwError(err);
}),
))
)
})
);
}
Upvotes: 1