NestJS TimeoutInterceptor and HttpService AbortSignal interoperability
0 reputation · 08 Apr 2021, 05:59 UTC
0 reputation · 08 Apr 2021, 05:59 UTC
Determine whether a NestJS HTTP server route protected by TimeoutInterceptor can automatically cancel an outbound HttpService request when the server-side timeout fires.
TimeoutInterceptor aborts the inbound response using Node’s AbortController, but it does not automatically attach that signal to outbound clients. HttpService accepts an AbortSignal in its request options, yet the wiring must be done manually in each handler. No single documented NestJS API propagates the request signal to every downstream transport, so the boundary between server cancellation and client cancellation remains an open design decision.
AbortSignal created by TimeoutInterceptor be shared with HttpService without a custom interceptor?34775 reputation · 08 Apr 2021, 12:27 UTC
1. Can the AbortSignal from TimeoutInterceptor be shared automatically?
No. TimeoutInterceptor creates a new AbortController that is only used to abort the inbound response stream. NestJS does not expose that controller in the request context, nor does it inject the signal into downstream transports. Therefore, an outbound HttpService call will continue running unless you explicitly pass the signal.
2. What cleanup is required?
HttpService returns an RxJS Observable (or a promise when toPromise() is called). If the request is aborted, the observable completes with an AbortError and the underlying Node HTTP request is terminated. You must catch this error to avoid unhandled promise rejections..catch() or a try/catch around the HttpService call when you forward the signal.Ensure you are using @nestjs/axios (the official HttpService). Import it in the module:
import { HttpModule } from '@nestjs/axios';
@Module({ imports: [HttpModule] })
export class AppModule {}
In your controller, inject HttpService and the ExecutionContextHost if you need the request object. The TimeoutInterceptor attaches the abort controller to the request via req.abortController (if you expose it). Since NestJS does not expose it automatically, you can retrieve the controller from the request manually:
@UseInterceptors(TimeoutInterceptor)
@Get('proxy')
async proxy(@Req() request: Request, @Inject(HttpService) http: HttpService) {
const controller = request.headers['x-abort-controller'] as AbortController;
// If you cannot access the controller directly, you must create one here.
const signal = controller ? controller.signal : new AbortController().signal;
try {
const response = await http.get('https://example.com', { signal }).toPromise();
return response.data;
} catch (err) {
if (err.name === 'AbortError') {
throw new HttpException('Request timed out', HttpStatus.GATEWAY_TIMEOUT);
}
throw err;
}
}
Note: The above assumes you expose the controller via a custom header or a shared context. The most robust way is to write a small custom interceptor that attaches the controller to the request object so downstream handlers can read it.
Cleanup: The AbortError is thrown by HttpService when the signal is aborted. Catch it as shown, log if necessary, and re‑throw a HttpException if you want the client to receive a 504/503. This guarantees no unhandled rejections and that the socket is closed.
Optional: If you want every outbound call to automatically use the request’s abort signal, create a custom interceptor:
@Injectable()
export class PropagateAbortInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const controller = request.abortController;
if (controller) {
// Attach to HttpService via a custom provider or context
// e.g., set a global variable or use request context
}
return next.handle();
}
}
Register it globally or per controller.
request.aborted before and after the timeout to confirm the controller fires.HttpService promise rejects with AbortError when the timeout occurs.netstat to ensure the outbound socket is closed after abort.node --unhandled-rejections=strict to catch any missing catch blocks.To tailor the recommendation further, could you confirm which NestJS major version you are using (e.g., 10.x) and whether you are injecting HttpService directly into the controller or via a provider? This will help adjust the code snippets for the exact API shape.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.