timeoutMiddleware function

Middleware timeoutMiddleware(
  1. Duration duration
)

Middleware that enforces a timeout on the request processing chain.

If the chain takes longer than the specified duration, the middleware will close the response with a 504 Gateway Timeout status code.

duration specifies the maximum amount of time the request is allowed to be processed before timing out.

Implementation

Middleware timeoutMiddleware(Duration duration) {
  return (ctx, next) {
    Response completeWithTimeout() {
      // Prevent further handlers from writing
      ctx.abort();
      if (!ctx.isClosed) {
        // Write timeout directly to the underlying response to avoid hitting
        // higher-level render logic which may ignore writes after abort.
        ctx.response.statusCode = HttpStatus.gatewayTimeout;
        if (ctx.wantsJson) {
          ctx.response.headers.contentType = ContentType.json;
          ctx.response.write('{"error":"Gateway Timeout","status":504}');
        } else if (ctx.acceptsHtml) {
          ctx.response.headers.contentType = ContentType.html;
          ctx.response.write(
            '<!DOCTYPE html><html><head><title>504</title></head>'
            '<body style="font-family:system-ui;display:flex;justify-content:center;'
            'align-items:center;min-height:100vh;margin:0"><div style="text-align:center">'
            '<h1 style="font-size:4rem;font-weight:300;color:#868e96">504</h1>'
            '<p>Gateway Timeout</p></div></body></html>',
          );
        } else {
          ctx.response.write('Gateway Timeout');
        }
        unawaited(ctx.response.close());
      }
      return ctx.response;
    }

    final elapsed = DateTime.now().difference(ctx.request.startedAt);
    final remaining = duration - elapsed;

    if (remaining <= Duration.zero) {
      return Future<Response>.value(completeWithTimeout());
    }

    final completer = Completer<Response>();
    late Timer timer;

    Future<Response>.value(next())
        .then((result) {
          if (!completer.isCompleted) {
            timer.cancel();
            completer.complete(result);
          }
        })
        .catchError((Object error, StackTrace stackTrace) {
          if (!completer.isCompleted) {
            timer.cancel();
            completer.completeError(error, stackTrace);
          }
        });

    timer = Timer(remaining, () {
      if (!ctx.isClosed && !completer.isCompleted) {
        final response = completeWithTimeout();
        completer.complete(response);
      }
    });

    return completer.future;
  };
}