| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
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.
|
Review requested:
|
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
|
@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. |
Sorry, something went wrong.
|
@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 |
Sorry, something went wrong.
|
I'm not planning to land this as-is. It's in drat mode after all. |
Sorry, something went wrong.
"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.
I trust that you personally know the limitations, but many people reading likely don't. |
Sorry, something went wrong.
Sorry, something went wrong.
Good is always subjective and specs can be changed/evolved/fixed. |
Sorry, something went wrong.
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. Refs: nodejs/undici#5690
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.
| Back | FazBrowse Home | New Git URL |
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.