977 lines
31 KiB
JavaScript
977 lines
31 KiB
JavaScript
|
|
import { FastURL, lazyInherit } from "../_chunks/_url.mjs";
|
||
|
|
import { createWaitUntil, fmtURL, printListening, resolvePortAndHost, resolveTLSOptions } from "../_chunks/_utils2.mjs";
|
||
|
|
import { errorPlugin, gracefulShutdownPlugin, wrapFetch } from "../_chunks/_plugins.mjs";
|
||
|
|
import { HOST_RE, firstForwardedValue, isTrustedProxy } from "../_chunks/_trust-proxy.mjs";
|
||
|
|
import { createBodyTooLargeError, limitBodyStream } from "../_chunks/_body-limit.mjs";
|
||
|
|
import nodeHTTP, { IncomingMessage, ServerResponse } from "node:http";
|
||
|
|
import { Duplex, PassThrough, Readable, addAbortSignal } from "node:stream";
|
||
|
|
import { pipeline } from "node:stream/promises";
|
||
|
|
import nodeHTTPS from "node:https";
|
||
|
|
import nodeHTTP2 from "node:http2";
|
||
|
|
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, detached);
|
||
|
|
}
|
||
|
|
if (webRes._toNodeResponse) {
|
||
|
|
const res = webRes._toNodeResponse();
|
||
|
|
if (res.body) {
|
||
|
|
if (res.body instanceof ReadableStream) {
|
||
|
|
writeHead(nodeRes, res.status, res.statusText, res.headers);
|
||
|
|
return streamBody(res.body, nodeRes);
|
||
|
|
} else if (typeof res.body?.pipe === "function") return pipeBody(res.body, nodeRes, res.status, res.statusText, res.headers);
|
||
|
|
writeHead(nodeRes, res.status, res.statusText, res.headers);
|
||
|
|
nodeRes.write(res.body);
|
||
|
|
} else writeHead(nodeRes, res.status, res.statusText, res.headers);
|
||
|
|
return endNodeResponse(nodeRes, detached);
|
||
|
|
}
|
||
|
|
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, detached);
|
||
|
|
}
|
||
|
|
function writeHead(nodeRes, status, statusText, rawHeaders) {
|
||
|
|
if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, rawHeaders);
|
||
|
|
else nodeRes.writeHead(status, statusText, rawHeaders);
|
||
|
|
}
|
||
|
|
function endNodeResponse(nodeRes, detached) {
|
||
|
|
if (detached) {
|
||
|
|
nodeRes.end();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
return new Promise((resolve) => nodeRes.end(resolve));
|
||
|
|
}
|
||
|
|
function pipeBody(stream, nodeRes, status, statusText, headers) {
|
||
|
|
if (nodeRes.destroyed) {
|
||
|
|
stream.destroy?.();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (typeof stream.on !== "function" || typeof stream.destroy !== "function") {
|
||
|
|
writeHead(nodeRes, status, statusText, headers);
|
||
|
|
stream.pipe(nodeRes);
|
||
|
|
return new Promise((resolve) => nodeRes.on("close", resolve));
|
||
|
|
}
|
||
|
|
if (stream.destroyed) {
|
||
|
|
writeHead(nodeRes, 500, "Internal Server Error", []);
|
||
|
|
return endNodeResponse(nodeRes);
|
||
|
|
}
|
||
|
|
return new Promise((resolve) => {
|
||
|
|
function onEarlyError() {
|
||
|
|
stream.off("readable", onReadable);
|
||
|
|
stream.destroy();
|
||
|
|
writeHead(nodeRes, 500, "Internal Server Error", []);
|
||
|
|
endNodeResponse(nodeRes).then(resolve);
|
||
|
|
}
|
||
|
|
function onReadable() {
|
||
|
|
stream.off("error", onEarlyError);
|
||
|
|
if (nodeRes.destroyed) {
|
||
|
|
stream.destroy();
|
||
|
|
return resolve();
|
||
|
|
}
|
||
|
|
writeHead(nodeRes, status, statusText, headers);
|
||
|
|
pipeline(stream, nodeRes).catch(() => {}).then(() => resolve());
|
||
|
|
}
|
||
|
|
stream.once("error", onEarlyError);
|
||
|
|
stream.once("readable", onReadable);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function streamBody(stream, nodeRes) {
|
||
|
|
if (nodeRes.destroyed) {
|
||
|
|
stream.cancel();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const reader = stream.getReader();
|
||
|
|
function streamCancel(error) {
|
||
|
|
reader.cancel(error).catch(() => {});
|
||
|
|
if (error) nodeRes.destroy(error);
|
||
|
|
}
|
||
|
|
function streamHandle({ done, value }) {
|
||
|
|
try {
|
||
|
|
if (done) nodeRes.end();
|
||
|
|
else if (nodeRes.write(value)) reader.read().then(streamHandle, streamCancel);
|
||
|
|
else nodeRes.once("drain", () => reader.read().then(streamHandle, streamCancel));
|
||
|
|
} catch (error) {
|
||
|
|
streamCancel(error instanceof Error ? error : void 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
nodeRes.on("close", streamCancel);
|
||
|
|
nodeRes.on("error", streamCancel);
|
||
|
|
reader.read().then(streamHandle, streamCancel);
|
||
|
|
return reader.closed.catch(streamCancel).finally(() => {
|
||
|
|
nodeRes.off("close", streamCancel);
|
||
|
|
nodeRes.off("error", streamCancel);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
var NodeRequestURL = class extends FastURL {
|
||
|
|
constructor({ req, trusted = false }) {
|
||
|
|
const path = req.url || "/";
|
||
|
|
const forwardedHost = trusted ? firstForwardedValue(req.headers["x-forwarded-host"]) : void 0;
|
||
|
|
let host = (forwardedHost && HOST_RE.test(forwardedHost) ? forwardedHost : void 0) || req.headers.host || req.headers[":authority"];
|
||
|
|
if (host && !HOST_RE.test(host)) host = "_invalid_";
|
||
|
|
else if (!host) if (req.socket) host = `${req.socket.localFamily === "IPv6" ? "[" + req.socket.localAddress + "]" : req.socket.localAddress}:${req.socket?.localPort || "80"}`;
|
||
|
|
else host = "localhost";
|
||
|
|
const forwardedProto = trusted ? firstForwardedValue(req.headers["x-forwarded-proto"]) : void 0;
|
||
|
|
const protocol = req.socket?.encrypted || forwardedProto === "https" || trusted && req.headers[":scheme"] === "https" ? "https:" : "http:";
|
||
|
|
if (path[0] === "/") {
|
||
|
|
const qIndex = path.indexOf("?");
|
||
|
|
super({
|
||
|
|
protocol,
|
||
|
|
host,
|
||
|
|
pathname: qIndex === -1 ? path : path.slice(0, qIndex) || "/",
|
||
|
|
search: qIndex === -1 ? "" : path.slice(qIndex) || ""
|
||
|
|
});
|
||
|
|
} else if (path === "*") super({
|
||
|
|
protocol,
|
||
|
|
host,
|
||
|
|
pathname: "/*",
|
||
|
|
search: ""
|
||
|
|
});
|
||
|
|
else super(path);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
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__ */ (() => {
|
||
|
|
const NativeHeaders = globalThis.Headers;
|
||
|
|
class Headers {
|
||
|
|
#req;
|
||
|
|
#headers;
|
||
|
|
constructor(req) {
|
||
|
|
this.#req = req;
|
||
|
|
}
|
||
|
|
static [Symbol.hasInstance](val) {
|
||
|
|
return val instanceof NativeHeaders;
|
||
|
|
}
|
||
|
|
get _headers() {
|
||
|
|
if (!this.#headers) {
|
||
|
|
const headers = new NativeHeaders();
|
||
|
|
const rawHeaders = this.#req.rawHeaders;
|
||
|
|
const len = rawHeaders.length;
|
||
|
|
for (let i = 0; i < len; i += 2) {
|
||
|
|
const key = rawHeaders[i];
|
||
|
|
if (key.charCodeAt(0) === 58) continue;
|
||
|
|
const value = rawHeaders[i + 1];
|
||
|
|
headers.append(key, value);
|
||
|
|
}
|
||
|
|
this.#headers = headers;
|
||
|
|
}
|
||
|
|
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) {
|
||
|
|
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() {
|
||
|
|
if (this.#headers) return this.#headers.getSetCookie();
|
||
|
|
const value = this.#req.headers["set-cookie"];
|
||
|
|
return Array.isArray(value) ? value.slice() : value ? [value] : [];
|
||
|
|
}
|
||
|
|
entries() {
|
||
|
|
return this._headers.entries();
|
||
|
|
}
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this.entries();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
lazyInherit(Headers.prototype, NativeHeaders.prototype, "_headers");
|
||
|
|
Object.setPrototypeOf(Headers, NativeHeaders);
|
||
|
|
Object.setPrototypeOf(Headers.prototype, NativeHeaders.prototype);
|
||
|
|
return Headers;
|
||
|
|
})();
|
||
|
|
const kNativeRequest = /* @__PURE__ */ Symbol.for("srvx.nativeRequest");
|
||
|
|
const NodeRequest = /* @__PURE__ */ (() => {
|
||
|
|
const NativeRequest = getNativeRequest();
|
||
|
|
class Request {
|
||
|
|
runtime;
|
||
|
|
waitUntil;
|
||
|
|
#req;
|
||
|
|
#url;
|
||
|
|
#bodyStream;
|
||
|
|
#request;
|
||
|
|
#headers;
|
||
|
|
#abortController;
|
||
|
|
#maxRequestBodySize;
|
||
|
|
#trustProxy;
|
||
|
|
#ip;
|
||
|
|
#ipResolved = false;
|
||
|
|
#remoteAddress;
|
||
|
|
#trusted;
|
||
|
|
constructor(ctx) {
|
||
|
|
this.#req = ctx.req;
|
||
|
|
this.#maxRequestBodySize = ctx.maxRequestBodySize;
|
||
|
|
this.#trustProxy = ctx.trustProxy;
|
||
|
|
this.runtime = {
|
||
|
|
name: "node",
|
||
|
|
node: ctx
|
||
|
|
};
|
||
|
|
}
|
||
|
|
static [Symbol.hasInstance](val) {
|
||
|
|
return val instanceof NativeRequest;
|
||
|
|
}
|
||
|
|
#resolveTrusted() {
|
||
|
|
if (this.#trusted === void 0) {
|
||
|
|
this.#remoteAddress = this.#req.socket?.remoteAddress;
|
||
|
|
this.#trusted = isTrustedProxy(this.#trustProxy, this.#remoteAddress);
|
||
|
|
}
|
||
|
|
return this.#trusted;
|
||
|
|
}
|
||
|
|
get ip() {
|
||
|
|
if (this.#ipResolved) return this.#ip;
|
||
|
|
this.#ipResolved = true;
|
||
|
|
if (this.#resolveTrusted()) {
|
||
|
|
const forwarded = firstForwardedValue(this.#req.headers["x-forwarded-for"]);
|
||
|
|
if (forwarded) return this.#ip = forwarded;
|
||
|
|
}
|
||
|
|
return this.#ip = this.#remoteAddress;
|
||
|
|
}
|
||
|
|
get method() {
|
||
|
|
if (this.#request) return this.#request.method;
|
||
|
|
return this.#req.method || "GET";
|
||
|
|
}
|
||
|
|
get _url() {
|
||
|
|
return this.#url ||= new NodeRequestURL({
|
||
|
|
req: this.#req,
|
||
|
|
trusted: this.#resolveTrusted()
|
||
|
|
});
|
||
|
|
}
|
||
|
|
set _url(url) {
|
||
|
|
this.#url = url;
|
||
|
|
}
|
||
|
|
get url() {
|
||
|
|
if (this.#request) return this.#request.url;
|
||
|
|
return this._url.href;
|
||
|
|
}
|
||
|
|
get headers() {
|
||
|
|
if (this.#request) return this.#request.headers;
|
||
|
|
return this.#headers ||= new NodeRequestHeaders(this.#req);
|
||
|
|
}
|
||
|
|
get _abortController() {
|
||
|
|
if (!this.#abortController) {
|
||
|
|
this.#abortController = new AbortController();
|
||
|
|
const { req, res } = this.runtime.node;
|
||
|
|
const abortController = this.#abortController;
|
||
|
|
const abort = (err) => abortController.abort?.(err);
|
||
|
|
if (res) res.once("close", () => {
|
||
|
|
const reqError = req.errored;
|
||
|
|
if (reqError) abort(reqError);
|
||
|
|
else if (!res.writableEnded) abort();
|
||
|
|
});
|
||
|
|
else req.once("close", () => {
|
||
|
|
if (!req.complete) abort();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return this.#abortController;
|
||
|
|
}
|
||
|
|
get signal() {
|
||
|
|
return this.#request ? this.#request.signal : this._abortController.signal;
|
||
|
|
}
|
||
|
|
get body() {
|
||
|
|
if (this.#request) return this.#request.body;
|
||
|
|
if (this.#bodyStream === void 0) {
|
||
|
|
const method = this.method;
|
||
|
|
let stream = !(method === "GET" || method === "HEAD") ? Readable.toWeb(this.#req) : null;
|
||
|
|
if (stream && this.#maxRequestBodySize !== void 0) stream = limitBodyStream(stream, this.#maxRequestBodySize);
|
||
|
|
this.#bodyStream = stream;
|
||
|
|
}
|
||
|
|
return this.#bodyStream;
|
||
|
|
}
|
||
|
|
#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 this.#readBuffered().then((buf) => buf.toString());
|
||
|
|
}
|
||
|
|
json() {
|
||
|
|
if (this.#request) return this.#request.json();
|
||
|
|
if (this.#bodyStream !== void 0) return this.text().then((text) => JSON.parse(text));
|
||
|
|
return this.#readBuffered().then((buf) => JSON.parse(buf.toString()));
|
||
|
|
}
|
||
|
|
get _request() {
|
||
|
|
if (!this.#request) {
|
||
|
|
const body = this.body;
|
||
|
|
this.#request = new NativeRequest(this.url, {
|
||
|
|
method: this.method,
|
||
|
|
headers: this.headers,
|
||
|
|
signal: this._abortController.signal,
|
||
|
|
body,
|
||
|
|
duplex: body ? "half" : void 0
|
||
|
|
});
|
||
|
|
this.#headers = void 0;
|
||
|
|
this.#bodyStream = void 0;
|
||
|
|
}
|
||
|
|
return this.#request;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
lazyInherit(Request.prototype, NativeRequest.prototype, "_request");
|
||
|
|
Object.setPrototypeOf(Request.prototype, NativeRequest.prototype);
|
||
|
|
return Request;
|
||
|
|
})();
|
||
|
|
function patchGlobalRequest() {
|
||
|
|
const NativeRequest = getNativeRequest();
|
||
|
|
const PatchedRequest = class Request extends NativeRequest {
|
||
|
|
static _srvx = true;
|
||
|
|
static [Symbol.hasInstance](instance) {
|
||
|
|
if (this === PatchedRequest) return instance instanceof NativeRequest;
|
||
|
|
else return Object.prototype.isPrototypeOf.call(this.prototype, instance);
|
||
|
|
}
|
||
|
|
constructor(input, options) {
|
||
|
|
if (typeof input === "object" && "_request" in input) input = input._request;
|
||
|
|
super(input, options);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
if (!globalThis.Request._srvx) globalThis.Request = PatchedRequest;
|
||
|
|
return PatchedRequest;
|
||
|
|
}
|
||
|
|
function readBody(req, maxRequestBodySize) {
|
||
|
|
if ("rawBody" in req && Buffer.isBuffer(req.rawBody)) {
|
||
|
|
if (maxRequestBodySize !== void 0 && req.rawBody.length > maxRequestBodySize) return Promise.reject(createBodyTooLargeError(maxRequestBodySize));
|
||
|
|
return Promise.resolve(req.rawBody);
|
||
|
|
}
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const chunks = [];
|
||
|
|
let size = 0;
|
||
|
|
const cleanup = () => {
|
||
|
|
req.off("data", onData);
|
||
|
|
req.off("end", onEnd);
|
||
|
|
req.off("error", onError);
|
||
|
|
};
|
||
|
|
const onData = (chunk) => {
|
||
|
|
if (maxRequestBodySize !== void 0) {
|
||
|
|
size += chunk.length;
|
||
|
|
if (size > maxRequestBodySize) {
|
||
|
|
cleanup();
|
||
|
|
req.pause?.();
|
||
|
|
reject(createBodyTooLargeError(maxRequestBodySize));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
chunks.push(chunk);
|
||
|
|
};
|
||
|
|
const onError = (err) => {
|
||
|
|
cleanup();
|
||
|
|
reject(err);
|
||
|
|
};
|
||
|
|
const onEnd = () => {
|
||
|
|
cleanup();
|
||
|
|
resolve(chunks.length === 1 ? chunks[0] : Buffer.concat(chunks));
|
||
|
|
};
|
||
|
|
req.on("data", onData).once("end", onEnd).once("error", onError);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function getNativeRequest() {
|
||
|
|
let R = globalThis[kNativeRequest] || globalThis.Request;
|
||
|
|
while (R?._srvx) R = Object.getPrototypeOf(R);
|
||
|
|
return globalThis[kNativeRequest] ??= R;
|
||
|
|
}
|
||
|
|
const NodeResponse = /* @__PURE__ */ (() => {
|
||
|
|
const NativeResponse = globalThis.Response;
|
||
|
|
const STATUS_CODES = globalThis.process?.getBuiltinModule?.("node:http")?.STATUS_CODES || {};
|
||
|
|
class NodeResponse {
|
||
|
|
#body;
|
||
|
|
#init;
|
||
|
|
#headers;
|
||
|
|
#response;
|
||
|
|
constructor(body, init) {
|
||
|
|
this.#body = body;
|
||
|
|
this.#init = init;
|
||
|
|
}
|
||
|
|
static [Symbol.hasInstance](val) {
|
||
|
|
return val instanceof NativeResponse;
|
||
|
|
}
|
||
|
|
get status() {
|
||
|
|
return this.#response?.status || this.#init?.status || 200;
|
||
|
|
}
|
||
|
|
get statusText() {
|
||
|
|
return this.#response?.statusText || this.#init?.statusText || STATUS_CODES[this.status] || "";
|
||
|
|
}
|
||
|
|
get headers() {
|
||
|
|
if (this.#response) return this.#response.headers;
|
||
|
|
if (this.#headers) return this.#headers;
|
||
|
|
const initHeaders = this.#init?.headers;
|
||
|
|
return this.#headers = initHeaders instanceof Headers ? initHeaders : new Headers(initHeaders);
|
||
|
|
}
|
||
|
|
get ok() {
|
||
|
|
if (this.#response) return this.#response.ok;
|
||
|
|
const status = this.status;
|
||
|
|
return status >= 200 && status < 300;
|
||
|
|
}
|
||
|
|
get _response() {
|
||
|
|
if (this.#response) return this.#response;
|
||
|
|
let body = this.#body;
|
||
|
|
if (body && typeof body.pipe === "function" && !(body instanceof Readable)) {
|
||
|
|
const stream = new PassThrough();
|
||
|
|
body.pipe(stream);
|
||
|
|
const abort = body.abort;
|
||
|
|
if (abort) stream.once("close", () => abort());
|
||
|
|
body = stream;
|
||
|
|
}
|
||
|
|
this.#response = new NativeResponse(body, this.#headers ? {
|
||
|
|
...this.#init,
|
||
|
|
headers: this.#headers
|
||
|
|
} : this.#init);
|
||
|
|
this.#init = void 0;
|
||
|
|
this.#headers = void 0;
|
||
|
|
this.#body = void 0;
|
||
|
|
return this.#response;
|
||
|
|
}
|
||
|
|
_toNodeResponse() {
|
||
|
|
const status = this.status;
|
||
|
|
const statusText = this.statusText;
|
||
|
|
let body;
|
||
|
|
let contentType;
|
||
|
|
let contentLength;
|
||
|
|
if (this.#response) body = this.#response.body;
|
||
|
|
else if (this.#body) if (this.#body instanceof ReadableStream) body = this.#body;
|
||
|
|
else if (typeof this.#body === "string") {
|
||
|
|
body = this.#body;
|
||
|
|
contentType = "text/plain; charset=UTF-8";
|
||
|
|
contentLength = Buffer.byteLength(this.#body);
|
||
|
|
} else if (this.#body instanceof ArrayBuffer) {
|
||
|
|
body = Buffer.from(this.#body);
|
||
|
|
contentLength = this.#body.byteLength;
|
||
|
|
} else if (this.#body instanceof Uint8Array) {
|
||
|
|
body = this.#body;
|
||
|
|
contentLength = this.#body.byteLength;
|
||
|
|
} else if (this.#body instanceof DataView) {
|
||
|
|
body = Buffer.from(this.#body.buffer);
|
||
|
|
contentLength = this.#body.byteLength;
|
||
|
|
} else if (this.#body instanceof Blob) {
|
||
|
|
body = this.#body.stream();
|
||
|
|
contentType = this.#body.type;
|
||
|
|
contentLength = this.#body.size;
|
||
|
|
} else if (typeof this.#body.pipe === "function") body = this.#body;
|
||
|
|
else body = this._response.body;
|
||
|
|
const headers = [];
|
||
|
|
const initHeaders = this.#init?.headers;
|
||
|
|
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) {
|
||
|
|
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));
|
||
|
|
this.#init = void 0;
|
||
|
|
this.#headers = void 0;
|
||
|
|
this.#response = void 0;
|
||
|
|
this.#body = void 0;
|
||
|
|
return {
|
||
|
|
status,
|
||
|
|
statusText,
|
||
|
|
headers,
|
||
|
|
body
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
lazyInherit(NodeResponse.prototype, NativeResponse.prototype, "_response");
|
||
|
|
Object.setPrototypeOf(NodeResponse, NativeResponse);
|
||
|
|
Object.setPrototypeOf(NodeResponse.prototype, NativeResponse.prototype);
|
||
|
|
return NodeResponse;
|
||
|
|
})();
|
||
|
|
function prematureCloseError() {
|
||
|
|
return Object.assign(/* @__PURE__ */ new Error("Connection closed before response was finished"), { code: "ERR_STREAM_PREMATURE_CLOSE" });
|
||
|
|
}
|
||
|
|
var WebRequestSocket = class extends Duplex {
|
||
|
|
_httpMessage;
|
||
|
|
autoSelectFamilyAttemptedAddresses = [];
|
||
|
|
bufferSize = 0;
|
||
|
|
bytesRead = 0;
|
||
|
|
bytesWritten = 0;
|
||
|
|
connecting = false;
|
||
|
|
pending = false;
|
||
|
|
readyState = "open";
|
||
|
|
remoteAddress = "";
|
||
|
|
remoteFamily = "";
|
||
|
|
remotePort = 0;
|
||
|
|
#request;
|
||
|
|
#timeoutTimer;
|
||
|
|
#reqReader;
|
||
|
|
#headersWritten;
|
||
|
|
#_writeBody;
|
||
|
|
#resBodyController;
|
||
|
|
#resBodyClosed;
|
||
|
|
_webResBody;
|
||
|
|
#tos = 0;
|
||
|
|
constructor(request) {
|
||
|
|
super({ allowHalfOpen: true });
|
||
|
|
this.#request = request;
|
||
|
|
this._webResBody = new ReadableStream({ start: (controller) => {
|
||
|
|
this.#resBodyController = controller;
|
||
|
|
this.#_writeBody = controller.enqueue.bind(controller);
|
||
|
|
this.once("finish", () => {
|
||
|
|
this.readyState = "closed";
|
||
|
|
this.#resBodyClosed = true;
|
||
|
|
controller.close();
|
||
|
|
});
|
||
|
|
} });
|
||
|
|
addAbortSignal(request.signal, this);
|
||
|
|
}
|
||
|
|
setTimeout(ms, cb) {
|
||
|
|
if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) return this;
|
||
|
|
if (cb) this.on("timeout", cb);
|
||
|
|
if (this.#timeoutTimer) clearTimeout(this.#timeoutTimer);
|
||
|
|
if (ms > 0) this.#timeoutTimer = setTimeout(() => this.emit("timeout"), ms);
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
setNoDelay() {
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
setKeepAlive() {
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
ref() {
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
unref() {
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
destroySoon() {
|
||
|
|
this.destroy();
|
||
|
|
}
|
||
|
|
connect() {
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
resetAndDestroy() {
|
||
|
|
this.destroy();
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
address() {
|
||
|
|
return {
|
||
|
|
address: "",
|
||
|
|
family: "",
|
||
|
|
port: 0
|
||
|
|
};
|
||
|
|
}
|
||
|
|
bodyReader() {
|
||
|
|
try {
|
||
|
|
return this.#request.body?.getReader();
|
||
|
|
} catch (error) {
|
||
|
|
this.emit("error", error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
getTypeOfService() {
|
||
|
|
return this.#tos;
|
||
|
|
}
|
||
|
|
setTypeOfService(tos) {
|
||
|
|
this.#tos = tos;
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
_read(_size) {
|
||
|
|
const reader = this.#reqReader ??= this.bodyReader();
|
||
|
|
if (!reader) {
|
||
|
|
this.push(null);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
reader.read().then((res) => this._onRead(res)).catch((error) => {
|
||
|
|
this.emit("error", error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
_onRead(res) {
|
||
|
|
if (res.done) {
|
||
|
|
this.push(null);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (res.value) {
|
||
|
|
this.bytesRead += res.value.byteLength;
|
||
|
|
this.push(res.value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_write(chunk, encoding, callback) {
|
||
|
|
if (this.#headersWritten) this.#_writeBody(typeof chunk === "string" ? Buffer.from(chunk, encoding) : chunk);
|
||
|
|
else if (chunk?.length > 0) {
|
||
|
|
this.#headersWritten = true;
|
||
|
|
const headerEnd = chunk.lastIndexOf("\r\n\r\n");
|
||
|
|
if (headerEnd === -1) throw new Error("Invalid HTTP headers chunk!");
|
||
|
|
if (headerEnd < chunk.length - 4) {
|
||
|
|
this._write(chunk.slice(headerEnd + 4), encoding, () => {
|
||
|
|
callback(null);
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
callback(null);
|
||
|
|
}
|
||
|
|
_final(callback) {
|
||
|
|
callback(null);
|
||
|
|
}
|
||
|
|
_destroy(err, cb) {
|
||
|
|
if (this.#timeoutTimer) clearTimeout(this.#timeoutTimer);
|
||
|
|
if (this.#reqReader) this.#reqReader.cancel().catch((error) => {
|
||
|
|
console.error(error);
|
||
|
|
});
|
||
|
|
if (!this.#resBodyClosed) {
|
||
|
|
this.#resBodyClosed = true;
|
||
|
|
try {
|
||
|
|
this.#resBodyController?.error(err ?? prematureCloseError());
|
||
|
|
} catch {}
|
||
|
|
}
|
||
|
|
this.readyState = "closed";
|
||
|
|
cb(err ?? void 0);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var WebIncomingMessage = class extends IncomingMessage {
|
||
|
|
constructor(req, socket) {
|
||
|
|
super(socket);
|
||
|
|
this.method = req.method;
|
||
|
|
const url = req._url ??= new FastURL(req.url);
|
||
|
|
this.url = url.pathname + url.search;
|
||
|
|
for (const [key, value] of req.headers.entries()) this.headers[key.toLowerCase()] = value;
|
||
|
|
if (req.method !== "GET" && req.method !== "HEAD" && !this.headers["content-length"] && !this.headers["transfer-encoding"]) this.headers["transfer-encoding"] = "chunked";
|
||
|
|
const onData = (chunk) => {
|
||
|
|
this.push(chunk);
|
||
|
|
};
|
||
|
|
socket.on("data", onData);
|
||
|
|
socket.once("end", () => {
|
||
|
|
this.emit("end");
|
||
|
|
this.off("data", onData);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
function callNodeHandler(handler, req) {
|
||
|
|
const isMiddleware = handler.length > 2;
|
||
|
|
const nodeCtx = req.runtime?.node;
|
||
|
|
if (!nodeCtx || !nodeCtx.req || !nodeCtx.res) throw new Error("Node.js runtime context is not available.");
|
||
|
|
const { req: nodeReq, res: nodeRes } = nodeCtx;
|
||
|
|
let _headers;
|
||
|
|
const webRes = new NodeResponse(void 0, {
|
||
|
|
get status() {
|
||
|
|
return nodeRes.statusCode;
|
||
|
|
},
|
||
|
|
get statusText() {
|
||
|
|
return nodeRes.statusMessage;
|
||
|
|
},
|
||
|
|
get headers() {
|
||
|
|
if (!_headers) {
|
||
|
|
const headerEntries = [];
|
||
|
|
const rawHeaders = nodeRes.getHeaders();
|
||
|
|
for (const [name, value] of Object.entries(rawHeaders)) if (Array.isArray(value)) for (const v of value) headerEntries.push([name, v]);
|
||
|
|
else if (value) headerEntries.push([name, String(value)]);
|
||
|
|
_headers = new Headers(headerEntries);
|
||
|
|
}
|
||
|
|
return _headers;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
nodeRes.once("close", () => resolve(webRes));
|
||
|
|
nodeRes.once("finish", () => resolve(webRes));
|
||
|
|
nodeRes.once("error", (error) => reject(error));
|
||
|
|
let streamPromise;
|
||
|
|
nodeRes.once("pipe", (stream) => {
|
||
|
|
streamPromise = new Promise((resolve) => {
|
||
|
|
stream.once("end", () => resolve(webRes));
|
||
|
|
stream.once("error", (error) => reject(error));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
try {
|
||
|
|
if (isMiddleware) Promise.resolve(handler(nodeReq, nodeRes, (error) => error ? reject(error) : streamPromise || resolve(webRes))).catch((error) => reject(error));
|
||
|
|
else Promise.resolve(handler(nodeReq, nodeRes)).then(() => streamPromise || webRes);
|
||
|
|
} catch (error) {
|
||
|
|
reject(error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
let needDrainSymbol;
|
||
|
|
function getNeedDrainSymbol(res) {
|
||
|
|
if (needDrainSymbol === void 0) needDrainSymbol = Object.getOwnPropertySymbols(res).find((s) => s.description === "kNeedDrain") ?? null;
|
||
|
|
return needDrainSymbol;
|
||
|
|
}
|
||
|
|
var WebServerResponse = class extends ServerResponse {
|
||
|
|
#socket;
|
||
|
|
#socketError;
|
||
|
|
constructor(req, socket) {
|
||
|
|
super(req);
|
||
|
|
this.assignSocket(socket);
|
||
|
|
this.once("finish", () => {
|
||
|
|
socket.end();
|
||
|
|
});
|
||
|
|
this.#socket = socket;
|
||
|
|
socket.once("error", (err) => {
|
||
|
|
this.#socketError ??= err;
|
||
|
|
});
|
||
|
|
socket.on("drain", () => {
|
||
|
|
const kNeedDrain = getNeedDrainSymbol(this);
|
||
|
|
if (kNeedDrain && !this[kNeedDrain]) return;
|
||
|
|
if (this.destroyed || this.writableFinished) return;
|
||
|
|
if (kNeedDrain) this[kNeedDrain] = false;
|
||
|
|
this.emit("drain");
|
||
|
|
});
|
||
|
|
this.waitToFinish = this.waitToFinish.bind(this);
|
||
|
|
this.toWebResponse = this.toWebResponse.bind(this);
|
||
|
|
}
|
||
|
|
waitToFinish() {
|
||
|
|
if (this.writableFinished) return Promise.resolve();
|
||
|
|
if (this.#socketError || this.#socket.destroyed) return Promise.reject(this.#socketError ?? prematureCloseError());
|
||
|
|
if (this.writableEnded) return Promise.resolve();
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const socket = this.#socket;
|
||
|
|
const settle = (err) => {
|
||
|
|
this.removeListener("finish", onFinish);
|
||
|
|
this.removeListener("error", onError);
|
||
|
|
socket.removeListener("error", onError);
|
||
|
|
socket.removeListener("close", onClose);
|
||
|
|
if (err) reject(err);
|
||
|
|
else resolve();
|
||
|
|
};
|
||
|
|
const onFinish = () => settle();
|
||
|
|
const onError = (err) => settle(err);
|
||
|
|
const onClose = () => {
|
||
|
|
if (!this.writableFinished) settle(this.#socketError ?? prematureCloseError());
|
||
|
|
};
|
||
|
|
this.on("finish", onFinish);
|
||
|
|
this.on("error", onError);
|
||
|
|
socket.on("error", onError);
|
||
|
|
socket.on("close", onClose);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
async toWebResponse() {
|
||
|
|
await this.waitToFinish();
|
||
|
|
const headers = [];
|
||
|
|
const httpHeader = this._header?.split("\r\n");
|
||
|
|
for (let i = 1; httpHeader && i < httpHeader.length; i++) {
|
||
|
|
const sepIndex = httpHeader[i].indexOf(": ");
|
||
|
|
if (sepIndex === -1) continue;
|
||
|
|
const key = httpHeader[i].slice(0, Math.max(0, sepIndex));
|
||
|
|
const value = httpHeader[i].slice(Math.max(0, sepIndex + 2));
|
||
|
|
if (!key) continue;
|
||
|
|
headers.push([key, value]);
|
||
|
|
}
|
||
|
|
return new Response(this.#socket._webResBody, {
|
||
|
|
status: this.statusCode,
|
||
|
|
statusText: this.statusMessage,
|
||
|
|
headers
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
async function fetchNodeHandler(handler, req) {
|
||
|
|
const nodeRuntime = req.runtime?.node;
|
||
|
|
if (nodeRuntime && nodeRuntime.req && nodeRuntime.res) return await callNodeHandler(handler, req);
|
||
|
|
const socket = new WebRequestSocket(req);
|
||
|
|
const nodeReq = new WebIncomingMessage(req, socket);
|
||
|
|
const nodeRes = new WebServerResponse(nodeReq, socket);
|
||
|
|
try {
|
||
|
|
await handler(nodeReq, nodeRes);
|
||
|
|
return await nodeRes.toWebResponse();
|
||
|
|
} catch (error) {
|
||
|
|
if (!(req.signal?.aborted || error?.name === "AbortError" || error?.code === "ERR_STREAM_PREMATURE_CLOSE")) console.error(error, { cause: {
|
||
|
|
req,
|
||
|
|
handler
|
||
|
|
} });
|
||
|
|
return new Response(null, {
|
||
|
|
status: 500,
|
||
|
|
statusText: "Internal Server Error"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function toNodeHandler(handler, options) {
|
||
|
|
if (handler.__nodeHandler) return handler.__nodeHandler;
|
||
|
|
function convertedNodeHandler(nodeReq, nodeRes) {
|
||
|
|
const res = handler(new NodeRequest({
|
||
|
|
req: nodeReq,
|
||
|
|
res: nodeRes,
|
||
|
|
maxRequestBodySize: options?.maxRequestBodySize,
|
||
|
|
trustProxy: options?.trustProxy
|
||
|
|
}));
|
||
|
|
return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
|
||
|
|
}
|
||
|
|
convertedNodeHandler.__fetchHandler = handler;
|
||
|
|
assignFnName(convertedNodeHandler, handler, " (converted to Node handler)");
|
||
|
|
return convertedNodeHandler;
|
||
|
|
}
|
||
|
|
function toFetchHandler(handler) {
|
||
|
|
if (handler.__fetchHandler) return handler.__fetchHandler;
|
||
|
|
function convertedNodeHandler(req) {
|
||
|
|
return fetchNodeHandler(handler, req);
|
||
|
|
}
|
||
|
|
convertedNodeHandler.__nodeHandler = handler;
|
||
|
|
assignFnName(convertedNodeHandler, handler, " (converted to Web handler)");
|
||
|
|
return convertedNodeHandler;
|
||
|
|
}
|
||
|
|
function assignFnName(target, source, suffix) {
|
||
|
|
if (source.name) try {
|
||
|
|
Object.defineProperty(target, "name", { value: `${source.name}${suffix}` });
|
||
|
|
} catch {}
|
||
|
|
}
|
||
|
|
function serve(options) {
|
||
|
|
return new NodeServer(options);
|
||
|
|
}
|
||
|
|
var NodeServer = class {
|
||
|
|
runtime = "node";
|
||
|
|
options;
|
||
|
|
node;
|
||
|
|
serveOptions;
|
||
|
|
fetch;
|
||
|
|
waitUntil;
|
||
|
|
#isSecure;
|
||
|
|
#listeningPromise;
|
||
|
|
#listenError;
|
||
|
|
#wait;
|
||
|
|
constructor(options) {
|
||
|
|
this.options = {
|
||
|
|
...options,
|
||
|
|
middleware: [...options.middleware || []]
|
||
|
|
};
|
||
|
|
for (const plugin of options.plugins || []) plugin(this);
|
||
|
|
errorPlugin(this);
|
||
|
|
const fetchHandler = this.fetch = wrapFetch(this);
|
||
|
|
const handler = (nodeReq, nodeRes) => {
|
||
|
|
const reqUrl = nodeReq.url;
|
||
|
|
if (reqUrl && reqUrl[0] !== "/" && reqUrl !== "*" && !URL.canParse(reqUrl)) {
|
||
|
|
nodeRes.statusCode = 400;
|
||
|
|
nodeRes.end();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const request = new NodeRequest({
|
||
|
|
req: nodeReq,
|
||
|
|
res: nodeRes,
|
||
|
|
maxRequestBodySize: this.options.maxRequestBodySize,
|
||
|
|
trustProxy: this.options.trustProxy
|
||
|
|
});
|
||
|
|
request.waitUntil = this.#wait?.waitUntil;
|
||
|
|
const res = fetchHandler(request);
|
||
|
|
return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponseDetached(nodeRes, resolvedRes)) : sendNodeResponseDetached(nodeRes, res);
|
||
|
|
};
|
||
|
|
this.node = {
|
||
|
|
handler,
|
||
|
|
server: void 0
|
||
|
|
};
|
||
|
|
const loader = globalThis.__srvxLoader__;
|
||
|
|
if (loader) {
|
||
|
|
loader({ server: this });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
gracefulShutdownPlugin(this);
|
||
|
|
this.#wait = createWaitUntil();
|
||
|
|
this.waitUntil = this.#wait.waitUntil;
|
||
|
|
const tls = resolveTLSOptions(this.options);
|
||
|
|
const { port, hostname: host } = resolvePortAndHost(this.options);
|
||
|
|
this.serveOptions = {
|
||
|
|
port,
|
||
|
|
host,
|
||
|
|
exclusive: !this.options.reusePort,
|
||
|
|
...tls,
|
||
|
|
...this.options.node
|
||
|
|
};
|
||
|
|
let server;
|
||
|
|
this.#isSecure = !!this.serveOptions.cert && this.options.protocol !== "http";
|
||
|
|
if (this.options.node?.http2 ?? this.#isSecure) if (this.#isSecure) server = nodeHTTP2.createSecureServer({
|
||
|
|
allowHTTP1: true,
|
||
|
|
...this.serveOptions
|
||
|
|
}, handler);
|
||
|
|
else throw new Error("node.http2 option requires tls certificate!");
|
||
|
|
else if (this.#isSecure) server = nodeHTTPS.createServer(this.serveOptions, handler);
|
||
|
|
else server = nodeHTTP.createServer(this.serveOptions, handler);
|
||
|
|
this.node.server = server;
|
||
|
|
if (!options.manual) this.serve().catch(() => {});
|
||
|
|
}
|
||
|
|
serve() {
|
||
|
|
if (this.#listeningPromise) return this.#listeningPromise.then(() => this);
|
||
|
|
const server = this.node?.server;
|
||
|
|
if (!server) return Promise.reject(/* @__PURE__ */ new Error("Server not initialized"));
|
||
|
|
this.#listenError = void 0;
|
||
|
|
this.#listeningPromise = new Promise((resolve, reject) => {
|
||
|
|
const onError = (error) => {
|
||
|
|
server.off("listening", onListening);
|
||
|
|
this.#listenError = error;
|
||
|
|
this.#listeningPromise = void 0;
|
||
|
|
reject(error);
|
||
|
|
};
|
||
|
|
const onListening = () => {
|
||
|
|
server.off("error", onError);
|
||
|
|
printListening(this.options, this.url);
|
||
|
|
resolve();
|
||
|
|
};
|
||
|
|
server.once("error", onError);
|
||
|
|
server.once("listening", onListening);
|
||
|
|
server.listen(this.serveOptions);
|
||
|
|
});
|
||
|
|
return this.#listeningPromise.then(() => this);
|
||
|
|
}
|
||
|
|
get url() {
|
||
|
|
const addr = this.node?.server?.address();
|
||
|
|
if (!addr) return;
|
||
|
|
return typeof addr === "string" ? addr : fmtURL(addr.address, addr.port, this.#isSecure);
|
||
|
|
}
|
||
|
|
ready() {
|
||
|
|
if (this.#listenError) return Promise.reject(this.#listenError);
|
||
|
|
return Promise.resolve(this.#listeningPromise).then(() => this);
|
||
|
|
}
|
||
|
|
async close(closeAll) {
|
||
|
|
await Promise.all([this.#wait?.wait(), new Promise((resolve, reject) => {
|
||
|
|
const server = this.node?.server;
|
||
|
|
if (server && closeAll && "closeAllConnections" in server) server.closeAllConnections();
|
||
|
|
if (!server || !server.listening) return resolve();
|
||
|
|
server.close((error) => error ? reject(error) : resolve());
|
||
|
|
})]);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
export { NodeResponse as FastResponse, FastURL, NodeRequest, NodeResponse, fetchNodeHandler, patchGlobalRequest, sendNodeResponse, serve, toFetchHandler, toNodeHandler };
|