Repository navigation
Share one Request handler wrapper across Deno.serve, Bun.serve and Cloudflare #25577
Description
Deno.serve, Bun.serve and Cloudflare each wrap a web-standard (Request) => Response handler. Each has its own copy of the same steps:
- isolation scope and request data
- client IP address
- request body capture
continueTraceand thehttp.serverspan- span name rules for span streaming
- response status and headers on the span
- end the span when a streamed response finishes
- capture errors that the handler throws
- request sessions (Deno only)
The files are packages/deno/src/wrap-deno-request-handler.ts,packages/bun/src/integrations/bunserver.ts andpackages/cloudflare/src/wrapRequestHandlerWithInit.ts, about 900 lines together.
The copies drift. A fix or feature in one runtime does not reach the others. Examples today:
- Bun.serve returns early for
OPTIONSandHEAD, with no error capture. Cloudflare captures errors for them. Deno records sessions for them. - Deno and Bun skip body capture for
GET. Cloudflare skipsGET,HEADandOPTIONS. - Deno and Bun each have a
utils/streaming.tswith a differentmonitorStream. Bun's reads only when the client pulls and captures errors from the stream source. Deno's reads ahead of the client, with no backpressure, and does not capture stream errors. Cloudflare has a third version inline. - Deno.serve has sessions and the full option set (feat(deno): Add incoming request options to
denoServeIntegration#25493, feat(deno): Record request sessions forDeno.serve#25496). Bun.serve and Cloudflare do not.
The shared ServerRequestOptions type (#25538) keeps option names in sync, but not behavior.
Proposed solution
Add a wrapper to @sentry/server-utils. That package is internal, so the wrapper can change as runtimes move onto it. Every runtime SDK already depends on it.
export function wrapServerRequestHandler(
config: {
request: Request;
options: ServerRequestOptions<Request, Response>;
origin: string; // e.g. 'auto.http.deno.serve'
getClientAddress?: () => { address: string; port?: number } | undefined;
getRouteAttributes?: () => SpanAttributes | undefined; // Bun route templates
isStreamingResponse?: (response: Response) => boolean | undefined;
onFinish?: () => void; // Cloudflare flush via waitUntil
},
handler: () => Response | Promise<Response>,
): Promise<Response>;
The wrapper owns every shared step in the list above, and it reads options for the shared rules. Each runtime keeps only what is unique to it:
- Deno: reads the address from
Deno.ServeHandlerInfo, and finds the handler in theDeno.servecall forms. - Bun: patches
fetch,routesandserver.reload. Gives route templates and params, and callsserver.requestIP(). - Cloudflare: client init and caching,
cfcontext, cloud resource context, theisStreamingResponseoption, flush throughwaitUntil. It passessessions: false.
One behavior for all three, taken from core's node:http server code (packages/core/src/integrations/http/server-subscription.ts), so web and node:http match:
OPTIONSandHEAD: no span. Still isolate the request, attach request data, record a session and capture errors.- Static asset filter:
GETonly. - Body capture: skip
GET,HEADandOPTIONS, then applyignoreRequestBody. - Streaming: keep Bun's
monitorStream(no read-ahead, captures source errors) and delete the other two.
Plan for the PR: move one runtime at a time, in separate commits, Deno first because it has the full option set. The existing tests of each runtime must pass with no change, except tests that assert one of the behavior differences above. List those changes in the PR description, because users can see them.
Activity
linear-code commentedon Oct 20, 2026
JS-3975
ref(deno): Use a shared request handler wrapper for `Deno.serve`
ref(bun): Use the shared request handler wrapper for `Bun.serve`
ref(cloudflare): Use the shared request handler wrapper
ref(cloudflare): Use the shared request handler wrapper
ref(deno): Use a shared request handler wrapper for `Deno.serve`
ref(bun): Use the shared request handler wrapper for `Bun.serve`
ref(cloudflare): Use the shared request handler wrapper
ref(deno): Use a shared request handler wrapper for `Deno.serve`
ref(bun): Use the shared request handler wrapper for `Bun.serve`
ref(cloudflare): Use the shared request handler wrapper
ref(cloudflare): Use the shared request handler wrapper