From e8d87505a5912cd06dec1a9839224d71f35050ac Mon Sep 17 00:00:00 2001 From: Amp Date: Wed, 19 Aug 2026 00:24:07 +0000 Subject: [PATCH] perf(server): skip empty middleware work Avoid route lookups, promise continuations, monitor snapshots, and sorting when the corresponding request pipeline stage has no work. Keep handler counts in sync with registration lifecycle changes. Amp-Thread-ID: https://ampcode.com/threads/T-01a01745-cfcc-7179-a3a1-e5aa33780a0c Co-authored-by: Upd4ting --- src/server.ts | 148 +++++++++++++++++++++-------- src/test/server-fast-paths.test.ts | 123 ++++++++++++++++++++++++ 2 files changed, 229 insertions(+), 42 deletions(-) create mode 100644 src/test/server-fast-paths.test.ts diff --git a/src/server.ts b/src/server.ts index ea7089e..43932c4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,6 +10,9 @@ export interface IdentifiableRouteCallback { priority: HandlerPriority; } +type HandlerMode = "prefix" | "postfix" | "handler" | "monitor" | "websocket"; +type MiddlewareMode = "prefix" | "postfix"; + export interface RequestContext { rawRequest: IncomingMessage; rawResponse: ServerResponse; @@ -39,7 +42,7 @@ class RouteLevel { catchAllRoutes: CatchAllRoute[] = []; } -const roots: Record> = { +const roots: Record> = { handler: {}, prefix: {}, postfix: {}, @@ -47,6 +50,14 @@ const roots: Record> = { websocket: {}, }; +const handlerCounts: Record = { + handler: 0, + prefix: 0, + postfix: 0, + monitor: 0, + websocket: 0, +}; + interface HandlerResult { handler: RouteCallback; parameters: Record; @@ -186,42 +197,43 @@ function getHandler( return multi ? result : undefined; } -function removeHandler( - id: string, - source: Record, -): boolean { +function removeHandler(id: string, source: Record): number { for (const level of Object.values(source)) { const handlerLength = level.handlers.length; level.handlers = level.handlers.filter((handler) => handler.id !== id); + const removedCount = handlerLength - level.handlers.length; - if (handlerLength !== level.handlers.length) { - return true; + if (removedCount > 0) { + return removedCount; } - if (removeHandler(id, level.staticRoutes)) { - return true; + const staticRemovedCount = removeHandler(id, level.staticRoutes); + if (staticRemovedCount > 0) { + return staticRemovedCount; } - if ( - removeHandler( - id, - Object.keys(level.dynamicRoutes).reduce( - (acc, key) => ({ ...acc, [key]: level.dynamicRoutes[key].sub }), - {} as Record, - ), - ) - ) { - return true; + const dynamicRemovedCount = removeHandler( + id, + Object.fromEntries( + Object.entries(level.dynamicRoutes).map(([key, route]) => [ + key, + route.sub, + ]), + ), + ); + if (dynamicRemovedCount > 0) { + return dynamicRemovedCount; } for (const catchAll of level.catchAllRoutes) { - if (removeHandler(id, { _: catchAll.level })) { - return true; + const catchAllRemovedCount = removeHandler(id, { _: catchAll.level }); + if (catchAllRemovedCount > 0) { + return catchAllRemovedCount; } } } - return false; + return 0; } const special = { @@ -240,7 +252,7 @@ const special = { }; export function registerHandler( id: string, - mode: "prefix" | "postfix" | "handler" | "monitor" | "websocket", + mode: HandlerMode, method: string | undefined, location: string, handler: RouteCallback, @@ -332,11 +344,14 @@ export function registerHandler( } } level.handlers.push({ id, callback: handler, priority }); + handlerCounts[mode] += 1; } export function unregisterHandler(id: string) { - for (const source of Object.values(roots)) { - if (removeHandler(id, source)) { + for (const [mode, source] of Object.entries(roots)) { + const removedCount = removeHandler(id, source); + if (removedCount > 0) { + handlerCounts[mode as HandlerMode] -= removedCount; return; } } @@ -405,7 +420,9 @@ async function executePriorityHandlers( handlers: HandlerResult[], requestContext: RequestContext, ) { - handlers.sort((a, b) => a.priority - b.priority); + if (handlers.length > 1) { + handlers.sort((a, b) => a.priority - b.priority); + } for (const { handler, parameters } of handlers) { requestContext.routeParameters = parameters; const result = await handler(requestContext); @@ -416,19 +433,29 @@ async function executePriorityHandlers( return undefined; } -async function executeMonitors( +function executeMiddleware( + mode: MiddlewareMode, method: string, path: string[], requestContext: RequestContext, ) { - const monitors = getMultiHandlers(method, path, roots.monitor); - const monitorContext: RequestContext = { - ...requestContext, - routeParameters: {}, - response: cloneResponse(requestContext.response), - }; + if (handlerCounts[mode] === 0) { + return undefined; + } + const handlers = getMultiHandlers(method, path, roots[mode]); + if (handlers.length === 0) { + return undefined; + } + return executePriorityHandlers(handlers, requestContext); +} - monitors.sort((a, b) => a.priority - b.priority); +async function runMonitors( + monitors: HandlerResult[], + monitorContext: RequestContext, +) { + if (monitors.length > 1) { + monitors.sort((a, b) => a.priority - b.priority); + } for (const { handler, parameters } of monitors) { monitorContext.routeParameters = parameters; try { @@ -439,6 +466,26 @@ async function executeMonitors( } } +function executeMonitors( + method: string, + path: string[], + requestContext: RequestContext, +) { + if (handlerCounts.monitor === 0) { + return; + } + const monitors = getMultiHandlers(method, path, roots.monitor); + if (monitors.length === 0) { + return; + } + const monitorContext: RequestContext = { + ...requestContext, + routeParameters: {}, + response: cloneResponse(requestContext.response), + }; + return runMonitors(monitors, monitorContext); +} + export async function requestListener( req: IncomingMessage, res: ServerResponse, @@ -469,10 +516,13 @@ export async function requestListener( if (!handler && method !== "options") { // Fall through to finally block to execute monitors. } else { - const prefixResult = await executePriorityHandlers( - getMultiHandlers(method, path, roots.prefix), + const prefixExecution = executeMiddleware( + "prefix", + method, + path, requestContext, ); + const prefixResult = prefixExecution ? await prefixExecution : undefined; if (prefixResult) { requestContext.response = HTTPResult.withHeaders( prefixResult, @@ -488,10 +538,15 @@ export async function requestListener( setHandlerResponse(requestContext, result); } - const postfixResult = await executePriorityHandlers( - getMultiHandlers(method, path, roots.postfix), + const postfixExecution = executeMiddleware( + "postfix", + method, + path, requestContext, ); + const postfixResult = postfixExecution + ? await postfixExecution + : undefined; if (postfixResult) { requestContext.response = HTTPResult.withHeaders( postfixResult, @@ -510,7 +565,10 @@ export async function requestListener( ); } finally { requestContext.error = requestError; - await executeMonitors(method, path, requestContext); + const monitorExecution = executeMonitors(method, path, requestContext); + if (monitorExecution) { + await monitorExecution; + } handleResult(isHeadRequest, requestContext.response, res); } } @@ -554,10 +612,13 @@ export async function upgradeListener( mustDestroySocket = true; // Fall through to finally block to execute monitors. } else { - const prefixResult = await executePriorityHandlers( - getMultiHandlers(method, path, roots.prefix), + const prefixExecution = executeMiddleware( + "prefix", + method, + path, requestContext, ); + const prefixResult = prefixExecution ? await prefixExecution : undefined; if (prefixResult) { requestContext.response = HTTPResult.withHeaders( prefixResult, @@ -587,7 +648,10 @@ export async function upgradeListener( } } finally { requestContext.error = requestError; - await executeMonitors(method, path, requestContext); + const monitorExecution = executeMonitors(method, path, requestContext); + if (monitorExecution) { + await monitorExecution; + } if (mustSendResponse) { handleResult(false, requestContext.response, res); } diff --git a/src/test/server-fast-paths.test.ts b/src/test/server-fast-paths.test.ts new file mode 100644 index 0000000..4e3fcb1 --- /dev/null +++ b/src/test/server-fast-paths.test.ts @@ -0,0 +1,123 @@ +import assert from "node:assert"; +import { createServer, type Server } from "node:http"; +import { HandlerPriority } from "@antelopejs/interface-api"; +import { registerHandler, requestListener, unregisterHandler } from "../server"; + +const TEST_HOST = "127.0.0.1"; +const TEST_PATH = "/server-fast-path-lifecycle"; +const HANDLER_ID = "server-fast-path-handler"; +const PREFIX_HIGH_ID = "server-fast-path-prefix-high"; +const PREFIX_LOW_ID = "server-fast-path-prefix-low"; +const POSTFIX_ID = "server-fast-path-postfix"; +const MONITOR_ID = "server-fast-path-monitor"; +const ROUTE_IDS = [ + HANDLER_ID, + PREFIX_HIGH_ID, + PREFIX_LOW_ID, + POSTFIX_ID, + MONITOR_ID, +]; + +interface TestResponse { + status: number; + body: string; +} + +function listen(server: Server): Promise { + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, TEST_HOST, () => { + const address = server.address(); + if (!address || typeof address === "string") { + reject(new Error("Test server did not bind a TCP port")); + return; + } + resolve(address.port); + }); + }); +} + +function close(server: Server): Promise { + return new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); +} + +async function request(port: number): Promise { + const response = await fetch(`http://${TEST_HOST}:${port}${TEST_PATH}`); + return { status: response.status, body: await response.text() }; +} + +describe("Server middleware fast paths", () => { + const events: string[] = []; + const server = createServer((incoming, response) => { + void requestListener(incoming, response, "http"); + }); + let port: number; + + before(async () => { + port = await listen(server); + registerHandler(HANDLER_ID, "handler", "get", TEST_PATH, () => { + events.push("handler"); + return "ok"; + }); + }); + + after(async () => { + ROUTE_IDS.forEach(unregisterHandler); + await close(server); + }); + + beforeEach(() => { + events.length = 0; + }); + + it("preserves lifecycle behavior as middleware is added and removed", async () => { + assert.deepEqual(await request(port), { status: 200, body: "ok" }); + assert.deepEqual(events, ["handler"]); + + registerHandler( + PREFIX_LOW_ID, + "prefix", + "get", + TEST_PATH, + () => { + events.push("prefix-low"); + }, + HandlerPriority.LOW, + ); + registerHandler( + PREFIX_HIGH_ID, + "prefix", + "get", + TEST_PATH, + () => { + events.push("prefix-high"); + }, + HandlerPriority.HIGH, + ); + registerHandler(POSTFIX_ID, "postfix", "get", TEST_PATH, () => { + events.push("postfix"); + }); + registerHandler(MONITOR_ID, "monitor", "get", TEST_PATH, () => { + events.push("monitor"); + }); + + events.length = 0; + assert.deepEqual(await request(port), { status: 200, body: "ok" }); + assert.deepEqual(events, [ + "prefix-high", + "prefix-low", + "handler", + "postfix", + "monitor", + ]); + + [PREFIX_HIGH_ID, PREFIX_LOW_ID, POSTFIX_ID, MONITOR_ID].forEach( + unregisterHandler, + ); + events.length = 0; + assert.deepEqual(await request(port), { status: 200, body: "ok" }); + assert.deepEqual(events, ["handler"]); + }); +});