Introduction
The Fetch Standard defined Request, Response, Headers, and fetch() for browser HTTP clients. Server-side runtimes adopted these types but diverged on everything around them.
This specification defines a server-side API that:
-
Introduces a
ServerContextfor connection metadata, server capabilities, and lifecycle — the things that belong to the processing environment, not the HTTP message. -
Unifies HTTP/1.1, HTTP/2, and HTTP/3 behind one programming model.
-
Handles both request/response exchanges and tunnel protocols (extended CONNECT).
-
Defines primitives for the Capsule Protocol and HTTP Datagrams.
Goals
-
One server-side programming model for all HTTP versions: A handler should not need to know whether a request arrived over HTTP/1.1, HTTP/2, or HTTP/3. Protocol-version-specific behavior is the implementation’s concern, not the application’s.
-
Standard Fetch types without extension: The
Requesta handler receives is aRequest. TheResponsea handler returns is aResponse. No duck-typing, no structural compatibility concerns, no server-specific subtypes that almost-but-not-quite match the standard types. -
Clean separation of message and environment: The HTTP message (
Request) is separate from the server processing environment (ServerContext). Connection metadata, lifecycle management, and server capabilities are properties of the context, not the request. -
Incremental adoption: A handler that ignores the context and uses only
RequestandResponseworks unchanged. Server-specific capabilities are available when needed but never required. -
Portability across runtimes: The same handler code should run on any conforming runtime without per-runtime adapters.
-
Extensibility for future protocols: Extended CONNECT is designed to carry new protocols. The handler model accommodates new
:protocolvalues without API changes and allows for entirely new handlers to be defined.
Non-Goals
-
Routing: This specification does not define a router, URL pattern matching, or request dispatch. Routing is an application or framework concern.
-
Middleware: This specification does not define a middleware pipeline, plugin system, or request/response transformation chain.
-
Response helpers: This specification does not define convenience methods for building responses (no
ctx.json(),ctx.html(), etc.). Handlers return a standardResponse. -
Replacing existing APIs: This does not replace
node:http,node:http2,Deno.serve(),Bun.serve(), or any existing API. Existing APIs continue to work. -
Browser implementation: This specification targets server-side runtimes.
Relationship to Existing Specifications
-
Fetch Standard: This specification depends on the
Request,Response,Headers, and body definitions from Fetch. It assumes thatRequestandResponsesupport trailers (Promise<Headers>.trailersproperty,trailersconstructor option) and thatfetch()supports anonInformationcallback for receiving informational responses on the client side. -
Streams Standard:
ReadableStreamandWritableStreamare used for bodies, tunnel data, capsules, and datagrams. -
RFC 9110: HTTP semantics, including trailer fields and informational responses.
-
RFC 9218: Extensible Prioritization Scheme for HTTP.
-
RFC 9297: HTTP Datagrams and the Capsule Protocol.
-
RFC 9221: Unreliable Datagram Extension to QUIC.
-
RFC 8441 / RFC 9220: WebSocket bootstrapping via extended CONNECT over HTTP/2 and HTTP/3.
-
RFC 9298: Proxying UDP in HTTP.
-
RFC 9484: Proxying IP in HTTP.
-
RFC 9651: Structured Field Values for HTTP.
Design Rationale
Why a context object?
The properties needed on the server side fall into distinct categories:
| Category | Examples | Belongs to... |
|---|---|---|
| The HTTP message | method, url, headers, body | The Request
|
| Connection metadata | remote address, ALPN protocol | The connection |
| Server capabilities | informational responses | The response pipeline |
| Execution lifecycle | waitUntil | The runtime environment |
None of these are properties of the HTTP message itself. Putting them on Request conflates the message with its processing environment. A context object keeps them separate.
Runtimes have independently arrived at similar patterns:
-
Cloudflare Workers:
fetch(request, env, ctx)— three positional arguments, wherectxprovideswaitUntil(). -
Deno:
Deno.serve((request, info) => ...)— two arguments, whereinfoprovidesremoteAddrandcompleted. -
Hono:
(c) => ...— a context object withc.reqfor the request.
A single context object avoids positional fragility and extends without signature changes.
The indirection also permits lazy materialization. Because the Request is reached through an accessor (ctx.request), an implementation can defer constructing it — parsing the URL, materializing the Headers, allocating the AbortSignal — until the handler first accesses it. A handler that denies a request under load never pays for a Request it never reads. A signature that passes the Request as an argument forecloses this optimization.
Why not extend Request and Response?
Subtyping (ServerRequest extends Request) creates structural compatibility questions: which client-specific Request properties (.cache, .credentials, .mode, .redirect, .destination) should a server-side subtype expose, and with what values? These properties are meaningless for incoming server requests.
Duck-typing (a ServerRequest that replicates Request’s interface) adds instanceof and type identity problems on top.
Using a standard Request avoids both. ctx.request instanceof Request is true. Proxying is return fetch(ctx.request).
Copyright
© 2026 Ecma International
Permission under Ecma’s copyright to copy, modify, prepare derivative works of, and distribute this work, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the full text of this copyright notice on ALL copies of the work or portions thereof.
THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
1. Scope
This proposal defines the Fetch Server API, a server-side HTTP API built on the Fetch Standard’s Request and Response types. It specifies:
-
A
ServerContextinterface providing connection metadata, server capabilities, and lifecycle management for incoming HTTP requests. -
A
ConnectContextinterface extendingServerContextfor CONNECT and extended CONNECT tunnel protocols. -
An exchange state model governing response commitment, and an error code registry mapping protocol-version-independent codes to HTTP/2 and HTTP/3 error signals.
-
A
Tunnelinterface for bidirectional communication over established HTTP tunnels, including the Capsule Protocol and HTTP Datagrams. -
A
WebTransportSessioninterface for multiplexed streams and datagrams over HTTP connections. -
A handler object model with
fetch()andconnect()methods for processing requests and tunnels. -
An optional infrastructure layer providing
serve(),Server,Listener, and lifecycle management.
2. Conformance
This specification has two layers:
Core (handler model): The ServerContext, ConnectContext, handler object pattern, and the handler callback signatures. A conforming implementation MUST support this layer.
Infrastructure (server lifecycle): The serve() function, Server, Listener, Closeable, ListenOptions, ServerOptions, and TLSOptions. An implementation MAY support this layer. An implementation that manages server lifecycle externally (e.g., an edge runtime where binding, TLS, and connection management are handled by the platform outside the application) is not required to expose serve(), Server, or Listener. Such an implementation is conforming as long as it implements the core layer.
A conforming implementation shall also conform to [ECMASCRIPT] and [WEBIDL].
Support for the following features is OPTIONAL at both layers:
-
Priority
-
Informational responses
-
Extended CONNECT and tunnel protocols
-
WebTransport
-
HTTP Datagrams
-
HTTP/3 and QUIC
An implementation that does not support an optional feature MUST still expose the relevant interface members. A method whose effect the implementation cannot provide throws a "NotSupportedError" DOMException; promise-returning methods reject with the same error. Priority is the exception: it is advisory by design and degrades silently — clientPriority returns the defaults, assignments to serverPriority are accepted and ignored, and an onpriority callback is never invoked.
deny() and waitUntil() are not optional. Every implementation can decline a request; the wire-level fidelity of error codes varies by protocol, and where a signal is unavailable the implementation uses the closest supported behavior (e.g., the HTTP/1.1 fallback in § 7.5 Denial). Applications rely on waitUntil() for cleanup and logging actions that must complete, so every implementation MUST honor it (subject to the time limit in § 7.6 Request lifecycle).
3. Normative references
The following documents are referred to in the text in such a way that some or all of their content constitutes requirements of this document.
References
Normative References
- [DOM]
- Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
- [ECMASCRIPT]
- ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
- [FETCH]
- Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
- [STREAMS]
- Adam Rice; et al. Streams Standard. Living Standard. URL: https://streams.spec.whatwg.org/
- [WEBIDL]
- Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/
4. Terms and definitions
For the purposes of this document, the terms and definitions given in [ECMASCRIPT], the Fetch Standard [FETCH], the DOM Standard [DOM], and the following apply.
4.1. Fetch Server API
the server-side HTTP API defined by this specification
4.2. web-interoperable runtime
ECMAScript-based runtime environment as defined by WinterTC
4.3. server
an entity that accepts HTTP connections, receives requests, and sends responses
4.4. handler
a JavaScript function provided by the application that processes incoming requests or tunnel establishment attempts
4.5. exchange
the server-side processing of a single incoming request or tunnel establishment attempt, from receipt until the response (or tunnel) completes and all lifecycle-extension promises settle
4.6. tunnel
a long-lived bidirectional communication channel established through an HTTP connection via the CONNECT method or extended CONNECT
4.7. connect protocol
the value of the :protocol pseudo-header in an extended CONNECT request, identifying the protocol being tunneled (e.g., "websocket", "webtransport", "connect-udp", "connect-ip")
4.8. protocol marker
a Symbol.for('server.protocol') property on a handler object with an integer version value, used by the runtime to distinguish this API from legacy invocation conventions
5. Web IDL definitions
5.1. SocketAddress
dictionary {SocketAddress DOMString ;address unsigned short ;port DOMString ; };family
A SocketAddress represents a network endpoint. The address member is the IP address as a string. The family member indicates the address family: "IPv4" or "IPv6".
5.2. RequestPriority
dictionary {RequestPriority unsigned short = 3;urgency boolean =incremental false ; };
A RequestPriority represents a priority signal per RFC 9218. The urgency member is an integer in the range 0–7, where 0 is the highest priority and 7 is the lowest. The default is 3. The incremental member indicates whether incremental delivery is preferred.
5.3. Callback definitions
callback =PriorityCallback undefined (optional RequestPriority = {});priority callback =FetchHandler any (ServerContext );ctx callback =ConnectHandler any (ConnectContext );ctx callback =ErrorHandler any (ServerContext ,ctx any );error
The FetchHandler callback receives a ServerContext and returns a Response, undefined, or a Promise resolving to one of those. The return type is specified as any because Web IDL cannot express (Response or undefined or Promise<Response or undefined>) as a callback return type.
The ConnectHandler callback receives a ConnectContext and returns a Response, undefined, or a Promise resolving to one of those.
The ErrorHandler callback receives the context of a failed exchange (a ServerContext or ConnectContext) and the thrown value, and returns a Response, undefined, or a Promise resolving to one of those. See § 12.3 The ErrorHandler.
5.4. Options dictionaries
dictionary {WebSocketUpgradeInit sequence <DOMString >; };protocol dictionary {WebTransportCloseInfo unsigned long = 0;closeCode USVString = ""; };reason dictionary { (TLSCertificate DOMString or BufferSource ); (cert DOMString or BufferSource ); };key callback =SNICallback any (DOMString );hostname dictionary :TLSOptions TLSCertificate {sequence <DOMString >; (alpn SNICallback or record <DOMString ,TLSCertificate >); };sni dictionary { };QUICOptions dictionary {ServerOptions unsigned short ;port DOMString ;hostname TLSOptions ; (tls boolean or QUICOptions )=quic false ;boolean =autoContinue true ;AbortSignal ; };signal dictionary {ListenOptions unsigned short = 0;port DOMString = "0.0.0.0";hostname TLSOptions ; (tls boolean or QUICOptions ); };quic dictionary {HandlerObject required FetchHandler ;fetch ConnectHandler ;connect ErrorHandler ; };error
5.5. The ServerContext interface
[Exposed=*]interface { [ServerContext SameObject ]readonly attribute Request request ;readonly attribute SocketAddress remoteAddress ;readonly attribute SocketAddress localAddress ;readonly attribute DOMString alpnProtocol ;readonly attribute boolean encrypted ;readonly attribute DOMString ?serverName ;readonly attribute RequestPriority clientPriority ;attribute RequestPriority ?serverPriority ;attribute PriorityCallback ?onpriority ;readonly attribute boolean autoContinue ;undefined sendInformational (unsigned short ,status optional HeadersInit );headers undefined deny (optional any );error undefined waitUntil (Promise <any >); };promise
The ServerContext interface is the server-side processing environment for an incoming HTTP request. It provides the Request, connection metadata, server capabilities, and lifecycle management.
5.6. The ConnectContext interface
[Exposed=*]interface :ConnectContext ServerContext {readonly attribute DOMString ?connectProtocol ;Promise <Tunnel >accept (optional ResponseInit = {});init object upgradeWebSocket (optional WebSocketUpgradeInit = {});options Promise <WebTransportSession >upgradeWebTransport (); };
The ConnectContext interface is the server-side processing environment for an incoming CONNECT or extended CONNECT request. It extends ServerContext with protocol identification and tunnel establishment capabilities.
5.7. The Tunnel interface
[Exposed=*]interface {Tunnel readonly attribute ReadableStream ;readable readonly attribute WritableStream ;writable CapsuleStream capsules ();DatagramStream datagrams ();undefined close ();readonly attribute Promise <undefined >closed ; };
The Tunnel interface represents an established tunnel through an HTTP connection.
5.8. The CapsuleStream interface
[Exposed=*]interface {CapsuleStream readonly attribute ReadableStream ;readable readonly attribute WritableStream ; };writable
The CapsuleStream interface provides typed access to the Capsule Protocol on the CONNECT data stream. The readable attribute yields Capsule objects. The writable attribute accepts Capsule objects.
5.9. The Capsule dictionary
dictionary {Capsule unsigned long long ;type Uint8Array ; };data
A Capsule represents a single typed capsule as defined in RFC 9297.
5.10. The DatagramStream interface
[Exposed=*]interface {DatagramStream readonly attribute ReadableStream ;readable readonly attribute WritableStream ;writable readonly attribute boolean ; };unreliable
The DatagramStream interface provides access to HTTP Datagrams associated with a tunnel or WebTransportSession. The readable attribute yields Uint8Array payloads. The writable attribute accepts Uint8Array payloads. The unreliable attribute indicates whether datagrams are sent as unreliable QUIC DATAGRAM frames (true) or as reliable DATAGRAM capsules (false).
5.11. The WebTransportSession interface
[Exposed=*]interface {WebTransportSession readonly attribute ReadableStream ;incomingBidirectionalStreams readonly attribute ReadableStream ;incomingUnidirectionalStreams Promise <WebTransportBidirectionalStream >();createBidirectionalStream Promise <WritableStream >();createUnidirectionalStream readonly attribute DatagramStream ;datagrams readonly attribute DOMString ;transport undefined (close optional WebTransportCloseInfo = {});closeInfo readonly attribute Promise <WebTransportCloseInfo >;closed readonly attribute Promise <undefined >; };ready
The WebTransportSession interface represents an established WebTransport session, providing multiplexed streams and datagrams over an HTTP connection.
5.12. The WebTransportBidirectionalStream interface
[Exposed=*]interface {WebTransportBidirectionalStream readonly attribute ReadableStream ;readable readonly attribute WritableStream ; };writable
5.13. The Closeable interface mixin
interface mixin {Closeable attribute boolean busy ;Promise <undefined >close ();undefined destroy (optional any );error readonly attribute Promise <undefined >closed ; };
The Closeable mixin provides a uniform lifecycle interface shared by Server and Listener. Implementations of Closeable should support Symbol.asyncDispose, which calls close() and waits for the closed promise.
5.14. The Listener interface
[Exposed=*]interface {Listener readonly attribute SocketAddress ; };address Listener includes Closeable ;
The Listener interface represents a single network binding. The address attribute returns the SocketAddress that this listener is bound to.
5.15. The Server interface
[Exposed=*]interface {Server Promise <Listener >listen (optional ListenOptions = {});options iterable <Listener >; };Server includes Closeable ;
The Server interface manages one or more listeners and dispatches incoming requests to handlers.
5.16. The serve() function
[Exposed=*]namespace {FetchServer Server serve (HandlerObject ,handler optional ServerOptions = {}); };options
FetchServer is used here for specification purposes. The actual module specifier is implementation-defined. Possible values include "http", "node:http", "node:serve", or a WinterTC-standardized module name. The import surface is an open question; see § 17.1 Module specifier.
6. Exchange states
Each exchange is in exactly one of five states. The state determines which context operations are valid, and is the foundation for the error model in § 11 Errors.
- open
-
Nothing has been sent to the client. The initial state.
- informational
-
At least one informational (1xx) response has been sent; no final response has begun.
- committed
-
A final response has begun: the handler returned a
Response, or a tunnel was accepted viaaccept(),upgradeWebSocket(), orupgradeWebTransport(). - denied
-
deny()was called. Terminal. - aborted
-
The client reset the stream, or the connection was lost, before the exchange completed. Terminal. The exchange’s abort reason is a protocol error describing the cause (§ 11.4 Inbound errors).
An exchange is response-capable while it is open or informational.
| Operation | open | informational | committed | denied | aborted |
|---|---|---|---|---|---|
sendInformational()
| Allowed → informational | Allowed | Throws | Throws | Discarded |
deny()
| Allowed → denied | Throws | Throws | Throws | Discarded |
deny() with the fall-through sentinel
| Allowed (remains open; rerouted to fetch())
| Throws | Throws | Throws | Discarded |
accept() and the upgrade methods
| Allowed → committed | Allowed → committed | Throws | Throws | Fails with the abort reason |
The two failure behaviors are deliberate:
-
Deterministic misuse throws. The informational, committed, and denied states are entered by the handler’s own actions, so a violation is a programming error: the operation throws an
"InvalidStateError"DOMException. -
Races are inert. The aborted state can be entered by the peer at any moment; a handler cannot reliably check-then-act against it. Synchronous operations are silently discarded rather than throwing. Promise-returning establishment methods reject with the abort reason.
Note: deny() is restricted to open because its default signal (ERR_HTTP_REQUEST_REJECTED) is a guarantee to the client that no processing occurred and the request may be safely retried — even for non-idempotent methods. Once anything has been sent, that guarantee would be false. The state model makes it structurally impossible to send it untruthfully.
7. ServerContext
7.1. The request
request attribute returns a standard Request as defined in the Fetch Standard.
The Request is constructed by the implementation from the incoming HTTP message. It has:
method: The HTTP method.url: The full request URL, reconstructed from the request target,Hostheader, and connection properties (scheme, port).headers: The request header fields.body: The request body as aReadableStream, ornull.signal: AnAbortSignalthat is aborted if the client disconnects or the connection is lost.trailers: APromise<Headers>that resolves to the trailing header fields after the body is consumed.
Because ctx.request is a standard Request, it can be passed directly to client-side fetch() for proxying:
async fetch( ctx) { return fetch( ctx. request); }
7.2. Connection metadata
remoteAddress attribute returns the SocketAddress of the remote peer. If the remote address is not available (e.g., the runtime abstracts it away), the implementation MAY return a SocketAddress with empty address and 0 port.
localAddress attribute returns the SocketAddress of the local endpoint the connection arrived on, identifying the binding when a server listens on multiple addresses or ports. The same availability escape applies as for remoteAddress.
alpnProtocol attribute returns the ALPN protocol identifier negotiated for this connection. Common values are "http/1.1", "h2", and "h3". If ALPN was not negotiated (e.g., plaintext HTTP/1.1), the value is "http/1.1".
encrypted attribute returns true if the connection is protected by TLS (including QUIC, where TLS is intrinsic), and false otherwise. This distinguishes plaintext HTTP/1.1 from HTTP/1.1 over TLS without ALPN, which alpnProtocol alone cannot.
serverName attribute returns the SNI server name from the TLS ClientHello, or null if the connection is not encrypted or the client did not send SNI. Unlike the Host header, this value is what TLS certificate selection was based on; the two can legitimately differ, which matters for multi-tenant routing.
Note: The alpnProtocol value identifies the HTTP version of the connection. Handlers generally should not branch on this value. It is provided for logging, debugging, and the rare case where application behavior legitimately depends on the transport.
7.3. Priority
RFC 9218 defines the Extensible Prioritization Scheme with urgency (0–7, default 3) and incremental (boolean, default false). Priority signals flow in two directions — client to server and server to implementation — represented by two properties:
-
**
clientPriority** (read-only) — what the client asked for. -
**
serverPriority** (read/write) — what the server decided.
7.3.1. Client priority
clientPriority attribute returns the client’s current priority signal, parsed from the Priority request header.
The clientPriority getter is live: it always reflects the most recent client signal. When the client sends a PRIORITY_UPDATE frame (HTTP/2 or HTTP/3), the implementation updates the value returned by clientPriority before invoking any registered callback.
If the request has no Priority header and no PRIORITY_UPDATE frame has been received, clientPriority returns the default values ({ urgency: 3, incremental: false }).
7.3.2. Reprioritization
onpriority attribute holds the callback invoked when a PRIORITY_UPDATE frame is received for this request’s stream, following the web platform’s event-handler idiom: ctx.onpriority = fn. The callback receives a RequestPriority dictionary with the new values. The attribute defaults to null; because it is a plain attribute, at most one callback is registered at a time, assignment replaces the previous value, and assigning null removes it.
PRIORITY_UPDATE frames are hop-by-hop (HTTP/2 and HTTP/3 only) and may arrive at any time during the request lifecycle. If no callback is registered, PRIORITY_UPDATE frames still update clientPriority — the handler is simply not notified synchronously.
For HTTP/1.1, where PRIORITY_UPDATE frames do not exist, the callback is never invoked. The initial priority from the Priority header (if present) is still available via clientPriority.
7.3.3. Server priority
serverPriority attribute is a read/write property that overrides the server-internal scheduling priority for this response’s delivery. It defaults to null, meaning "use the client’s priority signal."
When serverPriority is null, the implementation uses the client’s priority signal (clientPriority) for scheduling. When serverPriority is set, the implementation uses the server’s value instead, regardless of subsequent PRIORITY_UPDATE frames from the client. (The client’s updates still appear in clientPriority and still trigger the onpriority callback — the server simply overrides the scheduling decision.)
serverPriority is purely about server-internal scheduling. It does NOT set the Priority response header — that is for signaling priority preferences to intermediaries and is set directly on the Response:
ctx. serverPriority= { urgency: 1 }; // internal scheduling return new Response( body, { headers: { 'Priority' : 'u=1' }, // signal to intermediaries });
These are intentionally separate concerns. A server may override internal scheduling without signaling intermediaries, or vice versa.
7.4. Informational responses
sendInformational(status, headers) method sends an informational (1xx) response to the client.
- If the implementation does not support informational responses, throw a
"NotSupportedError"DOMException. - If status is
101, or is not in the range 100–199 inclusive, throw aRangeError. - If the exchange is committed or denied, throw an
"InvalidStateError"DOMException. - If the exchange is aborted, return.
-
If status is
100:- If no pending 100 obligation exists, throw an
"InvalidStateError"DOMException. - Construct a headers object from headers if provided, send a
100 Continueresponse with those headers, and clear the obligation. The exchange is now informational. - Return.
- If no pending 100 obligation exists, throw an
- Construct a headers object from headers if provided.
- If
autoContinueistrueand a pending 100 obligation exists, send a100 Continueresponse and clear the obligation. - Send the informational response with status status and the constructed headers. The exchange is now informational.
Status 101 (Switching Protocols) is categorically rejected: protocol upgrades are handled through the connect() handler, where accept() performs the handshake on the application’s behalf (§ 8.2 HTTP/1.1 upgrade normalization). A 101 is also unrepresentable in HTTP/2 and HTTP/3 (RFC 9113 Section 8.6), so permitting it would leak an HTTP/1.1 wire artifact through the version-transparent handler model.
Notable informational status codes:
-
100 Continue: Indicates the server is willing to accept the request body. Sent by the implementation according to the rules below.
-
103 Early Hints: Provides header fields (typically
Linkheaders) that the client can use to start preloading resources before the final response (RFC 8297).
An implementation that does not support informational responses throws a "NotSupportedError" DOMException rather than silently discarding the call: a swallowed 1xx would misrepresent observable protocol behavior that applications depend on, such as Expect: 100-continue flows and Early Hints preloading.
Note: The Fetch Standard’s onInformation callback (in fetch() options) is the client-side counterpart: it receives informational responses. sendInformational() is the server-side counterpart: it sends them.
7.4.1. 100 Continue
When a request arrives with Expect: 100-continue, the implementation records a pending 100 obligation rather than responding immediately. Sending 100 Continue on arrival would invite the request body before the handler has had the opportunity to deny() the exchange, defeating the purpose of the Expect mechanism.
autoContinue attribute returns true if the implementation resolves the pending 100 obligation automatically, and false if sending the 100 Continue is the application’s responsibility. In the infrastructure layer the value is configured via autoContinue (default true). In runtimes without the infrastructure layer, the value is determined by the runtime — platform policy, a compatibility flag, or similar external mechanism — and this attribute is how the application discovers it.
An explicit sendInformational(100) call is valid if and only if a pending 100 obligation exists, in both modes; it sends the 100 Continue (with the caller’s headers) and clears the obligation. Calling it when no obligation exists — the request carried no Expect: 100-continue, or a 100 Continue was already sent — throws an "InvalidStateError" DOMException: an unsolicited or duplicate 100 Continue is always a programming error. (RFC 9110 Section 15.2.1 merely has clients discard such interim responses; this API rejects them loudly rather than emitting meaningless ones.)
The obligation otherwise resolves at the first of the following events:
| Event | autoContinue is true
| autoContinue is false
|
|---|---|---|
The handler calls sendInformational(100)
| Send 100 Continue with the caller’s headers; clear the obligation
| |
The handler sends another 1xx via sendInformational()
| Send 100 Continue first, then the requested response
| Send only the requested response |
| The handler begins reading the request body | Send 100 Continue — the server is ready to receive
| Nothing is sent |
The handler returns a final Response without reading the body
| Omit, per RFC 9110 Section 10.1.1 | |
deny() is called, or the exchange is aborted
| Discard | |
An automatically sent 100 Continue transitions the exchange to informational, exactly as an explicit informational response does: bytes have been sent, so deny() is no longer available.
Flushing the pending 100 Continue ahead of any other explicit informational response keeps the client’s view deterministic: a client waiting on its Expect never has to interpret some other 1xx as an implicit answer. Any 1xx from the handler means the exchange is proceeding, so the implementation confirms the body is welcome first.
Note: When autoContinue is false, reading the request body without having sent the 100 Continue can stall: clients typically wait for the 100 before transmitting content, though RFC 9110 Section 10.1.1 advises them not to wait indefinitely. Liveness is the application’s responsibility. What manual mode buys is finer control — for example, sending 103 Early Hints while authorization is still deciding, without committing to receive the body.
7.5. Denial
deny(error) method signals that the handler is declining to process the request. No Response is generated; the handler returns undefined (or nothing) after calling deny().
- If the exchange is aborted, return.
- If the exchange is not open, throw an
"InvalidStateError"DOMException(see § 6 Exchange states). -
If error is the fall-through sentinel:
- If the exchange is not upgrade-originated, throw an
"InvalidStateError"DOMException. - Mark the exchange as falling through to
fetch(). The exchange remains open and nothing is sent. This context becomes inert: subsequent calls toaccept(), the upgrade methods, ordeny()throw an"InvalidStateError"DOMException. See § 8.7 Upgrade fall-through. - Return.
- If the exchange is not upgrade-originated, throw an
- Set the exchange state to denied.
-
Reset the stream, carrying the protocol error code selected by error:
-
HTTP/2:
RST_STREAM -
HTTP/3:
RESET_STREAM -
HTTP/1.1: Implementation-defined (close the connection, or send a 503 response and close as a pragmatic fallback)
-
deny() is terminal for the handler: the only valid completion after calling it is undefined (or a promise resolving to undefined). The handler may perform additional work before returning — the exchange is not complete until the handler’s promise and all waitUntil() promises settle — but no response is expected, and resolving with any other value is an invalid return (§ 12.4 Error handling).
If error has a .code property whose value is an error code sendable via deny() (see § 11.1 Error codes), the implementation maps it to the corresponding wire-level code. When error is omitted, has no .code, or the code is unrecognized, the default is ERR_HTTP_REQUEST_REJECTED (REFUSED_STREAM / H3_REQUEST_REJECTED). This is the correct default because deny() means the request was not processed, which is exactly the semantic REFUSED_STREAM was designed to express. A client receiving this signal knows the request can be safely retried — including non-idempotent methods like POST.
The ERR_HTTP_GOAWAY code is a connection-level signal. Unlike the other codes which reset a single stream, ERR_HTTP_GOAWAY instructs the implementation to:
-
Refuse the current request (as with any
deny()call). -
Send a GOAWAY frame on the underlying connection, indicating that no new streams will be accepted.
-
Allow in-flight streams (requests already being processed by other handler invocations on the same connection) to complete normally.
This provides a per-request escape hatch for connection-level concerns — rate limiting, credential revocation, or misbehavior detection — without exposing a connection object to the handler.
For HTTP/1.1, ERR_HTTP_GOAWAY closes the connection after the current exchange. Since HTTP/1.1 connections are serial (ignoring pipelining), this is equivalent to closing the connection.
Note: Proactive GOAWAY (shutting down connections without a triggering request) is handled by close() and destroy() in the infrastructure layer, not by deny(). The deny() mechanism covers the reactive case where a handler decides during request processing that the connection should be closed.
7.6. Request lifecycle
waitUntil(promise) method extends the lifetime of the request processing beyond the return of the handler function. The server does not consider the request fully complete until all promises passed to waitUntil() have settled.
This is analogous to ExtendableEvent.waitUntil() in Service Workers and ctx.waitUntil() in Cloudflare Workers.
Unlike the optional features enumerated in § 2 Conformance, waitUntil() MUST be supported by every implementation: applications depend on it for cleanup and logging actions that must complete.
An implementation MAY impose an implementation-defined time limit on how long it will wait for outstanding waitUntil() promises to settle.
If a promise passed to waitUntil() rejects, the implementation reports the rejection in an implementation-defined manner (e.g., logging). The rejection does not affect the response and does not invoke the handler’s error() method (§ 12.3 The ErrorHandler).
8. ConnectContext
8.1. Protocol identification
connectProtocol attribute returns the value of the :protocol pseudo-header for extended CONNECT requests:
"websocket"— WebSocket over HTTP/2 or HTTP/3"webtransport"— WebTransport session"connect-udp"— UDP proxying"connect-ip"— IP proxying- Any other registered or private-use protocol identifier
null— Plain CONNECT (no:protocolpseudo-header; TCP tunneling)
8.2. HTTP/1.1 upgrade normalization
HTTP/1.1 uses the Upgrade header field (RFC 9110 Section 7.8) rather than extended CONNECT. To provide a unified handler model, an implementation normalizes HTTP/1.1 upgrade requests into ConnectContext objects: when an HTTP/1.1 request is received with Upgrade and Connection: Upgrade header fields, the implementation constructs a ConnectContext with connectProtocol set to the first protocol token of the Upgrade field value and the request preserving the original method, headers, and URL. Such an exchange is upgrade-originated. (The full, preference-ordered token list remains available via the request’s Upgrade header.)
For WebSocket (GET with Upgrade: websocket), this means WebSocket handling is in one place — the connect() handler — regardless of HTTP version.
On an upgrade-originated exchange, accept() performs the HTTP/1.1 upgrade handshake: it sends a 101 (Switching Protocols) response with an Upgrade header field echoing connectProtocol, and the resulting Tunnel’s readable and writable expose the raw connection bytes. As with extended CONNECT, the application never sends the 101 itself; the handshake is the implementation’s concern (§ 7.4 Informational responses).
8.2.1. Routing
-
If a
connect()handler is provided, upgrade-originated exchanges are routed to it. The handler completes the upgrade (accept(),upgradeWebSocket()), refuses it (a non-2xxResponse, ordeny()), or declines only the upgrade offer via the fall-through sentinel (§ 8.7 Upgrade fall-through). -
If no
connect()handler is provided, the upgrade offer is ignored, as RFC 9110 Section 7.8 permits: the request is routed tofetch()as an ordinary request. TheUpgradeheader remains visible there, but the handler cannot act on it. -
An implementation MAY instead handle particular upgrade protocols itself, without routing them to the application.
Note: This differs from CONNECT and extended CONNECT, which have no ordinary-request interpretation; those are answered with 501 Not Implemented when no connect() handler is provided.
8.3. Accepting a tunnel
accept(init) method accepts the CONNECT request and establishes a tunnel. It returns a Promise<Tunnel> that resolves when the tunnel is established.
The optional init parameter allows setting response headers on the success response.
Accepting commits the exchange (committed): subsequent calls to accept(), the upgrade methods, or deny() throw an "InvalidStateError" DOMException, per § 6 Exchange states. If the exchange is aborted, the returned promise rejects with the abort reason. upgradeWebSocket() and upgradeWebTransport() are committing operations subject to the same rules.
On upgrade-originated exchanges, accepting performs the HTTP/1.1 upgrade handshake instead of a CONNECT success response; see § 8.2 HTTP/1.1 upgrade normalization.
8.4. WebSocket upgrade
upgradeWebSocket(options) method is a convenience for WebSocket tunnel establishment. It performs the WebSocket-specific handshake (including subprotocol negotiation if options.protocol is provided) and returns a standard WebSocket object already in the OPEN state.
If connectProtocol is not "websocket", this method throws an "InvalidStateError" DOMException.
8.5. WebTransport upgrade
upgradeWebTransport() method establishes a WebTransport session. It returns a Promise<WebTransportSession> that resolves when the session is established.
If connectProtocol is not "webtransport", this method throws an "InvalidStateError" DOMException.
8.6. Denying a tunnel
A connect() handler can refuse a tunnel outright in two ways:
Protocol-level denial via deny() (inherited from ServerContext). This resets the stream without sending an HTTP response:
async connect( ctx) { ctx. deny(); // REFUSED_STREAM — client may retry return ; }
Application-level denial by returning a Response. This sends a standard HTTP error response:
async connect( ctx) { return new Response( null , { status: 403 }); }
The choice depends on the intended signal: deny() says "I never processed this" (protocol-level); a Response says "I processed this and the answer is no" (application-level).
On upgrade-originated exchanges there is a third option: declining only the upgrade offer while letting the request proceed as ordinary HTTP. See § 8.7 Upgrade fall-through.
8.7. Upgrade fall-through
Calling deny() with the fall-through sentinel — Symbol.for('server.fallthrough') — denies the tunnel without denying the carrying request. The upgrade offer is treated as ignored, and the request proceeds to fetch() as ordinary HTTP:
async connect( ctx) { if ( ctx. connectProtocol=== 'websocket' && ! wsEnabled) { ctx. deny( Symbol. for ( 'server.fallthrough' )); return ; // fetch() takes it from here } const ws= ctx. upgradeWebSocket(); // ... }
The sentinel is a registered symbol, following the same pattern as the protocol marker: it costs no allocation, cannot collide with an application’s error objects, and is not an error code — nothing is sent on the wire.
The sentinel is valid only on upgrade-originated exchanges. An HTTP/1.1 request bearing Upgrade is an ordinary request carrying an opportunistic offer that a server may ignore (RFC 9110 Section 7.8); CONNECT and extended CONNECT requests have no such interpretation — the request is the tunnel — so the sentinel on those exchanges throws an "InvalidStateError" DOMException.
After the connect() invocation settles with undefined, the implementation invokes fetch() with a fresh ServerContext for the same exchange:
-
Nothing has been sent and the exchange is still open, so the
fetch()handler retains its full capabilities — includingdeny(), honestly, since no processing is visible to the client. -
waitUntil()registrations and any pending 100 obligation carry over. -
fetch()has no fall-through of its own; the flow terminates there.
fetch() is not invoked until the connect() invocation settles, so it is in the application’s interest to return promptly after calling deny() with the sentinel — waitUntil() is the tool for anything slow. If the invocation rejects, or resolves with anything other than undefined, the fault takes precedence: the exchange is still response-capable, so the standard fault path applies (§ 12.4 Error handling) and fetch() is not invoked.
9. Tunnel
A Tunnel represents an established tunnel through an HTTP connection. It provides three layers of communication corresponding to the layers defined in the Capsule Protocol:
-
Raw data stream: Bidirectional byte stream on the CONNECT data channel.
-
Capsule Protocol: Typed TLV-framed messages for control signaling.
-
HTTP Datagrams: Discrete messages that may be unreliable on HTTP/3.
9.1. Raw data stream
The readable and writable attributes provide direct access to the CONNECT data stream as a ReadableStream and WritableStream of bytes.
For a plain CONNECT tunnel (no :protocol), the raw data stream carries the tunneled TCP payload. For WebSocket, the raw data stream carries WebSocket frames.
For protocols that use the Capsule Protocol, consuming the raw data stream directly and consuming capsules are mutually exclusive.
9.2. Capsule Protocol
capsules() method returns a CapsuleStream providing typed access to the Capsule Protocol on the CONNECT data stream. Calling capsules() consumes the raw data stream — subsequent access to readable or writable throws an "InvalidStateError" DOMException.
const { readable, writable} = tunnel. capsules(); // Reading capsules for await ( const capsuleof readable) { console. log( capsule. type, capsule. data); } // Writing capsules const writer= writable. getWriter(); await writer. write({ type: 0xff37a2 , data: new Uint8Array([...]) });
9.3. HTTP Datagrams
datagrams() method returns a DatagramStream providing access to HTTP Datagrams associated with this tunnel.
On HTTP/3 connections where QUIC DATAGRAM frames are available, datagrams are sent and received as unreliable QUIC DATAGRAM frames. The unreliable property is true.
On HTTP/2 or HTTP/1.1 connections, datagrams are sent as DATAGRAM capsules (type 0x00) on the data stream. The unreliable property is false. Delivery is reliable (TCP guarantees it), but the API is the same.
Both CONNECT-UDP and CONNECT-IP use HTTP Datagrams for their data plane. The datagrams() API provides the foundation for both.
9.4. Closure
close() method closes the tunnel gracefully.
closed attribute returns a promise that resolves when the tunnel is closed cleanly by either peer, and rejects with a protocol error when the tunnel terminates abnormally — for example, the peer reset the stream or the connection was lost (see § 11.4 Inbound errors).
10. WebTransport
WebTransport provides multiplexed streams and unreliable datagrams over an HTTP connection. On HTTP/3, it uses native QUIC streams and QUIC DATAGRAM frames. On HTTP/2, it falls back to the Capsule Protocol.
10.1. Session establishment
A WebTransportSession is obtained via upgradeWebTransport():
async connect( ctx) { if ( ctx. connectProtocol=== 'webtransport' ) { const session= await ctx. upgradeWebTransport(); // Accept incoming bidirectional streams for await ( const streamof session. incomingBidirectionalStreams) { handleStream( stream); // { readable, writable } } } }
10.2. Streams
A WebTransportSession can carry multiple independent streams:
-
Bidirectional streams: Opened by either side. Each has a
readableandwritable. -
Unidirectional streams: Opened by one side, read by the other.
On HTTP/3, each WebTransport stream maps to a native QUIC stream. Streams are independent: a slow stream does not block others.
On HTTP/2, streams are multiplexed over the single CONNECT data stream using the Capsule Protocol. Head-of-line blocking applies (TCP guarantees ordering).
10.3. Datagrams
WebTransport datagrams follow the same DatagramStream interface as tunnel datagrams. The datagrams attribute provides a DatagramStream.
10.4. Transport awareness
The transport property indicates the underlying transport mechanism:
-
"quic": Native QUIC streams and QUIC DATAGRAM frames. No head-of-line blocking between streams. Datagrams are unreliable. -
"capsule": Capsule Protocol over a single HTTP/2 data stream. Head-of-line blocking applies. Datagrams are reliable (sent as DATAGRAM capsules).
11. Errors
HTTP/2 and HTTP/3 carry typed error signals in both directions: stream resets (RST_STREAM, RESET_STREAM, STOP_SENDING) and connection-level GOAWAY frames, each with an error code. This section defines a protocol-version-independent registry of error codes, the shape of the JavaScript errors that carry them, and how they propagate outbound (server to client) and inbound (client to server).
11.1. Error codes
An error code is a string identifying a protocol-level error condition independently of HTTP version.
| Code | HTTP/2 | HTTP/3 | Meaning |
|---|---|---|---|
ERR_HTTP_REQUEST_REJECTED
| REFUSED_STREAM | H3_REQUEST_REJECTED | Not processed. The client may safely retry, including non-idempotent methods. |
ERR_HTTP_REQUEST_CANCELLED
| CANCEL | H3_REQUEST_CANCELLED | Intentionally cancelled. May have been partially processed. |
ERR_HTTP_INTERNAL_ERROR
| INTERNAL_ERROR | H3_INTERNAL_ERROR | Internal error in the HTTP stack or application. |
ERR_HTTP_STREAM_RESET
| any other code | any other code | Generic stream reset; the wire code has no more specific mapping. |
ERR_HTTP_GOAWAY
| GOAWAY frame | GOAWAY frame | The connection is winding down; no new streams will be accepted. |
ERR_HTTP_PROTOCOL_ERROR
| PROTOCOL_ERROR | H3_GENERAL_PROTOCOL_ERROR | Framing or protocol violation. |
ERR_HTTP_CONNECT_ERROR
| CONNECT_ERROR | H3_CONNECT_ERROR | A tunnel was reset or abnormally closed. |
ERR_HTTP_REQUEST_BODY_REJECTED
| RST_STREAM (NO_ERROR) after a complete response | STOP_SENDING (H3_NO_ERROR) | The server does not want the remainder of the request body; the response is unaffected. |
ERR_HTTP_CONNECTION_RESET
| transport-level | The underlying connection was lost. | |
ERR_HTTP_TIMEOUT
| transport-level | An implementation-defined timeout elapsed. | |
Where each code may appear:
-
Sendable via
deny():ERR_HTTP_REQUEST_REJECTED(the default),ERR_HTTP_REQUEST_CANCELLED,ERR_HTTP_INTERNAL_ERROR,ERR_HTTP_CONNECT_ERROR,ERR_HTTP_GOAWAY. -
Sendable by aborting a committed response (§ 11.3 Outbound errors):
ERR_HTTP_REQUEST_CANCELLED,ERR_HTTP_INTERNAL_ERROR(the default). -
Sendable by cancelling the request body (§ 11.3 Outbound errors):
ERR_HTTP_REQUEST_BODY_REJECTED. -
Observable inbound (§ 11.4 Inbound errors):
ERR_HTTP_REQUEST_CANCELLED,ERR_HTTP_STREAM_RESET,ERR_HTTP_PROTOCOL_ERROR,ERR_HTTP_GOAWAY,ERR_HTTP_CONNECT_ERROR,ERR_HTTP_CONNECTION_RESET,ERR_HTTP_TIMEOUT.
Note: The registry is deliberately small. Most HTTP/2 and HTTP/3 error codes (FLOW_CONTROL_ERROR, FRAME_SIZE_ERROR, ...) are protocol plumbing that applications cannot act on; unrecognized wire codes surface as ERR_HTTP_STREAM_RESET. The taxonomy is designed to be shared with client-side fetch() error reporting, where the same codes describe the same wire events observed from the other end.
11.2. Protocol error objects
A protocol error is a TypeError with:
-
a
codeown property whose value is an error code; and -
optionally, a
detailown property whose value is an object of the form{ protocol, errorCode, errorName }carrying the raw wire-level information (e.g.,{ protocol: "h2", errorCode: 0x07, errorName: "REFUSED_STREAM" }).
The cause property is not used for protocol metadata; it retains its conventional role of chaining an underlying Error.
Note: Constructing errors with a code option depends on the TC39 Error Code proposal, which adds a standardized code property to the Error constructor options. If that proposal does not advance, code remains an own property assigned by the implementation; the observable shape is the same.
11.3. Outbound errors
A handler signals protocol-level errors to the client through three mechanisms, one per phase of the exchange:
-
Before commitment:
deny()resets the stream. Valid only while the exchange is open. See § 7.5 Denial. -
After commitment: if the
ReadableStreamserving as theResponsebody errors, the implementation resets the stream. If the erroring value is a protocol error with a sendable code, that code is mapped to the wire; otherwiseERR_HTTP_INTERNAL_ERRORis used.ERR_HTTP_REQUEST_REJECTEDis not sendable here: its retryability guarantee cannot be honored once bytes have been sent, and the state model keeps it structurally unreachable after commitment. -
Request body refusal: cancelling the request body’s
ReadableStreamsignals that the server does not want the remainder of the upload —STOP_SENDINGon HTTP/3, orRST_STREAMwith NO_ERROR after a complete response on HTTP/2, per RFC 9113 Section 8.1. The response side of the exchange is unaffected. This is the outbound use ofERR_HTTP_REQUEST_BODY_REJECTED.
11.4. Inbound errors
When the client resets the request stream or the connection is lost, the exchange becomes aborted and the event propagates as a protocol error — the exchange’s abort reason — on every surface the handler may be observing:
| Surface | Propagation |
|---|---|
request.signal
| Aborted, with the protocol error as the abort reason |
Reading request.body
| The read rejects with the protocol error |
A streaming Response body
| The ReadableStream is cancelled with the protocol error as the reason
|
closed / closed
| Rejects with the protocol error on abnormal termination |
Pending accept() or upgrade promises
| Reject with the protocol error |
The code distinguishes situations a handler may reasonably treat differently: ERR_HTTP_REQUEST_CANCELLED (the client deliberately cancelled), ERR_HTTP_CONNECTION_RESET (the connection died), ERR_HTTP_GOAWAY (the exchange could not complete before an orderly connection wind-down). Inbound wire codes with no specific mapping surface as ERR_HTTP_STREAM_RESET.
12. Handler model
An application provides one or two handler functions: fetch() for standard request/response exchanges, and optionally connect() for tunnel protocols.
12.1. The FetchHandler
The fetch() handler receives a ServerContext and returns a Response, undefined, or a Promise resolving to one.
Returning undefined is valid only after calling deny(), or when the exchange was aborted. Any other undefined — or any return value that is not a Response — is a programming error: the implementation invokes the error() handler (§ 12.3 The ErrorHandler) with a TypeError describing the invalid return, falling back to 500 Internal Server Error.
async fetch( ctx) { const { request} = ctx; const url= new URL( request. url); // Deny under load if ( atCapacity) { ctx. deny(); return ; } if ( url. pathname=== '/api/data' ) { return Response. json({ ok: true }); } return new Response( "Not Found" , { status: 404 }); }
12.2. The ConnectHandler
The connect() handler receives a ConnectContext and:
-
Returns
undefinedif the tunnel has been accepted, denied viadeny(), or fallen through via the fall-through sentinel. -
Returns a
Responseto deny the request at the application level. -
Throws or returns a rejected promise, causing a 502 Bad Gateway response.
async connect( ctx) { switch ( ctx. connectProtocol) { case 'websocket' : { const ws= ctx. upgradeWebSocket(); ws. addEventListener( 'message' , e=> ws. send( `Echo: ${ e. data} ` )); return ; } case 'webtransport' : { const session= await ctx. upgradeWebTransport(); handleWebTransport( session); return ; } case 'connect-udp' : { const tunnel= await ctx. accept(); const dg= tunnel. datagrams(); pipeUdpPayloads( dg); return ; } default : return new Response( null , { status: 501 }); } }
If no connect() handler is provided, the implementation responds to CONNECT requests with 501 Not Implemented.
12.3. The ErrorHandler
The optional error() handler customizes the response when another handler faults. It receives the same context object the faulting handler received and the thrown value, and may:
-
return a
Response(or aPromiseresolving to one), which is sent; -
call
deny()(valid only while the exchange is open) and returnundefined; or -
return
undefined, falling through to the default: 500 Internal Server Error forfetch(), 502 Bad Gateway forconnect().
export default { [ Symbol. for ( 'server.protocol' )] : 1 , async fetch( ctx) { return handle( ctx. request); }, error( ctx, err) { if ( err. code=== 'ERR_AT_CAPACITY' ) { ctx. deny(); // REFUSED_STREAM — client may retry elsewhere return ; } return new Response( 'Internal Error' , { status: 500 }); }, };
error() is invoked only while the exchange is response-capable: a fetch() or connect() handler threw, returned a rejected promise, or returned an invalid value before committing. It is not invoked for:
-
faults after commitment — the response or tunnel is aborted instead (§ 11.3 Outbound errors);
-
exchanges already denied or aborted — there is nothing to respond to;
-
rejected
waitUntil()promises — those are reported by the implementation; -
faults in
error()itself — there is no re-entry; the default response is sent.
12.4. Error handling
| Condition | Behavior |
|---|---|
Handler calls deny(), returns undefined
| Stream reset (per error code) |
Handler calls deny() with ERR_HTTP_GOAWAY
| Stream reset + GOAWAY on the connection |
Handler resolves with a non-undefined value after deny()
| Invalid return: reported; nothing is sent; error() not invoked
|
connect() calls deny() with the fall-through sentinel, settles with undefined
| fetch() is invoked for the same exchange
|
connect() rejects or resolves with a non-undefined value after the fall-through sentinel
| error() is invoked; fallback 502; fetch() not invoked
|
fetch() returns a Response
| That response is sent |
fetch() throws or rejects while response-capable
| error() is invoked; fallback 500
|
fetch() returns an invalid value
| error() is invoked with a TypeError; fallback 500
|
fetch() throws or rejects after commitment
| Response stream aborted; ERR_HTTP_INTERNAL_ERROR
|
connect() returns a Response before accepting
| That response is sent |
connect() returns undefined after accept or deny
| Normal completion |
connect() throws or rejects while response-capable
| error() is invoked; fallback 502
|
connect() throws or rejects after accepting
| Tunnel aborted; ERR_HTTP_CONNECT_ERROR
|
error() throws, rejects, or returns an invalid value
| Default 500/502; no re-entry |
| Exchange is aborted (client gone) | Handler outcome discarded; error() not invoked
|
waitUntil() promise rejects
| Reported by the implementation; response unaffected |
12.5. The handler object
Handlers are provided as an object with fetch and/or connect methods:
const handler= { [ Symbol. for ( 'server.protocol' )] : 1 , fetch( ctx) { return new Response( "Hello" ); }, connect( ctx) { // ... }, }; serve( handler, { port: 8080 });
When the handler is an object with methods, this inside the handler refers to the handler object. This allows the handler object to carry application state:
const app= { [ Symbol. for ( 'server.protocol' )] : 1 , db: createPool( process. env. DATABASE_URL), async fetch( ctx) { const rows= await this . db. query( 'SELECT * FROM users' ); return Response. json( rows); }, async [ Symbol. asyncDispose]() { await this . db. end(); }, }; serve( app, { port: 443 , tls: { cert, key} });
12.6. Declarative export and protocol identification
As an alternative to the imperative serve() call, an application may export a default handler object. Because existing runtimes already use export default { fetch() {} } with different handler signatures (e.g., Cloudflare Workers passes (Request, Env, ExecutionContext), Deno passes (Request, ServeHandlerInfo)), a handler object includes a protocol marker so the runtime can distinguish this API from legacy invocation conventions.
The marker is a Symbol.for('server.protocol') property with an integer version value:
export default { [ Symbol. for ( 'server.protocol' )] : 1 , async fetch( ctx) { return new Response( "Hello" ); }, async connect( ctx) { if ( ctx. connectProtocol=== 'websocket' ) { const ws= ctx. upgradeWebSocket(); ws. addEventListener( 'message' , e=> ws. send( e. data)); return ; } return new Response( null , { status: 501 }); }, };
A conforming runtime that supports declarative export checks for the presence and value of Symbol.for('server.protocol') on the default export before invoking handler methods:
-
If
Symbol.for('server.protocol')is present and its value is1, the runtime invokesfetch()with aServerContextandconnect()with aConnectContext. -
If
Symbol.for('server.protocol')is absent, the runtime invokes handler methods using its existing (legacy) calling convention. Existing application code continues to work without modification. -
If
Symbol.for('server.protocol')is present but its value is not a recognized version, the runtime rejects the handler with a descriptive error.
The version number allows the protocol to evolve. This specification defines version 1. Future revisions that change handler signatures would increment the version.
The handler object is the portable unit. The same handler object works with both patterns:
const handler= { [ Symbol. for ( 'server.protocol' )] : 1 , fetch( ctx) { return new Response( "Hello" ); }, }; // In Workers or similar edge runtime: export default handler; // In Node.js, Deno, or Bun: serve( handler, { port: 443 , tls: { cert, key} });
13. Server configuration
13.1. The serve() function
serve(handler, options) function creates a Server. If hostname and port (or signal) are provided in the options, the server begins listening immediately. Otherwise, the server is created in an unbound state and must be explicitly started with listen().
// One-step: create and listen const server= serve({ [ Symbol. for ( 'server.protocol' )] : 1 , fetch( ctx) { return new Response( "Hello" ); }, }, { port: 443 , hostname: '0.0.0.0' , tls: { cert: readFileSync( 'cert.pem' ), key: readFileSync( 'key.pem' ), }, quic: true , }); // Two-step: create then listen const server= serve({ [ Symbol. for ( 'server.protocol' )] : 1 , fetch( ctx) { return new Response( "Hello" ); }, }, { tls: { cert, key}, }); await server. listen({ port: 443 , hostname: '0.0.0.0' });
13.2. ServerOptions
When port is 0, the operating system assigns an available port. When hostname is omitted but port is provided, the default bind address is "0.0.0.0".
The autoContinue member controls automatic handling of Expect: 100-continue requests; see § 7.4.1 100 Continue. The configured value is exposed to handlers via autoContinue.
13.3. TLS configuration
When tls is provided, the server listens for TLS connections and negotiates ALPN. The default ALPN list is ["h2", "http/1.1"].
13.3.1. SNI-based certificate selection
The sni option enables serving multiple hostnames with different certificates on the same listener. It can be either an object mapping hostnames to certificates or a callback function.
Object form — when the set of hostnames is known up front:
serve( handler, { port: 443 , tls: { cert: defaultCert, key: defaultKey, sni: { 'example.com' : { cert: exampleCert, key: exampleKey}, '*.example.com' : { cert: wildcardCert, key: wildcardKey}, 'other.net' : { cert: otherCert, key: otherKey}, }, }, });
Keys support leading wildcard labels following RFC 6125 Section 6.4.3 matching rules.
Callback form — for dynamic selection (ACME, vault lookup, etc.):
serve( handler, { port: 443 , tls: { cert: defaultCert, key: defaultKey, async sni( hostname) { const record= await certStore. lookup( hostname); if ( record) return { cert: record. cert, key: record. key}; return null ; // fall through to default }, }, });
13.4. QUIC configuration
When quic is true or a QUICOptions object, the server additionally listens for QUIC connections on the same port and supports HTTP/3.
HTTP/3 requires TLS. If quic is enabled and tls is not provided, the implementation throws a TypeError. If quic is enabled and the implementation does not support QUIC, it throws a "NotSupportedError" DOMException (see § 2 Conformance).
14. Server and Listener
Server and Listener share the Closeable interface for lifecycle management. On a Listener, these apply to a single binding. On a Server, they apply to all listeners collectively.
14.1. Binding and listeners
listen(options) method binds the server to a network address and returns a Promise<Listener> that resolves when the binding is established.
listen() may be called multiple times to bind the server to multiple addresses or ports:
const server= serve( handler); const http= await server. listen({ port: 80 }); const https= await server. listen({ port: 443 , tls: { cert, key} }); const quic= await server. listen({ port: 443 , tls: { cert, key}, quic: true });
Server is iterable over its active listeners:
for ( const listenerof server) { console. log( listener. address); }
14.1.1. One-step vs. two-step vs. multi-listener
// One-step: serve() with port (single listener) const server= serve( handler, { port: 8080 }); // Two-step: serve() then listen() (single listener) const server= serve( handler); await server. listen({ port: 8080 }); // Multi-listener: HTTP + HTTPS + HTTP/3 const server= serve( handler); await server. listen({ port: 80 }); await server. listen({ port: 443 , tls: { cert, key, sni} }); await server. listen({ port: 443 , tls: { cert, key}, quic: true });
14.2. The Closeable interface
The Closeable mixin provides a uniform lifecycle interface shared by Server and Listener.
14.2.1. busy
busy is true, new requests are not dispatched to handlers. In-flight requests continue to be processed.
Note: busy is intended for brief back-pressure scenarios such as waiting for a downstream dependency to recover, performing a configuration reload, or coordinating with a load balancer during a rolling deploy. For permanent shutdown, use close().
14.2.2. close()
close() method initiates graceful shutdown.
On a Listener, it stops accepting new connections, sends GOAWAY frames on HTTP/2 and HTTP/3 connections, allows in-flight requests to complete, and resolves when all connections are closed.
On a Server, it calls close() on every active listener and waits for all waitUntil() promises to settle.
Closing a listener does not close the server. Other listeners remain active.
14.2.3. destroy()
destroy(error) method immediately terminates without draining.
| Behavior | close()
| destroy()
|
|---|---|---|
| New connections | Refused | Refused |
| GOAWAY sent | Yes | No |
| In-flight requests | Allowed to complete | Aborted immediately |
waitUntil() promises
| Allowed to settle | Ignored |
closed promise
| Resolves | Rejects (if error) |
| Returns | Promise (async) | undefined (sync) |
14.2.4. closed
closed attribute returns a promise that resolves when the server or listener is fully closed.
14.2.5. Symbol.asyncDispose
Both Server and Listener implement Symbol.asyncDispose, which calls close() and waits for the closed promise.
{ await using server= serve( handler, { port: 8080 }); // Server is running } // Server has been gracefully closed
14.3. Signal-based termination
If an AbortSignal is provided in ServerOptions, aborting the signal triggers abrupt termination (equivalent to calling destroy() with the signal’s reason).
const ac= new AbortController(); const server= serve( handler, { port: 8080 , signal: ac. signal}); // Later: ac. abort( new Error ( 'shutting down' )); // Triggers destroy() await server. closed. catch (() => {});
For graceful close, call close() directly rather than using the signal.
15. HTTP version negotiation
15.1. Transparent protocol handling
A conforming implementation routes requests to the appropriate handler regardless of HTTP version. The fetch() handler receives all non-CONNECT requests. The connect() handler receives all CONNECT and extended CONNECT requests. The handler does not select which HTTP version to handle.
15.2. Feature availability by protocol version
| Feature | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Trailers (request) | Chunked TE only | Yes | Yes |
| Trailers (response) | Chunked TE only | Yes | Yes |
| Informational responses | Yes | Yes | Yes |
| Extended CONNECT | No | Yes | Yes |
| WebSocket | Via Upgrade | Via ext. CONNECT | Via ext. CONNECT |
| WebTransport | No | Capsule fallback | Native QUIC |
| HTTP Datagrams (unreliable) | No | No | Yes (QUIC DG) |
| HTTP Datagrams (reliable) | No | Yes (capsule) | Yes (capsule) |
| CONNECT-UDP | No | Capsule fallback | Native QUIC DG |
| CONNECT-IP | No | Capsule fallback | Native QUIC DG |
| Full-duplex streaming | No | Yes | Yes |
16. Security considerations
-
No CORS Enforcement: This specification is for server-side runtimes. CORS is a browser security mechanism. Server-side implementations do not enforce CORS restrictions.
-
Header Filtering: Implementations should apply appropriate header filtering. Hop-by-hop headers are processed by the implementation and generally not exposed in
Requestheaders. Trailer field filtering per RFC 9110 Section 6.5.2 should remove fields not appropriate as trailers. -
Tunnel Security: The
connect()handler establishes tunnels that can carry arbitrary traffic. Applications must perform their own authorization before callingaccept(),upgradeWebSocket(), orupgradeWebTransport(). -
Denial of Service: WebTransport sessions can open many streams; implementations should impose limits.
waitUntil()extends request lifetime; implementations should impose timeouts. The Capsule Protocol and datagrams can generate high throughput; implementations should apply flow control.
17. Open issues
17.1. Module specifier
The import path for serve() is left implementation-defined. Possible values include:
-
node:http(extending the existing Node.js module) -
node:serve(new Node.js module) -
http(generic, non-Node.js-specific)
A standardized module specifier may be desirable if this API is adopted by WinterTC.
17.2. Structured fields on Headers
RFC 9651 defines typed values (integers, booleans, tokens, byte sequences, etc.) for HTTP fields. The Headers interface exposes only raw strings. Adding structured field parsing to Headers (e.g., a getStructured() method) would benefit many use cases beyond priority.
This is a potential change to the Fetch Standard’s Headers type and is out of scope for this specification, but would complement it.
17.3. Full-duplex streaming
HTTP/2 and HTTP/3 support full-duplex streaming: the request body and response body are independent streams that can be read/written concurrently with independent half-close.
The Fetch Standard’s duplex property currently allows only "half" for requests.
For server-side handlers, full-duplex is implicit: the handler can begin writing a response (via a ReadableStream body) while the request body is still being received.
17.4. Typed stream resets and error codes
This specification defines the server-side half of typed error signaling: the error code registry, outbound signaling via deny() and stream aborts, and inbound propagation as protocol errors (§ 11 Errors).
The client-side half remains open. When a fetch() call observes a stream reset, the Fetch Standard currently collapses it into a bare TypeError. Surfacing the same error codes there — on the rejection, and on errors of the response body stream — requires changes to the Fetch Standard’s network error concept, and benefits from the TC39 Error Code proposal for a standardized code constructor option. The registry in § 11.1 Error codes is designed to be shared by both sides.
17.5. ServerResponse
This specification currently requires no server-specific response type. Handlers return a standard Response with trailer support provided by the Fetch Standard.
If future server-specific response capabilities are identified (e.g., server push, priority signaling, response-side lifecycle), a ServerResponse type may be introduced. The handler model is designed to accommodate this: the fetch() handler’s return type can be extended to include ServerResponse without breaking existing handlers that return Response.
18. Addendum: Raw TCP sockets
The handler object pattern is designed to be extensible. New handler methods can be added without changing existing signatures. This addendum sketches how the model extends to support raw TCP ingress — connections that are not HTTP.
18.1. Motivation
Some server-side runtimes (e.g., Cloudflare Workers) support arbitrary TCP ingress where non-HTTP connections are routed to the application. These connections carry raw bytes — no HTTP framing, no request method, no headers. They need a different handler and a different context.
18.2. SocketContext
A SocketContext provides a WinterTC Socket and connection metadata. It is not related to ServerContext by inheritance — there is no HTTP Request, no sendInformational(), no deny().
[Exposed=*]interface { [SocketContext SameObject ]readonly attribute object ;socket readonly attribute SocketAddress ;remoteAddress readonly attribute DOMString ;alpnProtocol readonly attribute DOMString ?;serverName undefined (waitUntil Promise <any >); };promise
-
socket— aSocketwithreadableandwritablestreams. Already connected; TLS (if any) is complete. -
remoteAddress— the remote peer’sSocketAddress. -
alpnProtocol— ALPN protocol from TLS negotiation, or empty string. Can be any application-defined identifier, not just HTTP versions. -
serverName— SNI hostname from TLS ClientHello, ornull. Useful for multi-tenant routing. -
waitUntil()— lifecycle extension, same aswaitUntil().
18.3. SocketHandler
callback = (SocketHandler undefined or Promise <undefined >) (SocketContext );ctx
A socket() method is added to the handler object:
export default { [ Symbol. for ( 'server.protocol' )] : 1 , fetch( ctx) { /* HTTP request/response */ }, connect( ctx) { /* HTTP tunnels */ }, socket( ctx) { /* Raw TCP connections */ }, };
18.4. Routing
How the implementation distinguishes HTTP from non-HTTP connections is implementation-defined. Common approaches include port-based routing, ALPN-based routing (a non-HTTP ALPN token), or protocol detection (inspecting the first bytes of the connection).
If no socket() handler is provided and a non-HTTP connection arrives, the implementation closes the connection. If the handler throws, the implementation closes the socket.
Index
Terms defined by this specification
- aborted, in § 6
- abort reason, in § 6
- accept(), in § 8.3
- accept(init), in § 8.3
-
address
- attribute for Listener, in § 5.14
- dict-member for SocketAddress, in § 5.1
- alpn, in § 5.4
-
alpnProtocol
- attribute for ServerContext, in § 7.2
- attribute for SocketContext, in § 18.2
-
autoContinue
- attribute for ServerContext, in § 7.4.1
- dict-member for ServerOptions, in § 5.4
- busy, in § 14.2.1
- Capsule, in § 5.9
- capsules(), in § 9.2
- CapsuleStream, in § 5.8
- cert, in § 5.4
- clientPriority, in § 7.3.1
-
close()
- method for Closeable, in § 14.2.2
- method for Tunnel, in § 9.4
- method for WebTransportSession, in § 5.11
- Closeable, in § 5.13
- close(closeInfo), in § 5.11
- closeCode, in § 5.4
-
closed
- attribute for Closeable, in § 14.2.4
- attribute for Tunnel, in § 9.4
- attribute for WebTransportSession, in § 5.11
- committed, in § 6
- connect, in § 5.4
- ConnectContext, in § 5.6
- ConnectHandler, in § 5.3
- connect protocol, in § 4.7
- connectProtocol, in § 8.1
- createBidirectionalStream(), in § 5.11
- createUnidirectionalStream(), in § 5.11
- data, in § 5.9
- datagrams, in § 5.11
- datagrams(), in § 9.3
- DatagramStream, in § 5.10
- denied, in § 6
- deny(), in § 7.5
- deny(error), in § 7.5
- destroy(), in § 14.2.3
- destroy(error), in § 14.2.3
- encrypted, in § 7.2
- ERR_HTTP_CONNECT_ERROR, in § 11.1
- ERR_HTTP_CONNECTION_RESET, in § 11.1
- ERR_HTTP_GOAWAY, in § 11.1
- ERR_HTTP_INTERNAL_ERROR, in § 11.1
- ERR_HTTP_PROTOCOL_ERROR, in § 11.1
- ERR_HTTP_REQUEST_BODY_REJECTED, in § 11.1
- ERR_HTTP_REQUEST_CANCELLED, in § 11.1
- ERR_HTTP_REQUEST_REJECTED, in § 11.1
- ERR_HTTP_STREAM_RESET, in § 11.1
- ERR_HTTP_TIMEOUT, in § 11.1
- error, in § 5.4
- error code, in § 11.1
- ErrorHandler, in § 5.3
- exchange, in § 4.5
- fall-through sentinel, in § 8.7
- family, in § 5.1
- fetch, in § 5.4
- FetchHandler, in § 5.3
- FetchServer, in § 5.16
- Fetch Server API, in § 4.1
- handler, in § 4.4
- HandlerObject, in § 5.4
-
hostname
- dict-member for ListenOptions, in § 5.4
- dict-member for ServerOptions, in § 5.4
- incomingBidirectionalStreams, in § 5.11
- incomingUnidirectionalStreams, in § 5.11
- incremental, in § 5.2
- informational, in § 6
- key, in § 5.4
- listen(), in § 14.1
- Listener, in § 5.14
- listen(options), in § 14.1
- ListenOptions, in § 5.4
- localAddress, in § 7.2
- onpriority, in § 7.3.2
- open, in § 6
- pending 100 obligation, in § 7.4.1
-
port
- dict-member for ListenOptions, in § 5.4
- dict-member for ServerOptions, in § 5.4
- dict-member for SocketAddress, in § 5.1
- PriorityCallback, in § 5.3
- protocol, in § 5.4
- protocol error, in § 11.2
- protocol marker, in § 4.8
-
quic
- dict-member for ListenOptions, in § 5.4
- dict-member for ServerOptions, in § 5.4
- QUICOptions, in § 5.4
-
readable
- attribute for CapsuleStream, in § 5.8
- attribute for DatagramStream, in § 5.10
- attribute for Tunnel, in § 5.7
- attribute for WebTransportBidirectionalStream, in § 5.12
- ready, in § 5.11
- reason, in § 5.4
-
remoteAddress
- attribute for ServerContext, in § 7.2
- attribute for SocketContext, in § 18.2
- request, in § 7.1
- RequestPriority, in § 5.2
- response-capable, in § 6
- sendInformational(status), in § 7.4
- sendInformational(status, headers), in § 7.4
- serve(handler), in § 13.1
- serve(handler, options), in § 13.1
- Server, in § 5.15
- server, in § 4.3
- ServerContext, in § 5.5
-
serverName
- attribute for ServerContext, in § 7.2
- attribute for SocketContext, in § 18.2
- ServerOptions, in § 5.4
- serverPriority, in § 7.3.3
- signal, in § 5.4
- sni, in § 5.4
- SNICallback, in § 5.4
- socket, in § 18.2
- SocketAddress, in § 5.1
- SocketContext, in § 18.2
- SocketHandler, in § 18.3
-
tls
- dict-member for ListenOptions, in § 5.4
- dict-member for ServerOptions, in § 5.4
- TLSCertificate, in § 5.4
- TLSOptions, in § 5.4
- transport, in § 5.11
- Tunnel, in § 5.7
- tunnel, in § 4.6
- type, in § 5.9
- unreliable, in § 5.10
- upgrade-originated, in § 8.2
- upgradeWebSocket(), in § 8.4
- upgradeWebSocket(options), in § 8.4
- upgradeWebTransport(), in § 8.5
- urgency, in § 5.2
-
waitUntil(promise)
- method for ServerContext, in § 7.6
- method for SocketContext, in § 18.2
- web-interoperable runtime, in § 4.2
- WebSocketUpgradeInit, in § 5.4
- WebTransportBidirectionalStream, in § 5.12
- WebTransportCloseInfo, in § 5.4
- WebTransportSession, in § 5.11
-
writable
- attribute for CapsuleStream, in § 5.8
- attribute for DatagramStream, in § 5.10
- attribute for Tunnel, in § 5.7
- attribute for WebTransportBidirectionalStream, in § 5.12
Terms defined by reference
-
[DOM] defines the following terms:
- AbortSignal
-
[FETCH] defines the following terms:
- Headers
- HeadersInit
- Request
- Response
- ResponseInit
-
[STREAMS] defines the following terms:
- ReadableStream
- WritableStream
-
[WEBIDL] defines the following terms:
- BufferSource
- DOMException
- DOMString
- Promise
- RangeError
- SameObject
- TypeError
- USVString
- Uint8Array
- any
- boolean
- iterable
- object
- record
- sequence
- undefined
- unsigned long
- unsigned long long
- unsigned short
IDL Index
dictionary {SocketAddress DOMString ;address unsigned short ;port DOMString ; };family dictionary {RequestPriority unsigned short = 3;urgency boolean =incremental false ; };callback =PriorityCallback undefined (optional RequestPriority = {});priority callback =FetchHandler any (ServerContext );ctx callback =ConnectHandler any (ConnectContext );ctx callback =ErrorHandler any (ServerContext ,ctx any );error dictionary {WebSocketUpgradeInit sequence <DOMString >; };protocol dictionary {WebTransportCloseInfo unsigned long = 0;closeCode USVString = ""; };reason dictionary { (TLSCertificate DOMString or BufferSource ); (cert DOMString or BufferSource ); };key callback =SNICallback any (DOMString );hostname dictionary :TLSOptions TLSCertificate {sequence <DOMString >; (alpn SNICallback or record <DOMString ,TLSCertificate >); };sni dictionary { };QUICOptions dictionary {ServerOptions unsigned short ;port DOMString ;hostname TLSOptions ; (tls boolean or QUICOptions )=quic false ;boolean =autoContinue true ;AbortSignal ; };signal dictionary {ListenOptions unsigned short = 0;port DOMString = "0.0.0.0";hostname TLSOptions ; (tls boolean or QUICOptions ); };quic dictionary {HandlerObject required FetchHandler ;fetch ConnectHandler ;connect ErrorHandler ; }; [Exposed=*]error interface { [ServerContext SameObject ]readonly attribute Request request ;readonly attribute SocketAddress remoteAddress ;readonly attribute SocketAddress localAddress ;readonly attribute DOMString alpnProtocol ;readonly attribute boolean encrypted ;readonly attribute DOMString ?serverName ;readonly attribute RequestPriority clientPriority ;attribute RequestPriority ?serverPriority ;attribute PriorityCallback ?onpriority ;readonly attribute boolean autoContinue ;undefined sendInformational (unsigned short ,status optional HeadersInit );headers undefined deny (optional any );error undefined waitUntil (Promise <any >); }; [Exposed=*]promise interface :ConnectContext ServerContext {readonly attribute DOMString ?connectProtocol ;Promise <Tunnel >accept (optional ResponseInit = {});init object upgradeWebSocket (optional WebSocketUpgradeInit = {});options Promise <WebTransportSession >upgradeWebTransport (); }; [Exposed=*]interface {Tunnel readonly attribute ReadableStream ;readable readonly attribute WritableStream ;writable CapsuleStream capsules ();DatagramStream datagrams ();undefined close ();readonly attribute Promise <undefined >closed ; }; [Exposed=*]interface {CapsuleStream readonly attribute ReadableStream ;readable readonly attribute WritableStream ; };writable dictionary {Capsule unsigned long long ;type Uint8Array ; }; [Exposed=*]data interface {DatagramStream readonly attribute ReadableStream ;readable readonly attribute WritableStream ;writable readonly attribute boolean ; }; [Exposed=*]unreliable interface {WebTransportSession readonly attribute ReadableStream ;incomingBidirectionalStreams readonly attribute ReadableStream ;incomingUnidirectionalStreams Promise <WebTransportBidirectionalStream >();createBidirectionalStream Promise <WritableStream >();createUnidirectionalStream readonly attribute DatagramStream ;datagrams readonly attribute DOMString ;transport undefined (close optional WebTransportCloseInfo = {});closeInfo readonly attribute Promise <WebTransportCloseInfo >;closed readonly attribute Promise <undefined >; }; [Exposed=*]ready interface {WebTransportBidirectionalStream readonly attribute ReadableStream ;readable readonly attribute WritableStream ; };writable interface mixin {Closeable attribute boolean busy ;Promise <undefined >close ();undefined destroy (optional any );error readonly attribute Promise <undefined >closed ; }; [Exposed=*]interface {Listener readonly attribute SocketAddress ; };address Listener includes Closeable ; [Exposed=*]interface {Server Promise <Listener >listen (optional ListenOptions = {});options iterable <Listener >; };Server includes Closeable ; [Exposed=*]namespace {FetchServer Server serve (HandlerObject ,handler optional ServerOptions = {}); }; [Exposed=*]options interface { [SocketContext SameObject ]readonly attribute object ;socket readonly attribute SocketAddress ;remoteAddress readonly attribute DOMString ;alpnProtocol readonly attribute DOMString ?;serverName undefined (waitUntil Promise <any >); };promise callback = (SocketHandler undefined or Promise <undefined >) (SocketContext );ctx
Copyright & Software License
Ecma International
Rue du Rhone 114
CH-1204 Geneva
Tel: +41 22 849 6000
Fax: +41 22 849 6001
Web: https://ecma-international.org/
Copyright Notice
© 2026 Ecma International
This draft document may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published, and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this section are included on all such copies and derivative works. However, this document itself may not be modified in any way, including by removing the copyright notice or references to Ecma International, except as needed for the purpose of developing any document or deliverable produced by Ecma International.
This disclaimer is valid only prior to final version of this document. After approval all rights on the standard are reserved by Ecma International.
The limited permissions are granted through the standardization phase and will not be revoked by Ecma International or its successors or assigns during this time.
This document and the information contained herein is provided on an "AS IS" basis and ECMA INTERNATIONAL DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY OWNERSHIP RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Software License
All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.