@@ -28,3 +28,4 @@ import { FetchHandler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerOptions, ServerRequest } from "../types.mjs"; | ||
| statusText: string; | ||
| headers: [string, string][]; | ||
| /** Flat rawHeaders-style list: `[name1, value1, name2, value2, …]` */ | ||
| headers: string[]; | ||
| body: PreparedNodeResponseBody; | ||
@@ -43,2 +44,8 @@ } | ||
| type NodeResponse = InstanceType<typeof NodeResponse>; | ||
| /** | ||
| * Sends a web `Response` to a Node.js `ServerResponse`. | ||
| * | ||
| * The returned promise resolves once the response has been fully sent | ||
| * (kept for `toNodeHandler` consumers that await completion). | ||
| */ | ||
| declare function sendNodeResponse(nodeRes: NodeServerResponse, webRes: Response | NodeResponse): Promise<void>; | ||
@@ -45,0 +52,0 @@ /** |
+96
-22
@@ -10,6 +10,27 @@ import { FastURL, lazyInherit } from "../_chunks/_url.mjs"; | ||
| import nodeHTTP2 from "node:http2"; | ||
| async function sendNodeResponse(nodeRes, webRes) { | ||
| function sendNodeResponse(nodeRes, webRes) { | ||
| try { | ||
| return _sendNodeResponse(nodeRes, webRes, false) || Promise.resolve(); | ||
| } catch (error) { | ||
| return Promise.reject(error); | ||
| } | ||
| } | ||
| function sendNodeResponseDetached(nodeRes, webRes) { | ||
| try { | ||
| return _sendNodeResponse(nodeRes, webRes, true); | ||
| } catch (error) { | ||
| handleSendError(nodeRes, error); | ||
| } | ||
| } | ||
| function handleSendError(nodeRes, _error) { | ||
| if (nodeRes.headersSent) nodeRes.destroy(); | ||
| else { | ||
| nodeRes.statusCode = 500; | ||
| nodeRes.end(); | ||
| } | ||
| } | ||
| function _sendNodeResponse(nodeRes, webRes, detached) { | ||
| if (!webRes) { | ||
| nodeRes.statusCode = 500; | ||
| return endNodeResponse(nodeRes); | ||
| return endNodeResponse(nodeRes, detached); | ||
| } | ||
@@ -26,14 +47,18 @@ if (webRes._toNodeResponse) { | ||
| } else writeHead(nodeRes, res.status, res.statusText, res.headers); | ||
| return endNodeResponse(nodeRes); | ||
| return endNodeResponse(nodeRes, detached); | ||
| } | ||
| const rawHeaders = [...webRes.headers]; | ||
| const rawHeaders = []; | ||
| for (const [key, value] of webRes.headers) rawHeaders.push(key, value); | ||
| writeHead(nodeRes, webRes.status, webRes.statusText, rawHeaders); | ||
| return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes); | ||
| return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes, detached); | ||
| } | ||
| function writeHead(nodeRes, status, statusText, rawHeaders) { | ||
| const writeHeaders = rawHeaders.flat(); | ||
| if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, writeHeaders); | ||
| else nodeRes.writeHead(status, statusText, writeHeaders); | ||
| if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, rawHeaders); | ||
| else nodeRes.writeHead(status, statusText, rawHeaders); | ||
| } | ||
| function endNodeResponse(nodeRes) { | ||
| function endNodeResponse(nodeRes, detached) { | ||
| if (detached) { | ||
| nodeRes.end(); | ||
| return; | ||
| } | ||
| return new Promise((resolve) => nodeRes.end(resolve)); | ||
@@ -137,2 +162,34 @@ } | ||
| }; | ||
| const _nonJoinedHeaders = /* @__PURE__ */ new Set([ | ||
| "age", | ||
| "authorization", | ||
| "content-length", | ||
| "content-type", | ||
| "etag", | ||
| "expires", | ||
| "from", | ||
| "host", | ||
| "if-modified-since", | ||
| "if-unmodified-since", | ||
| "last-modified", | ||
| "location", | ||
| "max-forwards", | ||
| "proxy-authorization", | ||
| "referer", | ||
| "retry-after", | ||
| "server", | ||
| "user-agent" | ||
| ]); | ||
| const _validHeaderNameRE = /^[!#$%&'*+\-.^_`|~\dA-Za-z]+$/; | ||
| function _isRepeated(rawHeaders, lowerName) { | ||
| let seen = false; | ||
| for (let i = 0; i < rawHeaders.length; i += 2) { | ||
| const key = rawHeaders[i]; | ||
| if (key.length === lowerName.length && key.toLowerCase() === lowerName) { | ||
| if (seen) return true; | ||
| seen = true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| const NodeRequestHeaders = /* @__PURE__ */ (() => { | ||
@@ -165,9 +222,21 @@ const NativeHeaders = globalThis.Headers; | ||
| get(name) { | ||
| return this._headers.get(name); | ||
| if (this.#headers) return this.#headers.get(name); | ||
| const lower = name.toLowerCase(); | ||
| if (lower.charCodeAt(0) === 58) return this._headers.get(name); | ||
| const value = this.#req.headers[lower]; | ||
| if (typeof value === "string") return _nonJoinedHeaders.has(lower) && _isRepeated(this.#req.rawHeaders, lower) ? this._headers.get(name) : value; | ||
| if (Array.isArray(value)) return value.join(", "); | ||
| return lower !== "__proto__" && _validHeaderNameRE.test(name) ? null : this._headers.get(name); | ||
| } | ||
| has(name) { | ||
| return this._headers.has(name); | ||
| if (this.#headers) return this.#headers.has(name); | ||
| const lower = name.toLowerCase(); | ||
| if (lower.charCodeAt(0) === 58) return this._headers.has(name); | ||
| if (Object.hasOwn(this.#req.headers, lower)) return true; | ||
| return lower !== "__proto__" && _validHeaderNameRE.test(name) ? false : this._headers.has(name); | ||
| } | ||
| getSetCookie() { | ||
| return this._headers.getSetCookie(); | ||
| if (this.#headers) return this.#headers.getSetCookie(); | ||
| const value = this.#req.headers["set-cookie"]; | ||
| return Array.isArray(value) ? value.slice() : value ? [value] : []; | ||
| } | ||
@@ -191,2 +260,3 @@ entries() { | ||
| runtime; | ||
| waitUntil; | ||
| #req; | ||
@@ -261,10 +331,14 @@ #url; | ||
| } | ||
| #readBuffered() { | ||
| return readBody(this.#req, this.#maxRequestBodySize); | ||
| } | ||
| text() { | ||
| if (this.#request) return this.#request.text(); | ||
| if (this.#bodyStream !== void 0) return this.#bodyStream ? new Response(this.#bodyStream).text() : Promise.resolve(""); | ||
| return readBody(this.#req, this.#maxRequestBodySize).then((buf) => buf.toString()); | ||
| return this.#readBuffered().then((buf) => buf.toString()); | ||
| } | ||
| json() { | ||
| if (this.#request) return this.#request.json(); | ||
| return this.text().then((text) => JSON.parse(text)); | ||
| if (this.#bodyStream !== void 0) return this.text().then((text) => JSON.parse(text)); | ||
| return this.#readBuffered().then((buf) => JSON.parse(buf.toString())); | ||
| } | ||
@@ -338,3 +412,3 @@ get _request() { | ||
| cleanup(); | ||
| resolve(Buffer.concat(chunks)); | ||
| resolve(chunks.length === 1 ? chunks[0] : Buffer.concat(chunks)); | ||
| }; | ||
@@ -429,14 +503,14 @@ req.on("data", onData).once("end", onEnd).once("error", onError); | ||
| const initHeaders = this.#init?.headers; | ||
| const headerEntries = this.#response?.headers || this.#headers || (initHeaders ? Array.isArray(initHeaders) ? initHeaders : initHeaders?.entries ? initHeaders.entries() : Object.entries(initHeaders).map(([k, v]) => [k.toLowerCase(), v]) : void 0); | ||
| const headerEntries = this.#response?.headers || this.#headers || (initHeaders ? Array.isArray(initHeaders) ? initHeaders : initHeaders?.entries ? initHeaders.entries() : Object.entries(initHeaders) : void 0); | ||
| let hasContentTypeHeader; | ||
| let hasContentLength; | ||
| if (headerEntries) for (const [key, value] of headerEntries) { | ||
| if (Array.isArray(value)) for (const v of value) headers.push([key, v]); | ||
| else headers.push([key, value]); | ||
| const lowerKey = typeof key === "string" ? key.toLowerCase() : key; | ||
| const lowerKey = typeof key === "string" ? key.toLowerCase() : String(key); | ||
| if (Array.isArray(value)) for (const v of value) headers.push(lowerKey, v); | ||
| else headers.push(lowerKey, value); | ||
| if (lowerKey === "content-type") hasContentTypeHeader = true; | ||
| else if (lowerKey === "content-length") hasContentLength = true; | ||
| } | ||
| if (contentType && !hasContentTypeHeader) headers.push(["content-type", contentType]); | ||
| if (contentLength && !hasContentLength) headers.push(["content-length", String(contentLength)]); | ||
| if (contentType && !hasContentTypeHeader) headers.push("content-type", contentType); | ||
| if (contentLength && !hasContentLength) headers.push("content-length", String(contentLength)); | ||
| this.#init = void 0; | ||
@@ -817,3 +891,3 @@ this.#headers = void 0; | ||
| const res = fetchHandler(request); | ||
| return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res); | ||
| return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponseDetached(nodeRes, resolvedRes)) : sendNodeResponseDetached(nodeRes, res); | ||
| }; | ||
@@ -820,0 +894,0 @@ this.node = { |
+1
-1
@@ -182,3 +182,3 @@ import { bold, cyan, gray, green, magenta, red, url, yellow } from "./_chunks/_utils.mjs"; | ||
| name: "srvx", | ||
| version: "0.11.18", | ||
| version: "0.11.19", | ||
| description: "Universal Server." | ||
@@ -185,0 +185,0 @@ }; |
+1
-1
| { | ||
| "name": "srvx", | ||
| "version": "0.11.19", | ||
| "version": "0.11.20", | ||
| "description": "Universal Server.", | ||
@@ -5,0 +5,0 @@ "homepage": "https://srvx.h3.dev", |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
120211
2.23%2671
2.85%