Skip to content

Commit b614c0d

Browse files
tianzhouclaude
andauthored
fix(http): raise keep-alive timeout above proxy idle timeouts (#473)
The HTTP transport kept Node's default keepAliveTimeout of 5s. Proxies that pool upstream connections (AWS ALB, API Gateway VPC links, nginx) hold them idle for 60s, so a request written into a socket Node was closing surfaced as a proxy 502/503 that never reached DBHub's logs. Set keepAliveTimeout to 65s and headersTimeout to 66s so the proxy is always the side that closes an idle connection. Hard-coded on purpose; no new flag. Fixes #472 Claude-Session: https://claude.ai/code/session_013piqks7dS3HsaAKC6YVWLN Co-authored-by: Claude <[email protected]>
1 parent f7b3146 commit b614c0d

3 files changed

Lines changed: 66 additions & 0 deletions

File tree

‎src/server.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { getToolsForSource } from "./utils/tool-metadata.js";
1818
import { startConfigWatcher } from "./utils/config-watcher.js";
1919
import { validateOrigin, buildAllowedHosts, getSelfHosts, ALLOW_ANY_HOST } from "./utils/cross-origin.js";
2020
import { validateAuthToken } from "./utils/auth-token.js";
21+
import { configureHttpKeepAlive } from "./utils/http-keep-alive.js";
2122

2223
// Create __dirname equivalent for ES modules
2324
const __filename = fileURLToPath(import.meta.url);
@@ -299,6 +300,12 @@ See documentation for more details on configuring database connections.
299300
// is registered. Matches the pattern used in utils/ssh-tunnel.ts.
300301
const httpServer = http.createServer(app);
301302

303+
// Outlive proxy idle timeouts (ALB/API Gateway/nginx default to 60s) so
304+
// the proxy, not DBHub, closes idle connections. Otherwise a request
305+
// written into a socket Node is closing surfaces as a proxy 502/503
306+
// that never reaches our logs (issue #472).
307+
configureHttpKeepAlive(httpServer);
308+
302309
httpServer.on('error', (err) => {
303310
const displayHost = host!.includes(':') ? `[${host!}]` : host!;
304311
console.error(`Failed to bind HTTP server to ${displayHost}:${port}: ${err.message}`);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, it, expect } from "vitest";
2+
import http from "http";
3+
import {
4+
configureHttpKeepAlive,
5+
HTTP_KEEP_ALIVE_TIMEOUT_MS,
6+
HTTP_HEADERS_TIMEOUT_MS,
7+
} from "../http-keep-alive.js";
8+
9+
describe("configureHttpKeepAlive", () => {
10+
it("raises keep-alive above a 60s proxy idle timeout (issue #472)", () => {
11+
const server = http.createServer();
12+
expect(server.keepAliveTimeout).toBe(5_000); // Node default, the bug
13+
14+
configureHttpKeepAlive(server);
15+
16+
expect(server.keepAliveTimeout).toBe(HTTP_KEEP_ALIVE_TIMEOUT_MS);
17+
expect(server.headersTimeout).toBe(HTTP_HEADERS_TIMEOUT_MS);
18+
expect(server.keepAliveTimeout).toBeGreaterThan(60_000);
19+
server.close();
20+
});
21+
22+
it("keeps headersTimeout strictly above keepAliveTimeout, as Node requires", () => {
23+
expect(HTTP_HEADERS_TIMEOUT_MS).toBeGreaterThan(HTTP_KEEP_ALIVE_TIMEOUT_MS);
24+
});
25+
});

‎src/utils/http-keep-alive.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type http from "http";
2+
3+
/**
4+
* Idle keep-alive timeout for the HTTP transport, in milliseconds.
5+
*
6+
* Node's default is 5s. Reverse proxies and load balancers that pool
7+
* upstream connections hold them idle for longer than that (AWS ALB and
8+
* API Gateway VPC links default to 60s, as does nginx `keepalive`). When the
9+
* proxy writes a request into a socket that Node is closing at the same
10+
* moment, the proxy answers the client with its own 502/503 and the request
11+
* never reaches DBHub (issue #472).
12+
*
13+
* The backend's idle timeout must be longer than the proxy's so the proxy is
14+
* always the side that closes an idle connection. 65s is the widely used
15+
* value for 60s proxies.
16+
*/
17+
export const HTTP_KEEP_ALIVE_TIMEOUT_MS = 65_000;
18+
19+
/**
20+
* Headers timeout for the HTTP transport, in milliseconds.
21+
*
22+
* Node requires this to be strictly greater than `keepAliveTimeout`;
23+
* otherwise a reused socket can be closed while a request's headers are
24+
* still arriving.
25+
*/
26+
export const HTTP_HEADERS_TIMEOUT_MS = HTTP_KEEP_ALIVE_TIMEOUT_MS + 1_000;
27+
28+
/**
29+
* Apply keep-alive timeouts that survive a 60s proxy idle timeout.
30+
*/
31+
export function configureHttpKeepAlive(server: http.Server): void {
32+
server.keepAliveTimeout = HTTP_KEEP_ALIVE_TIMEOUT_MS;
33+
server.headersTimeout = HTTP_HEADERS_TIMEOUT_MS;
34+
}

0 commit comments

Comments
 (0)