From 85dc9b932b4585ce08ea0c3cc8ff7cb3d7d70844 Mon Sep 17 00:00:00 2001 From: Amp Date: Wed, 19 Aug 2026 12:20:32 +0000 Subject: [PATCH 1/3] perf(http): reuse response results through request lifecycle Amp-Thread-ID: https://ampcode.com/threads/T-01a019cf-7de3-7163-8c4d-39257b622dbe Co-authored-by: Upd4ting --- src/server.ts | 92 ++++++----- src/test/response-lifecycle.test.ts | 244 ++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+), 37 deletions(-) create mode 100644 src/test/response-lifecycle.test.ts diff --git a/src/server.ts b/src/server.ts index 3ae70ea..4ed87ce 100644 --- a/src/server.ts +++ b/src/server.ts @@ -126,6 +126,10 @@ function hasParameter(parameters: Record, name: string) { return Object.getOwnPropertyDescriptor(parameters, name) !== undefined; } +interface HeaderPeekable { + peekHeaders(): Readonly> | undefined; +} + function findHandlers( path: string[], depth: number, @@ -547,23 +551,51 @@ function extractError(error: unknown) { return error; } -function setHandlerResponse(requestContext: RequestContext, result: unknown) { - if (requestContext.response.isStream()) { +function getExistingHeaders(response: HTTPResult) { + const peekHeaders = (response as Partial).peekHeaders; + if (typeof peekHeaders === "function") { + return peekHeaders.call(response); + } + return response.getHeaders(); +} + +function copyHeaders(source: HTTPResult, target: HTTPResult) { + if (source === target) { return; } - if (result) { - requestContext.response = HTTPResult.withHeaders( - result, - requestContext.response.getHeaders(), - 200, - ); + for (const [name, value] of Object.entries( + getExistingHeaders(source) ?? {}, + )) { + target.addHeader(name, value); + } +} + +function setResponse( + requestContext: RequestContext, + result: unknown, + status: number, + mustReplace = false, +) { + const previousResponse = requestContext.response; + if (result instanceof HTTPResult) { + copyHeaders(previousResponse, result); + requestContext.response = result; return; } - requestContext.response = HTTPResult.withHeaders( - "", - requestContext.response.getHeaders(), - 200, - ); + if (!mustReplace && !previousResponse.isStream()) { + previousResponse.setBody(result); + previousResponse.setStatus(status); + return; + } + const response = new HTTPResult(status, result); + copyHeaders(previousResponse, response); + requestContext.response = response; +} + +function setHandlerResponse(requestContext: RequestContext, result: unknown) { + if (!requestContext.response.isStream()) { + setResponse(requestContext, result || "", 200); + } } function cloneResponse(response: HTTPResult) { @@ -572,9 +604,7 @@ function cloneResponse(response: HTTPResult) { response.getBody(), response.getContentType(), ); - for (const [name, value] of Object.entries(response.getHeaders())) { - snapshot.addHeader(name, value); - } + copyHeaders(response, snapshot); return snapshot; } @@ -732,11 +762,7 @@ function setMiddlewareResponse( result: unknown, ): void { if (result) { - requestContext.response = HTTPResult.withHeaders( - result, - requestContext.response.getHeaders(), - 200, - ); + setResponse(requestContext, result, 200); } } @@ -775,6 +801,10 @@ function executeRequest( handler = getHandler("get", path, roots.handler, false, exactPath); } const selectedHandler = Array.isArray(handler) ? undefined : handler; + if (!selectedHandler) { + requestContext.response.setBody("Not Found"); + requestContext.response.setStatus(404); + } if (!selectedHandler && method !== "options") { return; } @@ -806,11 +836,7 @@ function completeRequest( requestContext: RequestContext, ): Awaitable { if (didFail) { - requestContext.response = HTTPResult.withHeaders( - extractError(error), - requestContext.response.getHeaders(), - 500, - ); + setResponse(requestContext, extractError(error), 500, true); } requestContext.error = error; const monitorExecution = executeMonitors(method, path, requestContext); @@ -837,7 +863,7 @@ function processRequest( rawResponse: res, url, routeParameters: {}, - response: new HTTPResult(404, "Not Found"), + response: new HTTPResult(), }; const path = url.pathname.split("/").filter((part) => part); const method = req.method?.toLowerCase() || "get"; @@ -927,11 +953,7 @@ export async function upgradeListener( ); const prefixResult = prefixExecution ? await prefixExecution : undefined; if (prefixResult) { - requestContext.response = HTTPResult.withHeaders( - prefixResult, - requestContext.response.getHeaders(), - 200, - ); + setResponse(requestContext, prefixResult, 200); mustSendResponse = true; mustDestroySocket = true; // Fall through to finally block to execute monitors. @@ -945,11 +967,7 @@ export async function upgradeListener( requestError = error; mustDestroySocket = true; if (!hasUpgradedConnection) { - requestContext.response = HTTPResult.withHeaders( - extractError(error), - requestContext.response.getHeaders(), - 500, - ); + setResponse(requestContext, extractError(error), 500, true); mustSendResponse = true; } } finally { diff --git a/src/test/response-lifecycle.test.ts b/src/test/response-lifecycle.test.ts new file mode 100644 index 0000000..d37655b --- /dev/null +++ b/src/test/response-lifecycle.test.ts @@ -0,0 +1,244 @@ +import assert from "node:assert"; +import * as http from "node:http"; +import { HandlerPriority, HTTPResult } from "@antelopejs/interface-api"; +import { + type RequestContext, + registerHandler, + requestListener, + unregisterHandler, +} from "../server"; + +const HOST = "127.0.0.1"; +const ROUTE_ROOT = "/response-lifecycle"; +const registeredHandlers: string[] = []; + +interface CapturedResponse { + status: number; + headers: http.IncomingHttpHeaders; + body: string; +} + +function register( + mode: "prefix" | "postfix" | "handler" | "monitor", + path: string, + callback: (context: RequestContext) => unknown, +) { + const id = `${mode}-${path}-${registeredHandlers.length}`; + registeredHandlers.push(id); + registerHandler(id, mode, "get", path, callback, HandlerPriority.NORMAL); +} + +function listen(server: http.Server): Promise { + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, HOST, () => { + const address = server.address(); + assert.ok(address && typeof address === "object"); + resolve(address.port); + }); + }); +} + +function close(server: http.Server): Promise { + return new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); +} + +function request(port: number, path: string, method = "GET") { + return new Promise((resolve, reject) => { + const req = http.request({ host: HOST, port, path, method }, (res) => { + const chunks: Buffer[] = []; + res.on("data", (chunk: Buffer) => chunks.push(chunk)); + res.on("end", () => + resolve({ + status: res.statusCode ?? 0, + headers: res.headers, + body: Buffer.concat(chunks).toString(), + }), + ); + }); + req.once("error", reject); + req.end(); + }); +} + +describe("HTTP response lifecycle", () => { + let server: http.Server; + let port: number; + + before(async () => { + server = http.createServer( + (req, res) => void requestListener(req, res, "http"), + ); + port = await listen(server); + }); + + after(async () => { + for (const id of registeredHandlers) { + unregisterHandler(id); + } + await close(server); + }); + + it("reuses the matched route response without observing headers", async () => { + const path = `${ROUTE_ROOT}/common`; + let contextResponse: HTTPResult | undefined; + let initialStatus = 0; + let initialBody: unknown; + let headerReads = 0; + const originalGetHeaders = HTTPResult.prototype.getHeaders; + register("handler", path, (context) => { + contextResponse = context.response; + initialStatus = context.response.getStatus(); + initialBody = context.response.getBody(); + return { ok: true }; + }); + HTTPResult.prototype.getHeaders = function getHeaders() { + headerReads += 1; + return originalGetHeaders.call(this); + }; + + try { + const response = await request(port, path); + assert.equal(response.status, 200); + assert.equal(response.body, '{"ok":true}'); + assert.equal(initialStatus, 200); + assert.equal(initialBody, ""); + assert.equal(contextResponse?.getStatus(), 200); + assert.equal(contextResponse?.getBody(), '{"ok":true}'); + assert.equal(headerReads, 0); + } finally { + HTTPResult.prototype.getHeaders = originalGetHeaders; + } + }); + + it("uses peekHeaders when the installed interface exposes it", async () => { + const path = `${ROUTE_ROOT}/peek-headers`; + let headerReads = 0; + const originalGetHeaders = HTTPResult.prototype.getHeaders; + const hasPeekHeaders = "peekHeaders" in HTTPResult.prototype; + register("handler", path, () => new HTTPResult(203, "result")); + HTTPResult.prototype.getHeaders = function getHeaders() { + headerReads += 1; + return originalGetHeaders.call(this); + }; + + try { + const response = await request(port, path); + assert.equal(response.status, 203); + assert.equal(response.body, "result"); + assert.equal(headerReads, hasPeekHeaders ? 0 : 1); + } finally { + HTTPResult.prototype.getHeaders = originalGetHeaders; + } + }); + + it("preserves headers across prefix, handler, and postfix results", async () => { + const path = `${ROUTE_ROOT}/headers`; + register("prefix", path, ({ response }) => + response.addHeader("X-Prefix", "1"), + ); + register("handler", path, ({ response }) => { + response.addHeader("X-Handler", "2"); + return "handler"; + }); + register("postfix", path, () => { + const response = new HTTPResult(202, "postfix"); + response.addHeader("X-Postfix", "3"); + return response; + }); + + const response = await request(port, path); + assert.equal(response.status, 202); + assert.equal(response.body, "postfix"); + assert.equal(response.headers["x-prefix"], "1"); + assert.equal(response.headers["x-handler"], "2"); + assert.equal(response.headers["x-postfix"], "3"); + }); + + it("keeps the status and body of a returned HTTPResult", async () => { + const path = `${ROUTE_ROOT}/result`; + register("handler", path, () => new HTTPResult(201, { created: true })); + + const response = await request(port, path); + assert.equal(response.status, 201); + assert.equal(response.body, '{"created":true}'); + assert.equal(response.headers["content-type"], "application/json"); + }); + + it("creates the Not Found response only for a route miss", async () => { + const path = `${ROUTE_ROOT}/missing`; + let monitorStatus = 0; + let monitorBody: unknown; + register("monitor", path, ({ response }) => { + monitorStatus = response.getStatus(); + monitorBody = response.getBody(); + }); + + const response = await request(port, path); + assert.equal(response.status, 404); + assert.equal(response.body, "Not Found"); + assert.equal(monitorStatus, 404); + assert.equal(monitorBody, "Not Found"); + }); + + it("isolates monitor response snapshots", async () => { + const path = `${ROUTE_ROOT}/monitor`; + let snapshot: HTTPResult | undefined; + register("handler", path, ({ response }) => { + response.addHeader("X-Handler", "kept"); + return "body"; + }); + register("monitor", path, ({ response }) => { + snapshot = response; + response.setBody("changed"); + response.addHeader("X-Monitor", "isolated"); + }); + + const response = await request(port, path); + assert.equal(response.body, "body"); + assert.equal(response.headers["x-handler"], "kept"); + assert.equal(response.headers["x-monitor"], undefined); + assert.equal(snapshot?.getBody(), "changed"); + }); + + it("preserves headers while converting thrown errors", async () => { + const path = `${ROUTE_ROOT}/error`; + register("prefix", path, ({ response }) => + response.addHeader("X-Prefix", "kept"), + ); + register("handler", path, () => { + throw new Error("failure"); + }); + + const response = await request(port, path); + assert.equal(response.status, 500); + assert.equal(response.body, "failure"); + assert.equal(response.headers["x-prefix"], "kept"); + }); + + it("sends HEAD through HTTPResult without a response body", async () => { + const path = `${ROUTE_ROOT}/head`; + register("handler", path, () => "not sent"); + + const response = await request(port, path, "HEAD"); + assert.equal(response.status, 200); + assert.equal(response.body, ""); + assert.equal(response.headers["content-type"], "text/plain"); + }); + + it("keeps stream status, headers, and body", async () => { + const path = `${ROUTE_ROOT}/stream`; + register("handler", path, ({ response }) => { + response.addHeader("X-Stream", "kept"); + response.getWriteStream("text/event-stream", 206).end("event"); + }); + + const response = await request(port, path); + assert.equal(response.status, 206); + assert.equal(response.body, "event"); + assert.equal(response.headers["content-type"], "text/event-stream"); + assert.equal(response.headers["x-stream"], "kept"); + }); +}); From 42ea1c2b4617ce8d882d477c0a5a285a234cbc10 Mon Sep 17 00:00:00 2001 From: Amp Date: Wed, 19 Aug 2026 16:37:49 +0000 Subject: [PATCH 2/3] build(api): require interface-api 0.0.11 Amp-Thread-ID: https://ampcode.com/threads/T-01a019cf-7de3-7163-8c4d-39257b622dbe Co-authored-by: Upd4ting --- package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- src/server.ts | 16 +--------------- src/test/response-lifecycle.test.ts | 5 ++--- 4 files changed, 12 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 1902221..1d10693 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ } }, "dependencies": { - "@antelopejs/interface-api": ">=0.0.10 <1.0.0", + "@antelopejs/interface-api": ">=0.0.11 <1.0.0", "@antelopejs/interface-api-util": "^0.1.1", "@antelopejs/interface-core": "^0.0.6", "reflect-metadata": "^0.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ab9d98..1b0f70f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@antelopejs/interface-api': - specifier: '>=0.0.10 <1.0.0' - version: 0.0.10(@antelopejs/interface-core@0.0.6) + specifier: '>=0.0.11 <1.0.0' + version: 0.0.11(@antelopejs/interface-core@0.0.6) '@antelopejs/interface-api-util': specifier: ^0.1.1 - version: 0.1.1(@antelopejs/interface-api@0.0.10(@antelopejs/interface-core@0.0.6))(@antelopejs/interface-core@0.0.6) + version: 0.1.1(@antelopejs/interface-api@0.0.11(@antelopejs/interface-core@0.0.6))(@antelopejs/interface-core@0.0.6) '@antelopejs/interface-core': specifier: ^0.0.6 version: 0.0.6 @@ -85,8 +85,8 @@ packages: '@antelopejs/interface-api': '>=0.0.3 <1.0.0' '@antelopejs/interface-core': '>=0.0.3 <1.0.0' - '@antelopejs/interface-api@0.0.10': - resolution: {integrity: sha512-nToojyDDp/175hR3ryuxFxlU9zyKcSr2/V+ftEySlpVHO469skNkG9s9U8OPmFMH9wj/t2iul9gNB6/NvNo7KQ==} + '@antelopejs/interface-api@0.0.11': + resolution: {integrity: sha512-egWmk2m+aVSN022xmxBCq5SbPgkN1VVJosXvLnNX2nBB+YAPKmPwsJGg53jota7TE7dK1gdL42HmiwWRHhh1eA==} peerDependencies: '@antelopejs/interface-core': '>=0.0.3 <1.0.0' @@ -1231,9 +1231,9 @@ packages: snapshots: - '@antelopejs/interface-api-util@0.1.1(@antelopejs/interface-api@0.0.10(@antelopejs/interface-core@0.0.6))(@antelopejs/interface-core@0.0.6)': + '@antelopejs/interface-api-util@0.1.1(@antelopejs/interface-api@0.0.11(@antelopejs/interface-core@0.0.6))(@antelopejs/interface-core@0.0.6)': dependencies: - '@antelopejs/interface-api': 0.0.10(@antelopejs/interface-core@0.0.6) + '@antelopejs/interface-api': 0.0.11(@antelopejs/interface-core@0.0.6) '@antelopejs/interface-core': 0.0.6 '@antelopejs/interface-api-util@0.1.1(@antelopejs/interface-api@0.0.8(@antelopejs/interface-core@0.0.6))(@antelopejs/interface-core@0.0.6)': @@ -1241,7 +1241,7 @@ snapshots: '@antelopejs/interface-api': 0.0.8(@antelopejs/interface-core@0.0.6) '@antelopejs/interface-core': 0.0.6 - '@antelopejs/interface-api@0.0.10(@antelopejs/interface-core@0.0.6)': + '@antelopejs/interface-api@0.0.11(@antelopejs/interface-core@0.0.6)': dependencies: '@antelopejs/interface-core': 0.0.6 diff --git a/src/server.ts b/src/server.ts index 4ed87ce..75893ba 100644 --- a/src/server.ts +++ b/src/server.ts @@ -126,10 +126,6 @@ function hasParameter(parameters: Record, name: string) { return Object.getOwnPropertyDescriptor(parameters, name) !== undefined; } -interface HeaderPeekable { - peekHeaders(): Readonly> | undefined; -} - function findHandlers( path: string[], depth: number, @@ -551,21 +547,11 @@ function extractError(error: unknown) { return error; } -function getExistingHeaders(response: HTTPResult) { - const peekHeaders = (response as Partial).peekHeaders; - if (typeof peekHeaders === "function") { - return peekHeaders.call(response); - } - return response.getHeaders(); -} - function copyHeaders(source: HTTPResult, target: HTTPResult) { if (source === target) { return; } - for (const [name, value] of Object.entries( - getExistingHeaders(source) ?? {}, - )) { + for (const [name, value] of Object.entries(source.peekHeaders() ?? {})) { target.addHeader(name, value); } } diff --git a/src/test/response-lifecycle.test.ts b/src/test/response-lifecycle.test.ts index d37655b..8d3c6f0 100644 --- a/src/test/response-lifecycle.test.ts +++ b/src/test/response-lifecycle.test.ts @@ -113,11 +113,10 @@ describe("HTTP response lifecycle", () => { } }); - it("uses peekHeaders when the installed interface exposes it", async () => { + it("uses the published non-allocating header lookup", async () => { const path = `${ROUTE_ROOT}/peek-headers`; let headerReads = 0; const originalGetHeaders = HTTPResult.prototype.getHeaders; - const hasPeekHeaders = "peekHeaders" in HTTPResult.prototype; register("handler", path, () => new HTTPResult(203, "result")); HTTPResult.prototype.getHeaders = function getHeaders() { headerReads += 1; @@ -128,7 +127,7 @@ describe("HTTP response lifecycle", () => { const response = await request(port, path); assert.equal(response.status, 203); assert.equal(response.body, "result"); - assert.equal(headerReads, hasPeekHeaders ? 0 : 1); + assert.equal(headerReads, 0); } finally { HTTPResult.prototype.getHeaders = originalGetHeaders; } From bbd94e45374d0ddd629b6fc251d583b6c9436b90 Mon Sep 17 00:00:00 2001 From: Amp Date: Wed, 19 Aug 2026 21:26:47 +0000 Subject: [PATCH 3/3] perf(http): specialize synchronous response updates Co-authored-by: Upd4ting --- src/server.ts | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/server.ts b/src/server.ts index 75893ba..dd2a4b4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -556,32 +556,28 @@ function copyHeaders(source: HTTPResult, target: HTTPResult) { } } -function setResponse( +function replaceResponse( requestContext: RequestContext, result: unknown, status: number, - mustReplace = false, ) { const previousResponse = requestContext.response; - if (result instanceof HTTPResult) { - copyHeaders(previousResponse, result); - requestContext.response = result; - return; - } - if (!mustReplace && !previousResponse.isStream()) { - previousResponse.setBody(result); - previousResponse.setStatus(status); - return; - } - const response = new HTTPResult(status, result); + const response = + result instanceof HTTPResult ? result : new HTTPResult(status, result); copyHeaders(previousResponse, response); requestContext.response = response; } function setHandlerResponse(requestContext: RequestContext, result: unknown) { - if (!requestContext.response.isStream()) { - setResponse(requestContext, result || "", 200); + if (requestContext.response.isStream()) { + return; } + if (result instanceof HTTPResult) { + replaceResponse(requestContext, result, 200); + return; + } + requestContext.response.setBody(result || ""); + requestContext.response.setStatus(200); } function cloneResponse(response: HTTPResult) { @@ -747,9 +743,15 @@ function setMiddlewareResponse( requestContext: RequestContext, result: unknown, ): void { - if (result) { - setResponse(requestContext, result, 200); + if (!result) { + return; + } + if (result instanceof HTTPResult || requestContext.response.isStream()) { + replaceResponse(requestContext, result, 200); + return; } + requestContext.response.setBody(result); + requestContext.response.setStatus(200); } function executePostfix( @@ -822,7 +824,7 @@ function completeRequest( requestContext: RequestContext, ): Awaitable { if (didFail) { - setResponse(requestContext, extractError(error), 500, true); + replaceResponse(requestContext, extractError(error), 500); } requestContext.error = error; const monitorExecution = executeMonitors(method, path, requestContext); @@ -939,7 +941,7 @@ export async function upgradeListener( ); const prefixResult = prefixExecution ? await prefixExecution : undefined; if (prefixResult) { - setResponse(requestContext, prefixResult, 200); + setMiddlewareResponse(requestContext, prefixResult); mustSendResponse = true; mustDestroySocket = true; // Fall through to finally block to execute monitors. @@ -953,7 +955,7 @@ export async function upgradeListener( requestError = error; mustDestroySocket = true; if (!hasUpgradedConnection) { - setResponse(requestContext, extractError(error), 500, true); + replaceResponse(requestContext, extractError(error), 500); mustSendResponse = true; } } finally {