From 4b9964bc89b9382af2427ce48b49491ac1d63f00 Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 31 Aug 2026 15:36:54 +0200 Subject: [PATCH] fix(api): stop bounding request body receipt at the header timeout (#2245) --- packages/hono/src/Server.ts | 6 ++- packages/hono/src/tests/ServerLimits.test.ts | 2 +- .../hono/src/tests/ServerTimeouts.test.ts | 44 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 packages/hono/src/tests/ServerTimeouts.test.ts diff --git a/packages/hono/src/Server.ts b/packages/hono/src/Server.ts index 8f59ec150..1489d3cd5 100644 --- a/packages/hono/src/Server.ts +++ b/packages/hono/src/Server.ts @@ -4,7 +4,8 @@ import {applyFluxerVersionHeader} from '@fluxer/hono/src/middleware/VersionHeade import {type Http2Bindings, type HttpBindings, type ServerType, serve} from '@hono/node-server'; import type {Env, Hono} from 'hono'; -const DEFAULT_REQUEST_TIMEOUT_MS = 30_000; +const DEFAULT_HEADERS_TIMEOUT_MS = 30_000; +const DEFAULT_REQUEST_TIMEOUT_MS = 120_000; const DEFAULT_KEEP_ALIVE_TIMEOUT_MS = 125_000; const DEFAULT_MAX_REQUESTS_PER_SOCKET = 1_000; @@ -12,6 +13,7 @@ interface ServerOptions { port: number; hostname?: string; onListen?: (info: {address: string; port: number}) => void; + headersTimeoutMs?: number; requestTimeoutMs?: number; keepAliveTimeoutMs?: number; maxRequestsPerSocket?: number; @@ -28,6 +30,7 @@ export function createServer(app: Hono, options: ServerO port, hostname, onListen, + headersTimeoutMs = DEFAULT_HEADERS_TIMEOUT_MS, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, keepAliveTimeoutMs = DEFAULT_KEEP_ALIVE_TIMEOUT_MS, maxRequestsPerSocket = DEFAULT_MAX_REQUESTS_PER_SOCKET, @@ -39,6 +42,7 @@ export function createServer(app: Hono, options: ServerO ...(hostname !== undefined && {hostname}), serverOptions: { keepAliveTimeout: keepAliveTimeoutMs, + headersTimeout: requestTimeoutMs > 0 ? Math.min(headersTimeoutMs, requestTimeoutMs) : headersTimeoutMs, requestTimeout: requestTimeoutMs, }, }, diff --git a/packages/hono/src/tests/ServerLimits.test.ts b/packages/hono/src/tests/ServerLimits.test.ts index 2e8a8e937..fb51ebec9 100644 --- a/packages/hono/src/tests/ServerLimits.test.ts +++ b/packages/hono/src/tests/ServerLimits.test.ts @@ -42,7 +42,7 @@ describe('Server limits', () => { const listening = await listen({port: 0}); server = listening.server; const httpServer = server as Server; - expect(httpServer.requestTimeout).toBe(30_000); + expect(httpServer.requestTimeout).toBe(120_000); expect(httpServer.keepAliveTimeout).toBe(125_000); expect(httpServer.maxRequestsPerSocket).toBe(1_000); expect(httpServer.headersTimeout).toBeLessThanOrEqual(httpServer.requestTimeout); diff --git a/packages/hono/src/tests/ServerTimeouts.test.ts b/packages/hono/src/tests/ServerTimeouts.test.ts new file mode 100644 index 000000000..e35afce95 --- /dev/null +++ b/packages/hono/src/tests/ServerTimeouts.test.ts @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {Hono} from 'hono'; +import {afterEach, describe, expect, it} from 'vitest'; +import {createServer} from '../Server'; + +const servers: Array<{close: (cb: () => void) => void}> = []; + +afterEach(async () => { + await Promise.all(servers.splice(0).map((s) => new Promise((r) => s.close(() => r())))); +}); + +function listen(options: Parameters[1]) { + const app = new Hono(); + app.post('/echo', async (ctx) => ctx.text(String((await ctx.req.text()).length))); + const server = createServer(app, options) as never as { + close: (cb: () => void) => void; + address: () => {port: number}; + headersTimeout: number; + requestTimeout: number; + }; + servers.push(server); + return server; +} + +describe('createServer timeouts', () => { + it('separates the header timeout from the total request timeout', () => { + const server = listen({port: 0}); + expect(server.headersTimeout).toBe(30_000); + expect(server.requestTimeout).toBe(120_000); + }); + + it('lets both be overridden', () => { + const server = listen({port: 0, headersTimeoutMs: 1_000, requestTimeoutMs: 2_000}); + expect(server.headersTimeout).toBe(1_000); + expect(server.requestTimeout).toBe(2_000); + }); + + it('never lets the header timeout exceed the request timeout', () => { + const server = listen({port: 0, requestTimeoutMs: 5_000}); + expect(server.requestTimeout).toBe(5_000); + expect(server.headersTimeout).toBeLessThanOrEqual(server.requestTimeout); + }); +});