398 lines
No EOL
12 KiB
JavaScript
398 lines
No EOL
12 KiB
JavaScript
import { createWaitUntil, fmtURL, printListening, resolvePortAndHost, resolveTLSOptions } from "../_chunks/_utils-DRF_4b_y.mjs";
|
|
import { wrapFetch } from "../_chunks/_middleware-BvRR7B4M.mjs";
|
|
import { FastURL$1 as FastURL } from "../_chunks/_url-CdE4ce6F.mjs";
|
|
import { NodeRequestHeaders, NodeResponse, NodeResponseHeaders, inheritProps, kNodeInspect } from "../_chunks/response-6LJL3Qlz.mjs";
|
|
import { errorPlugin } from "../_chunks/_plugins-DOhVIkXu.mjs";
|
|
import { splitSetCookieString } from "cookie-es";
|
|
|
|
//#region src/adapters/_node/send.ts
|
|
async function sendNodeResponse(nodeRes, webRes) {
|
|
if (!webRes) {
|
|
nodeRes.statusCode = 500;
|
|
return endNodeResponse(nodeRes);
|
|
}
|
|
if (webRes.nodeResponse) {
|
|
const res = webRes.nodeResponse();
|
|
writeHead(nodeRes, res.status, res.statusText, res.headers.flat());
|
|
if (res.body) {
|
|
if (res.body instanceof ReadableStream) return streamBody(res.body, nodeRes);
|
|
else if (typeof res.body?.pipe === "function") {
|
|
res.body.pipe(nodeRes);
|
|
return new Promise((resolve) => nodeRes.on("close", resolve));
|
|
}
|
|
nodeRes.write(res.body);
|
|
}
|
|
return endNodeResponse(nodeRes);
|
|
}
|
|
const headerEntries = [];
|
|
for (const [key, value] of webRes.headers) if (key === "set-cookie") for (const setCookie of splitSetCookieString(value)) headerEntries.push(["set-cookie", setCookie]);
|
|
else headerEntries.push([key, value]);
|
|
writeHead(nodeRes, webRes.status, webRes.statusText, headerEntries.flat());
|
|
return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes);
|
|
}
|
|
function writeHead(nodeRes, status, statusText, headers) {
|
|
if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, headers.flat());
|
|
else nodeRes.writeHead(status, statusText, headers.flat());
|
|
}
|
|
function endNodeResponse(nodeRes) {
|
|
return new Promise((resolve) => nodeRes.end(resolve));
|
|
}
|
|
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.finally(() => {
|
|
nodeRes.off("close", streamCancel);
|
|
nodeRes.off("error", streamCancel);
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/adapters/_node/url.ts
|
|
const NodeRequestURL = /* @__PURE__ */ (() => {
|
|
const _URL = class URL {
|
|
_node;
|
|
_hash = "";
|
|
_username = "";
|
|
_password = "";
|
|
_protocol;
|
|
_hostname;
|
|
_port;
|
|
_pathname;
|
|
_search;
|
|
_searchParams;
|
|
constructor(nodeCtx) {
|
|
this._node = nodeCtx;
|
|
}
|
|
get hash() {
|
|
return this._hash;
|
|
}
|
|
set hash(value) {
|
|
this._hash = value;
|
|
}
|
|
get username() {
|
|
return this._username;
|
|
}
|
|
set username(value) {
|
|
this._username = value;
|
|
}
|
|
get password() {
|
|
return this._password;
|
|
}
|
|
set password(value) {
|
|
this._password = value;
|
|
}
|
|
get host() {
|
|
return this._node.req.headers.host || this._node.req.headers[":authority"] || "";
|
|
}
|
|
set host(value) {
|
|
this._hostname = void 0;
|
|
this._port = void 0;
|
|
this._node.req.headers.host = value;
|
|
}
|
|
get hostname() {
|
|
if (this._hostname === void 0) {
|
|
const [hostname, port] = parseHost(this._node.req.headers.host);
|
|
if (this._port === void 0 && port) this._port = String(Number.parseInt(port) || "");
|
|
this._hostname = hostname || "localhost";
|
|
}
|
|
return this._hostname;
|
|
}
|
|
set hostname(value) {
|
|
this._hostname = value;
|
|
}
|
|
get port() {
|
|
if (this._port === void 0) {
|
|
const [hostname, port] = parseHost(this._node.req.headers.host);
|
|
if (this._hostname === void 0 && hostname) this._hostname = hostname;
|
|
this._port = port || String(this._node.req.socket?.localPort || "");
|
|
}
|
|
return this._port;
|
|
}
|
|
set port(value) {
|
|
this._port = String(Number.parseInt(value) || "");
|
|
}
|
|
get pathname() {
|
|
if (this._pathname === void 0) {
|
|
const [pathname, search] = parsePath(this._node.req.url || "/");
|
|
this._pathname = pathname;
|
|
if (this._search === void 0) this._search = search;
|
|
}
|
|
return this._pathname;
|
|
}
|
|
set pathname(value) {
|
|
if (value[0] !== "/") value = "/" + value;
|
|
if (value === this._pathname) return;
|
|
this._pathname = value;
|
|
this._node.req.url = value + this.search;
|
|
}
|
|
get search() {
|
|
if (this._search === void 0) {
|
|
const [pathname, search] = parsePath(this._node.req.url || "/");
|
|
this._search = search;
|
|
if (this._pathname === void 0) this._pathname = pathname;
|
|
}
|
|
return this._search;
|
|
}
|
|
set search(value) {
|
|
if (value === "?") value = "";
|
|
else if (value && value[0] !== "?") value = "?" + value;
|
|
if (value === this._search) return;
|
|
this._search = value;
|
|
this._searchParams = void 0;
|
|
this._node.req.url = this.pathname + value;
|
|
}
|
|
get searchParams() {
|
|
if (!this._searchParams) this._searchParams = new URLSearchParams(this.search);
|
|
return this._searchParams;
|
|
}
|
|
set searchParams(value) {
|
|
this._searchParams = value;
|
|
this._search = value.toString();
|
|
}
|
|
get protocol() {
|
|
if (!this._protocol) this._protocol = this._node.req.socket?.encrypted || this._node.req.headers["x-forwarded-proto"] === "https" ? "https:" : "http:";
|
|
return this._protocol;
|
|
}
|
|
set protocol(value) {
|
|
this._protocol = value;
|
|
}
|
|
get origin() {
|
|
return `${this.protocol}//${this.host}`;
|
|
}
|
|
set origin(_value) {}
|
|
get href() {
|
|
return `${this.protocol}//${this.host}${this.pathname}${this.search}`;
|
|
}
|
|
set href(value) {
|
|
const _url = new globalThis.URL(value);
|
|
this._protocol = _url.protocol;
|
|
this.username = _url.username;
|
|
this.password = _url.password;
|
|
this._hostname = _url.hostname;
|
|
this._port = _url.port;
|
|
this.pathname = _url.pathname;
|
|
this.search = _url.search;
|
|
this.hash = _url.hash;
|
|
}
|
|
toString() {
|
|
return this.href;
|
|
}
|
|
toJSON() {
|
|
return this.href;
|
|
}
|
|
get [Symbol.toStringTag]() {
|
|
return "URL";
|
|
}
|
|
[kNodeInspect]() {
|
|
return this.href;
|
|
}
|
|
};
|
|
Object.setPrototypeOf(_URL.prototype, globalThis.URL.prototype);
|
|
return _URL;
|
|
})();
|
|
function parsePath(input) {
|
|
const url = (input || "/").replace(/\\/g, "/");
|
|
const qIndex = url.indexOf("?");
|
|
if (qIndex === -1) return [url, ""];
|
|
return [url.slice(0, qIndex), url.slice(qIndex)];
|
|
}
|
|
function parseHost(host) {
|
|
const s = (host || "").split(":");
|
|
return [s[0], String(Number.parseInt(s[1]) || "")];
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/adapters/_node/request.ts
|
|
const NodeRequest = /* @__PURE__ */ (() => {
|
|
const { Readable } = process.getBuiltinModule("node:stream");
|
|
const NativeRequest = globalThis._Request ??= globalThis.Request;
|
|
const PatchedRequest = class Request extends NativeRequest {
|
|
static _srvx = true;
|
|
constructor(input, options) {
|
|
if (typeof input === "object" && "_request" in input) input = input._request;
|
|
if ((options?.body)?.getReader !== void 0) options.duplex ??= "half";
|
|
super(input, options);
|
|
}
|
|
};
|
|
if (!globalThis.Request._srvx) globalThis.Request = PatchedRequest;
|
|
class NodeRequest$1 {
|
|
_node;
|
|
_url;
|
|
runtime;
|
|
#request;
|
|
#headers;
|
|
#abortSignal;
|
|
constructor(ctx) {
|
|
this._node = ctx;
|
|
this._url = new NodeRequestURL({ req: ctx.req });
|
|
this.runtime = {
|
|
name: "node",
|
|
node: ctx
|
|
};
|
|
}
|
|
get ip() {
|
|
return this._node.req.socket?.remoteAddress;
|
|
}
|
|
get method() {
|
|
return this._node.req.method || "GET";
|
|
}
|
|
get url() {
|
|
return this._url.href;
|
|
}
|
|
get headers() {
|
|
return this.#headers ||= new NodeRequestHeaders(this._node);
|
|
}
|
|
get signal() {
|
|
if (!this.#abortSignal) {
|
|
this.#abortSignal = new AbortController();
|
|
this._node.req.once("close", () => {
|
|
this.#abortSignal?.abort();
|
|
});
|
|
}
|
|
return this.#abortSignal.signal;
|
|
}
|
|
get _request() {
|
|
if (!this.#request) {
|
|
const method = this.method;
|
|
const hasBody = !(method === "GET" || method === "HEAD");
|
|
this.#request = new PatchedRequest(this.url, {
|
|
method,
|
|
headers: this.headers,
|
|
signal: this.signal,
|
|
body: hasBody ? Readable.toWeb(this._node.req) : void 0
|
|
});
|
|
}
|
|
return this.#request;
|
|
}
|
|
}
|
|
inheritProps(NodeRequest$1.prototype, NativeRequest.prototype, "_request");
|
|
Object.setPrototypeOf(NodeRequest$1.prototype, PatchedRequest.prototype);
|
|
return NodeRequest$1;
|
|
})();
|
|
|
|
//#endregion
|
|
//#region src/adapters/node.ts
|
|
function serve(options) {
|
|
return new NodeServer(options);
|
|
}
|
|
function toNodeHandler(fetchHandler) {
|
|
return (nodeReq, nodeRes) => {
|
|
const request = new NodeRequest({
|
|
req: nodeReq,
|
|
res: nodeRes
|
|
});
|
|
const res = fetchHandler(request);
|
|
return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
|
|
};
|
|
}
|
|
var NodeServer = class {
|
|
runtime = "node";
|
|
options;
|
|
node;
|
|
serveOptions;
|
|
fetch;
|
|
#isSecure;
|
|
#listeningPromise;
|
|
#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);
|
|
this.#wait = createWaitUntil();
|
|
const handler = (nodeReq, nodeRes) => {
|
|
const request = new NodeRequest({
|
|
req: nodeReq,
|
|
res: nodeRes
|
|
});
|
|
request.waitUntil = this.#wait.waitUntil;
|
|
const res = fetchHandler(request);
|
|
return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
|
|
};
|
|
const tls = resolveTLSOptions(this.options);
|
|
const { port, hostname: host } = resolvePortAndHost(this.options);
|
|
this.serveOptions = {
|
|
port,
|
|
host,
|
|
exclusive: !this.options.reusePort,
|
|
...tls ? {
|
|
cert: tls.cert,
|
|
key: tls.key,
|
|
passphrase: tls.passphrase
|
|
} : {},
|
|
...this.options.node
|
|
};
|
|
let server;
|
|
this.#isSecure = !!this.serveOptions.cert && this.options.protocol !== "http";
|
|
const isHttp2 = this.options.node?.http2 ?? this.#isSecure;
|
|
if (isHttp2) if (this.#isSecure) {
|
|
const { createSecureServer } = process.getBuiltinModule("node:http2");
|
|
server = createSecureServer({
|
|
allowHTTP1: true,
|
|
...this.serveOptions
|
|
}, handler);
|
|
} else throw new Error("node.http2 option requires tls certificate!");
|
|
else if (this.#isSecure) {
|
|
const { createServer } = process.getBuiltinModule("node:https");
|
|
server = createServer(this.serveOptions, handler);
|
|
} else {
|
|
const { createServer } = process.getBuiltinModule("node:http");
|
|
server = createServer(this.serveOptions, handler);
|
|
}
|
|
this.node = {
|
|
server,
|
|
handler
|
|
};
|
|
if (!options.manual) this.serve();
|
|
}
|
|
serve() {
|
|
if (this.#listeningPromise) return Promise.resolve(this.#listeningPromise).then(() => this);
|
|
this.#listeningPromise = new Promise((resolve) => {
|
|
this.node.server.listen(this.serveOptions, () => {
|
|
printListening(this.options, this.url);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
get url() {
|
|
const addr = this.node?.server?.address();
|
|
if (!addr) return;
|
|
return typeof addr === "string" ? addr : fmtURL(addr.address, addr.port, this.#isSecure);
|
|
}
|
|
ready() {
|
|
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) return resolve();
|
|
if (closeAll && "closeAllConnections" in server) server.closeAllConnections();
|
|
server.close((error) => error ? reject(error) : resolve());
|
|
})]);
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
export { NodeResponse as FastResponse, FastURL, NodeRequest, NodeRequestHeaders, NodeResponse, NodeResponseHeaders, sendNodeResponse, serve, toNodeHandler }; |