Skip to content

http: add serve() with NodeRequest and NodeResponse - #65292

Draft
mcollina wants to merge 4 commits into
nodejs:mainfrom
mcollina:node-server-fetch
Draft

http: add serve() with NodeRequest and NodeResponse#65292
mcollina wants to merge 4 commits into
nodejs:mainfrom
mcollina:node-server-fetch

Conversation

@mcollina

Copy link
Copy Markdown
Member

Adds http.serve(options, handler), an HTTP server API using the Fetch Request/Response model, built directly on the HTTP parser with a dedicated parser pool.

Handlers receive a NodeRequest (subclass of Request) built without the public constructor: headers list filled straight from the parser, lazy URL parsing, lazy AbortSignal that fires on client disconnect, and socket metadata getters. NodeResponse (subclass of Response) adds a fast construction path for string/Uint8Array/null bodies with no ReadableStream allocation unless one is requested. Both classes remain fully compatible with the fetch ecosystem (instanceof, body mixins, clone, wrapping, formData).

With wrk at c=500, NodeResponse handlers are 20-30% faster than http.createServer for small/medium bodies; plain Response handlers currently trail it by ~35% (cost is in undici's public constructor).

Depends on the undici serverKit surface: nodejs/undici#5690 (interim hand-patch to the bundle included here as a separate commit).

Still to do: docs.

mcollina and others added 4 commits July 14, 2026 17:43
Add a modern HTTP server API that uses the Fetch API's Request and
Response objects, bypassing the legacy IncomingMessage/ServerResponse
stream-based model.

Features:
- serve(options, handler) creates HTTP or HTTPS servers
- Handler receives Request, returns Response (sync or async)
- getRemoteMetadata(request) retrieves connection info
- Automatic chunked transfer encoding when no Content-Length
- Keep-alive connection support
- Graceful shutdown via AbortSignal
- Custom error handling via onError callback

The implementation reuses the battle-tested HTTPParser but creates
Fetch API objects directly from parser output. Requests are serialized
per connection (no HTTP pipelining) - parser is paused after headers
and resumed after response is written.

Example usage:
  const { serve, getRemoteMetadata } = require('http');

  const server = serve({}, async (request) => {
    const body = await request.json();
    return Response.json({ received: body });
  });
  server.listen(3000);
- Enable TCP_NODELAY (noDelay: true) to prevent Nagle's algorithm
  from batching small writes, which was causing 40ms delays
- Batch header writes into a single socket.write() call
- Use cork()/uncork() to batch chunked body writes

These optimizations improve hello-world throughput from ~24 req/sec
to ~13,600 req/sec (567x improvement). The remaining 2.3x gap vs
createServer() is due to Response body streaming overhead.
Add a single frozen serverKit namespace to the bundle exports, exposing
the existing fetch internals (kConstruct, HeadersList, headers guard and
list accessors, request/response state accessors) that the http.serve()
implementation needs to construct Request/Response subclasses without
going through the public constructors. No undici logic is changed; the
request and response modules only gain exports for accessors they
already define.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RGGHNWBpZyJEEN1Pb2msxY
Introduce NodeRequest and NodeResponse, real subclasses of the fetch
Request/Response built through undici's kConstruct path, and use them in
serve(). Incoming requests skip the public Request constructor entirely:
the HeadersList is filled directly from llhttp-validated parser output,
the URL is parsed lazily, and the AbortSignal is only materialized on
access. NodeResponse adds a fast construction path for null, string and
Uint8Array bodies with no WebIDL conversion and no ReadableStream until
one is requested, and writeResponse writes body sources directly instead
of entering the Web Streams reader loop.

Also:

- request.signal now aborts and the request body stream errors when the
  client disconnects before the request completes, following
  http.Server's half-open connection semantics
- socket metadata is exposed as getters on NodeRequest and cached per
  connection; getRemoteMetadata is unchanged
- multiple Set-Cookie response headers are written as separate header
  lines instead of one comma-joined line
- Response.error() is rejected instead of serializing as status 0
- fetch-compatible Response objects from a different undici instance are
  serialized through their public API
- serve() is lazily loaded from lib/http.js; loading the undici bundle
  while http.js is still initializing let undici capture the module
  without maxHeaderSize, breaking fetch's default dispatcher

benchmark/http/serve.js gains a serve-fast variant using NodeResponse:
with wrk at c=500 it roughly doubles throughput against plain Response
handlers, and plain Response handlers gain 8-16% over the previous
implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RGGHNWBpZyJEEN1Pb2msxY
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net
  • @nodejs/performance
  • @nodejs/security-wg

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Aug 14, 2026
@KhafraDev

Copy link
Copy Markdown
Member

I really implore everyone to reconsider. The fetch spec is over a decade old and has many, MANY pitfalls. Request and Response are simple classes with terrible defaults - request method normalization, methods being blocked such as new Request('https://a', { method: 'TRACE' }), no max body size on the body mixin methods and how easy it to DOS your app by using a number of them, and these are just the issues off of the top of my head.

@jasnell

jasnell commented Aug 14, 2026

Copy link
Copy Markdown
Member

@KhafraDev ... literally no one is suggesting that we adopt the fetch spec exactly as it is defined in the WHATWG fetch specification. We are also not going into this blind. We know what the pitfalls are. We know that the current specifications definitions of Request and Response, if interpreted strictly are not great for server environments. We are going into this with eyes open and working forward on a plan. There's literally nothing that says we can't use the fetch APIs and have a good implementation of it and a little bit of trust that we know what we're doing here would be fantastic.

@jasnell

jasnell commented Aug 14, 2026

Copy link
Copy Markdown
Member

@mcollina ... let's keep this experimental / build-flagged until we are able to reconcile with this https://proposal-fetch-server.jasnell.me/ as it moves forward

@mcollina

Copy link
Copy Markdown
Member Author

I'm not planning to land this as-is. It's in drat mode after all.

@KhafraDev

Copy link
Copy Markdown
Member

There's literally nothing that says we can't use the fetch APIs and have a good implementation

"Good" is subjective here, I personally think a good implementation is one that follows the spec. When implementing a web spec, there are downsides and limitations that the project must accept beforehand. "You can't have your cake and eat it too". From 2022, you said that the goal is to be "as close as possible" to the fetch spec.

little bit of trust that we know what we're doing here would be fantastic.

I trust that you personally know the limitations, but many people reading likely don't.

@efekrskl

Copy link
Copy Markdown
Member

This seems to address #63096. Could we add Closes #63096 to the PR description?

@jasnell jasnell added http Issues or PRs related to the http subsystem. https Issues or PRs related to the https subsystem. http2 Issues or PRs related to the http2 subsystem. http3 experimental Issues and PRs related to experimental features. labels Aug 14, 2026
@jasnell

jasnell commented Aug 14, 2026

Copy link
Copy Markdown
Member

@KhafraDev:

... I personally think a good implementation is one that follows the spec

Good is always subjective and specs can be changed/evolved/fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental Issues and PRs related to experimental features. http Issues or PRs related to the http subsystem. http2 Issues or PRs related to the http2 subsystem. http3 https Issues or PRs related to the https subsystem. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants