@panmdaa/server runs over Node's http2 module when ServerOptions.http2 is set, with enableConnectProtocol: true so extended CONNECT (WebSocket over HTTP/2) works.
new Server({ http2: true }) // h2c
new Server({ http2: true, tls }) // h2 over TLS, allowHTTP1 for ALPN fallbackThe HTTP/2 servers are created without a request handler (server/utils.ts:19-20) so Server intercepts every stream.
Server.handleStream (server.ts:68-78):
WebSocketUpgrader.handleStream(stream, headers)is tried first — if the stream is an extended CONNECT (:method: CONNECT,:protocol: websocket), it's consumed by the WS stack andhandleStreamreturnstrue.- Otherwise, a regular HTTP/2 request is synthesized and the normal request path runs.
Node's native HTTP/2 request/response conversion would also consume extended CONNECT streams, so the server hand-builds its own (ws/http2/http2.ts):
isExtendedConnectWebSocketRequest(headers)(:23-33)::method === CONNECT&&:protocol === websocket.createHttp2StreamRequest(:41-75): a minimalServerRequest— headers (host from:authority),httpVersion: "2.0", method/url from pseudo-headers,socketfromstream.session.socket, event methods delegating to the stream.Http2StreamResponse extends Writable(:82-168): defersstream.respond()until the first_write/_final/writeHead(:78-81), mirroringServerResponsesemantics. Headers are stored lowercased in aMapand emitted as:status+ entries on respond.
Because Http2StreamResponse behaves like a ServerResponse, the whole HTTP pipeline (context, auto-finish, error path) works unchanged.
acceptExtendedConnectWebSocket(ws/handshake/handshake.ts:235-301): validates:method: CONNECT(405 otherwise),:protocol: websocket(400), version 13, runs session checks, thenstream.respond({ ":status": 200, ... }).- Response headers are sanitized: lowercased, forbidden header set dropped (
HTTP2_FORBIDDEN_RESPONSE_HEADERS,handshake.ts:21-27). rejectExtendedConnect(:97-120) responds with an error status and ends/closes the stream.