aboutsummaryrefslogtreecommitdiff
path: root/src/js/node/http.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/node/http.ts')
-rw-r--r--src/js/node/http.ts1906
1 files changed, 1906 insertions, 0 deletions
diff --git a/src/js/node/http.ts b/src/js/node/http.ts
new file mode 100644
index 000000000..fe075c832
--- /dev/null
+++ b/src/js/node/http.ts
@@ -0,0 +1,1906 @@
+// Hardcoded module "node:http"
+import { EventEmitter } from "node:events";
+import { Readable, Writable, Duplex } from "node:stream";
+import { isTypedArray } from "util/types";
+
+const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
+/**
+ * True if val contains an invalid field-vchar
+ * field-value = *( field-content / obs-fold )
+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+ * field-vchar = VCHAR / obs-text
+ */
+function checkInvalidHeaderChar(val: string) {
+ return RegExpPrototypeExec.call(headerCharRegex, val) !== null;
+}
+
+export const validateHeaderName = (name, label) => {
+ if (typeof name !== "string" || !name || !checkIsHttpToken(name)) {
+ // throw new ERR_INVALID_HTTP_TOKEN(label || "Header name", name);
+ throw new Error("ERR_INVALID_HTTP_TOKEN");
+ }
+};
+
+export const validateHeaderValue = (name, value) => {
+ if (value === undefined) {
+ // throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
+ throw new Error("ERR_HTTP_INVALID_HEADER_VALUE");
+ }
+ if (checkInvalidHeaderChar(value)) {
+ // throw new ERR_INVALID_CHAR("header content", name);
+ throw new Error("ERR_INVALID_CHAR");
+ }
+};
+
+// Cheaper to duplicate this than to import it from node:net
+function isIPv6(input) {
+ const v4Seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
+ const v4Str = `(${v4Seg}[.]){3}${v4Seg}`;
+ const v6Seg = "(?:[0-9a-fA-F]{1,4})";
+ const IPv6Reg = new RegExp(
+ "^(" +
+ `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
+ `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
+ `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
+ `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
+ `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
+ `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
+ `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
+ `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
+ ")(%[0-9a-zA-Z-.:]{1,})?$",
+ );
+
+ return IPv6Reg.test(input);
+}
+
+// TODO: add primordial for URL
+// Importing from node:url is unnecessary
+const { URL } = globalThis;
+
+const { newArrayWithSize, String, Object, Array } = globalThis[Symbol.for("Bun.lazy")]("primordials");
+
+const globalReportError = globalThis.reportError;
+const setTimeout = globalThis.setTimeout;
+const fetch = Bun.fetch;
+const nop = () => {};
+
+const __DEBUG__ = process.env.__DEBUG__;
+const debug = __DEBUG__ ? (...args) => console.log("node:http", ...args) : nop;
+
+const kEmptyObject = Object.freeze(Object.create(null));
+const kOutHeaders = Symbol.for("kOutHeaders");
+const kEndCalled = Symbol.for("kEndCalled");
+const kAbortController = Symbol.for("kAbortController");
+const kClearTimeout = Symbol("kClearTimeout");
+
+const kCorked = Symbol.for("kCorked");
+const searchParamsSymbol = Symbol.for("query"); // This is the symbol used in Node
+
+// Primordials
+const StringPrototypeSlice = String.prototype.slice;
+const StringPrototypeStartsWith = String.prototype.startsWith;
+const StringPrototypeToUpperCase = String.prototype.toUpperCase;
+const StringPrototypeIncludes = String.prototype.includes;
+const StringPrototypeCharCodeAt = String.prototype.charCodeAt;
+const StringPrototypeIndexOf = String.prototype.indexOf;
+const ArrayIsArray = Array.isArray;
+const RegExpPrototypeExec = RegExp.prototype.exec;
+const ObjectAssign = Object.assign;
+const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
+
+const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;
+const NODE_HTTP_WARNING =
+ "WARN: Agent is mostly unused in Bun's implementation of http. If you see strange behavior, this is probably the cause.";
+
+var _defaultHTTPSAgent;
+var kInternalRequest = Symbol("kInternalRequest");
+var kInternalSocketData = Symbol.for("::bunternal::");
+
+const kEmptyBuffer = Buffer.alloc(0);
+
+function isValidTLSArray(obj) {
+ if (typeof obj === "string" || isTypedArray(obj) || obj instanceof ArrayBuffer || obj instanceof Blob) return true;
+ if (Array.isArray(obj)) {
+ for (var i = 0; i < obj.length; i++) {
+ if (typeof obj !== "string" && !isTypedArray(obj) && !(obj instanceof ArrayBuffer) && !(obj instanceof Blob))
+ return false;
+ }
+ return true;
+ }
+}
+
+class ERR_INVALID_ARG_TYPE extends TypeError {
+ constructor(name, expected, actual) {
+ super(`The ${name} argument must be of type ${expected}. Received type ${typeof actual}`);
+ this.code = "ERR_INVALID_ARG_TYPE";
+ }
+}
+
+function validateMsecs(numberlike: any, field: string) {
+ if (typeof numberlike !== "number" || numberlike < 0) {
+ throw new ERR_INVALID_ARG_TYPE(field, "number", numberlike);
+ }
+
+ return numberlike;
+}
+function validateFunction(callable: any, field: string) {
+ if (typeof callable !== "function") {
+ throw new ERR_INVALID_ARG_TYPE(field, "Function", callable);
+ }
+
+ return callable;
+}
+
+function getHeader(headers, name) {
+ if (!headers) return;
+ const result = headers.get(name);
+ return result == null ? undefined : result;
+}
+
+type FakeSocket = InstanceType<typeof FakeSocket>;
+var FakeSocket = class Socket extends Duplex {
+ bytesRead = 0;
+ bytesWritten = 0;
+ connecting = false;
+ remoteAddress: string | null = null;
+ remotePort;
+ timeout = 0;
+
+ isServer = false;
+
+ address() {
+ return {
+ address: this.localAddress,
+ family: this.localFamily,
+ port: this.localPort,
+ };
+ }
+
+ get bufferSize() {
+ return this.writableLength;
+ }
+
+ connect(port, host, connectListener) {
+ return this;
+ }
+
+ _destroy(err, callback) {}
+
+ _final(callback) {}
+
+ get localAddress() {
+ return "127.0.0.1";
+ }
+
+ get localFamily() {
+ return "IPv4";
+ }
+
+ get localPort() {
+ return 80;
+ }
+
+ get pending() {
+ return this.connecting;
+ }
+
+ _read(size) {}
+
+ get readyState() {
+ if (this.connecting) return "opening";
+ if (this.readable) {
+ return this.writable ? "open" : "readOnly";
+ } else {
+ return this.writable ? "writeOnly" : "closed";
+ }
+ }
+
+ ref() {}
+
+ get remoteFamily() {
+ return "IPv4";
+ }
+
+ resetAndDestroy() {}
+
+ setKeepAlive(enable = false, initialDelay = 0) {}
+
+ setNoDelay(noDelay = true) {
+ return this;
+ }
+
+ setTimeout(timeout, callback) {
+ return this;
+ }
+
+ unref() {}
+
+ _write(chunk, encoding, callback) {}
+};
+
+export function createServer(options, callback) {
+ return new Server(options, callback);
+}
+
+export class Agent extends EventEmitter {
+ defaultPort = 80;
+ protocol = "http:";
+ options;
+ requests;
+ sockets;
+ freeSockets;
+
+ keepAliveMsecs;
+ keepAlive;
+ maxSockets;
+ maxFreeSockets;
+ scheduling;
+ maxTotalSockets;
+ totalSocketCount;
+
+ #fakeSocket;
+
+ static get globalAgent() {
+ return globalAgent;
+ }
+
+ static get defaultMaxSockets() {
+ return Infinity;
+ }
+
+ constructor(options = kEmptyObject) {
+ super();
+ this.options = options = { ...options, path: null };
+ if (options.noDelay === undefined) options.noDelay = true;
+
+ // Don't confuse net and make it think that we're connecting to a pipe
+ this.requests = kEmptyObject;
+ this.sockets = kEmptyObject;
+ this.freeSockets = kEmptyObject;
+
+ this.keepAliveMsecs = options.keepAliveMsecs || 1000;
+ this.keepAlive = options.keepAlive || false;
+ this.maxSockets = options.maxSockets || Agent.defaultMaxSockets;
+ this.maxFreeSockets = options.maxFreeSockets || 256;
+ this.scheduling = options.scheduling || "lifo";
+ this.maxTotalSockets = options.maxTotalSockets;
+ this.totalSocketCount = 0;
+ this.defaultPort = options.defaultPort || 80;
+ this.protocol = options.protocol || "http:";
+ }
+
+ createConnection() {
+ debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.createConnection is a no-op, returns fake socket");
+ return (this.#fakeSocket ??= new FakeSocket());
+ }
+
+ getName(options = kEmptyObject) {
+ let name = `http:${options.host || "localhost"}:`;
+ if (options.port) name += options.port;
+ name += ":";
+ if (options.localAddress) name += options.localAddress;
+ // Pacify parallel/test-http-agent-getname by only appending
+ // the ':' when options.family is set.
+ if (options.family === 4 || options.family === 6) name += `:${options.family}`;
+ if (options.socketPath) name += `:${options.socketPath}`;
+ return name;
+ }
+
+ addRequest() {
+ debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.addRequest is a no-op");
+ }
+
+ createSocket(req, options, cb) {
+ debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.createSocket returns fake socket");
+ cb(null, (this.#fakeSocket ??= new FakeSocket()));
+ }
+
+ removeSocket() {
+ debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.removeSocket is a no-op");
+ }
+
+ keepSocketAlive() {
+ debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.keepSocketAlive is a no-op");
+
+ return true;
+ }
+
+ reuseSocket() {
+ debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.reuseSocket is a no-op");
+ }
+
+ destroy() {
+ debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.destroy is a no-op");
+ }
+}
+function emitListeningNextTick(self, onListen, err, hostname, port) {
+ if (typeof onListen === "function") {
+ try {
+ onListen(err, hostname, port);
+ } catch (err) {
+ self.emit("error", err);
+ }
+ }
+
+ self.listening = !err;
+
+ if (err) {
+ self.emit("error", err);
+ } else {
+ self.emit("listening", hostname, port);
+ }
+}
+
+export class Server extends EventEmitter {
+ #server;
+ #options;
+ #tls;
+ #is_tls = false;
+ listening = false;
+ serverName;
+
+ constructor(options, callback) {
+ super();
+
+ if (typeof options === "function") {
+ callback = options;
+ options = {};
+ } else if (options == null || typeof options === "object") {
+ options = { ...options };
+ this.#tls = null;
+ let key = options.key;
+ if (key) {
+ if (!isValidTLSArray(key)) {
+ throw new TypeError(
+ "key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
+ );
+ }
+ this.#is_tls = true;
+ }
+ let cert = options.cert;
+ if (cert) {
+ if (!isValidTLSArray(cert)) {
+ throw new TypeError(
+ "cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
+ );
+ }
+ this.#is_tls = true;
+ }
+
+ let ca = options.ca;
+ if (ca) {
+ if (!isValidTLSArray(ca)) {
+ throw new TypeError(
+ "ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
+ );
+ }
+ this.#is_tls = true;
+ }
+ let passphrase = options.passphrase;
+ if (passphrase && typeof passphrase !== "string") {
+ throw new TypeError("passphrase argument must be an string");
+ }
+
+ let serverName = options.servername;
+ if (serverName && typeof serverName !== "string") {
+ throw new TypeError("servername argument must be an string");
+ }
+
+ let secureOptions = options.secureOptions || 0;
+ if (secureOptions && typeof secureOptions !== "number") {
+ throw new TypeError("secureOptions argument must be an number");
+ }
+
+ if (this.#is_tls) {
+ this.#tls = {
+ serverName,
+ key: key,
+ cert: cert,
+ ca: ca,
+ passphrase: passphrase,
+ secureOptions: secureOptions,
+ };
+ } else {
+ this.#tls = null;
+ }
+ } else {
+ throw new Error("bun-http-polyfill: invalid arguments");
+ }
+
+ this.#options = options;
+
+ if (callback) this.on("request", callback);
+ }
+
+ closeAllConnections() {
+ const server = this.#server;
+ if (!server) {
+ return;
+ }
+ this.#server = undefined;
+ server.stop(true);
+ this.emit("close");
+ }
+
+ closeIdleConnections() {
+ // not actually implemented
+ }
+
+ close(optionalCallback?) {
+ const server = this.#server;
+ if (!server) {
+ if (typeof optionalCallback === "function")
+ process.nextTick(optionalCallback, new Error("Server is not running"));
+ return;
+ }
+ this.#server = undefined;
+ if (typeof optionalCallback === "function") this.once("close", optionalCallback);
+ server.stop();
+ this.emit("close");
+ }
+
+ address() {
+ if (!this.#server) return null;
+
+ const address = this.#server.hostname;
+ return {
+ address,
+ family: isIPv6(address) ? "IPv6" : "IPv4",
+ port: this.#server.port,
+ };
+ }
+
+ listen(port, host, backlog, onListen) {
+ const server = this;
+ if (typeof host === "function") {
+ onListen = host;
+ host = undefined;
+ }
+
+ if (typeof port === "function") {
+ onListen = port;
+ } else if (typeof port === "object") {
+ port?.signal?.addEventListener("abort", () => {
+ this.close();
+ });
+
+ host = port?.host;
+ port = port?.port;
+
+ if (typeof port?.callback === "function") onListen = port?.callback;
+ }
+
+ if (typeof backlog === "function") {
+ onListen = backlog;
+ }
+
+ const ResponseClass = this.#options.ServerResponse || ServerResponse;
+ const RequestClass = this.#options.IncomingMessage || IncomingMessage;
+
+ try {
+ const tls = this.#tls;
+ if (tls) {
+ this.serverName = tls.serverName || host || "localhost";
+ }
+ this.#server = Bun.serve<any>({
+ tls,
+ port,
+ hostname: host,
+ // Bindings to be used for WS Server
+ websocket: {
+ open(ws) {
+ ws.data.open(ws);
+ },
+ message(ws, message) {
+ ws.data.message(ws, message);
+ },
+ close(ws, code, reason) {
+ ws.data.close(ws, code, reason);
+ },
+ drain(ws) {
+ ws.data.drain(ws);
+ },
+ },
+ fetch(req, _server) {
+ var pendingResponse;
+ var pendingError;
+ var rejectFunction, resolveFunction;
+ var reject = err => {
+ if (pendingError) return;
+ pendingError = err;
+ if (rejectFunction) rejectFunction(err);
+ };
+
+ var reply = function (resp) {
+ if (pendingResponse) return;
+ pendingResponse = resp;
+ if (resolveFunction) resolveFunction(resp);
+ };
+
+ const http_req = new RequestClass(req);
+ const http_res = new ResponseClass({ reply, req: http_req });
+
+ http_req.once("error", err => reject(err));
+ http_res.once("error", err => reject(err));
+
+ const upgrade = req.headers.get("upgrade");
+ if (upgrade) {
+ const socket = new FakeSocket();
+ socket[kInternalSocketData] = [_server, http_res, req];
+ server.emit("upgrade", http_req, socket, kEmptyBuffer);
+ } else {
+ server.emit("request", http_req, http_res);
+ }
+
+ if (pendingError) {
+ throw pendingError;
+ }
+
+ if (pendingResponse) {
+ return pendingResponse;
+ }
+
+ return new Promise((resolve, reject) => {
+ resolveFunction = resolve;
+ rejectFunction = reject;
+ });
+ },
+ });
+ setTimeout(emitListeningNextTick, 1, this, onListen, null, this.#server.hostname, this.#server.port);
+ } catch (err) {
+ setTimeout(emitListeningNextTick, 1, this, onListen, err);
+ }
+
+ return this;
+ }
+ setTimeout(msecs, callback) {}
+}
+
+function assignHeaders(object, req) {
+ var headers = req.headers.toJSON();
+ const rawHeaders = newArrayWithSize(req.headers.count * 2);
+ var i = 0;
+ for (const key in headers) {
+ rawHeaders[i++] = key;
+ rawHeaders[i++] = headers[key];
+ }
+ object.headers = headers;
+ object.rawHeaders = rawHeaders;
+}
+function destroyBodyStreamNT(bodyStream) {
+ bodyStream.destroy();
+}
+
+var defaultIncomingOpts = { type: "request" };
+
+function getDefaultHTTPSAgent() {
+ return (_defaultHTTPSAgent ??= new Agent({ defaultPort: 443, protocol: "https:" }));
+}
+
+export class IncomingMessage extends Readable {
+ method: string;
+ complete: boolean;
+
+ constructor(req, defaultIncomingOpts) {
+ const method = req.method;
+
+ super();
+
+ const url = new URL(req.url);
+
+ var { type = "request", [kInternalRequest]: nodeReq } = defaultIncomingOpts || {};
+
+ this.#noBody =
+ type === "request" // TODO: Add logic for checking for body on response
+ ? "GET" === method ||
+ "HEAD" === method ||
+ "TRACE" === method ||
+ "CONNECT" === method ||
+ "OPTIONS" === method ||
+ (parseInt(req.headers.get("Content-Length") || "") || 0) === 0
+ : false;
+
+ this.#req = req;
+ this.method = method;
+ this.#type = type;
+ this.complete = !!this.#noBody;
+
+ this.#bodyStream = undefined;
+ const socket = new FakeSocket();
+ socket.remoteAddress = url.hostname;
+ socket.remotePort = url.port;
+ this.#fakeSocket = socket;
+
+ this.url = url.pathname + url.search;
+ this.#nodeReq = nodeReq;
+ assignHeaders(this, req);
+ }
+
+ headers;
+ rawHeaders;
+ _consuming = false;
+ _dumped = false;
+ #bodyStream: ReadableStreamDefaultReader | undefined;
+ #fakeSocket: FakeSocket | undefined;
+ #noBody = false;
+ #aborted = false;
+ #req;
+ url;
+ #type;
+ #nodeReq;
+
+ get req() {
+ return this.#nodeReq;
+ }
+
+ _construct(callback) {
+ // TODO: streaming
+ if (this.#type === "response" || this.#noBody) {
+ callback();
+ return;
+ }
+
+ const contentLength = this.#req.headers.get("content-length");
+ const length = contentLength ? parseInt(contentLength, 10) : 0;
+ if (length === 0) {
+ this.#noBody = true;
+ callback();
+ return;
+ }
+
+ callback();
+ }
+
+ async #consumeStream(reader: ReadableStreamDefaultReader) {
+ while (true) {
+ var { done, value } = await reader.readMany();
+ if (this.#aborted) return;
+ if (done) {
+ this.push(null);
+ this.destroy();
+ break;
+ }
+ for (var v of value) {
+ this.push(v);
+ }
+ }
+ }
+
+ _read(size) {
+ if (this.#noBody) {
+ this.push(null);
+ this.complete = true;
+ } else if (this.#bodyStream == null) {
+ const reader = this.#req.body?.getReader() as ReadableStreamDefaultReader;
+ if (!reader) {
+ this.push(null);
+ return;
+ }
+ this.#bodyStream = reader;
+ this.#consumeStream(reader);
+ }
+ }
+
+ get aborted() {
+ return this.#aborted;
+ }
+
+ #abort() {
+ if (this.#aborted) return;
+ this.#aborted = true;
+ var bodyStream = this.#bodyStream;
+ if (!bodyStream) return;
+ bodyStream.cancel();
+ this.complete = true;
+ this.#bodyStream = undefined;
+ this.push(null);
+ }
+
+ get connection() {
+ return this.#fakeSocket;
+ }
+
+ get statusCode() {
+ return this.#req.status;
+ }
+
+ get statusMessage() {
+ return STATUS_CODES[this.#req.status];
+ }
+
+ get httpVersion() {
+ return "1.1";
+ }
+
+ get rawTrailers() {
+ return [];
+ }
+
+ get httpVersionMajor() {
+ return 1;
+ }
+
+ get httpVersionMinor() {
+ return 1;
+ }
+
+ get trailers() {
+ return kEmptyObject;
+ }
+
+ get socket() {
+ return (this.#fakeSocket ??= new FakeSocket());
+ }
+
+ set socket(val) {
+ this.#fakeSocket = val;
+ }
+
+ setTimeout(msecs, callback) {
+ throw new Error("not implemented");
+ }
+}
+
+function emitErrorNt(msg, err, callback) {
+ callback(err);
+ if (typeof msg.emit === "function" && !msg._closed) {
+ msg.emit("error", err);
+ }
+}
+
+function onError(self, err, cb) {
+ process.nextTick(() => emitErrorNt(self, err, cb));
+}
+
+function write_(msg, chunk, encoding, callback, fromEnd) {
+ if (typeof callback !== "function") callback = nop;
+
+ let len;
+ if (chunk === null) {
+ // throw new ERR_STREAM_NULL_VALUES();
+ throw new Error("ERR_STREAM_NULL_VALUES");
+ } else if (typeof chunk === "string") {
+ len = Buffer.byteLength(chunk, encoding);
+ } else {
+ throw new Error("Invalid arg type for chunk");
+ // throw new ERR_INVALID_ARG_TYPE(
+ // "chunk",
+ // ["string", "Buffer", "Uint8Array"],
+ // chunk,
+ // );
+ }
+
+ let err;
+ if (msg.finished) {
+ // err = new ERR_STREAM_WRITE_AFTER_END();
+ err = new Error("ERR_STREAM_WRITE_AFTER_END");
+ } else if (msg.destroyed) {
+ // err = new ERR_STREAM_DESTROYED("write");
+ err = new Error("ERR_STREAM_DESTROYED");
+ }
+
+ if (err) {
+ if (!msg.destroyed) {
+ onError(msg, err, callback);
+ } else {
+ process.nextTick(callback, err);
+ }
+ return false;
+ }
+
+ if (!msg._header) {
+ if (fromEnd) {
+ msg._contentLength = len;
+ }
+ // msg._implicitHeader();
+ }
+
+ if (!msg._hasBody) {
+ debug("This type of response MUST NOT have a body. " + "Ignoring write() calls.");
+ process.nextTick(callback);
+ return true;
+ }
+
+ // if (!fromEnd && msg.socket && !msg.socket.writableCorked) {
+ // msg.socket.cork();
+ // process.nextTick(connectionCorkNT, msg.socket);
+ // }
+
+ return true;
+}
+
+export class OutgoingMessage extends Writable {
+ #headers;
+ headersSent = false;
+ sendDate = true;
+ req;
+ timeout;
+
+ #finished = false;
+ [kEndCalled] = false;
+
+ #fakeSocket;
+ #timeoutTimer?: Timer;
+ [kAbortController]: AbortController | null = null;
+
+ // Express "compress" package uses this
+ _implicitHeader() {}
+
+ // For compat with IncomingRequest
+ get headers() {
+ if (!this.#headers) return kEmptyObject;
+ return this.#headers.toJSON();
+ }
+
+ get shouldKeepAlive() {
+ return true;
+ }
+
+ get chunkedEncoding() {
+ return false;
+ }
+
+ set chunkedEncoding(value) {
+ // throw new Error('not implemented');
+ }
+
+ set shouldKeepAlive(value) {
+ // throw new Error('not implemented');
+ }
+
+ get useChunkedEncodingByDefault() {
+ return true;
+ }
+
+ set useChunkedEncodingByDefault(value) {
+ // throw new Error('not implemented');
+ }
+
+ get socket() {
+ return (this.#fakeSocket ??= new FakeSocket());
+ }
+
+ set socket(val) {
+ this.#fakeSocket = val;
+ }
+
+ get connection() {
+ return this.socket;
+ }
+
+ get finished() {
+ return this.#finished;
+ }
+
+ appendHeader(name, value) {
+ var headers = (this.#headers ??= new Headers());
+ headers.append(name, value);
+ }
+
+ flushHeaders() {}
+
+ getHeader(name) {
+ return getHeader(this.#headers, name);
+ }
+
+ getHeaders() {
+ if (!this.#headers) return kEmptyObject;
+ return this.#headers.toJSON();
+ }
+
+ getHeaderNames() {
+ var headers = this.#headers;
+ if (!headers) return [];
+ return Array.from(headers.keys());
+ }
+
+ removeHeader(name) {
+ if (!this.#headers) return;
+ this.#headers.delete(name);
+ }
+
+ setHeader(name, value) {
+ var headers = (this.#headers ??= new Headers());
+ headers.set(name, value);
+ return this;
+ }
+
+ hasHeader(name) {
+ if (!this.#headers) return false;
+ return this.#headers.has(name);
+ }
+
+ addTrailers(headers) {
+ throw new Error("not implemented");
+ }
+
+ [kClearTimeout]() {
+ if (this.#timeoutTimer) {
+ clearTimeout(this.#timeoutTimer);
+ this.removeAllListeners("timeout");
+ this.#timeoutTimer = undefined;
+ }
+ }
+
+ #onTimeout() {
+ this.#timeoutTimer = undefined;
+ this[kAbortController]?.abort();
+ this.emit("timeout");
+ }
+
+ setTimeout(msecs, callback) {
+ if (this.destroyed) return this;
+
+ this.timeout = msecs = validateMsecs(msecs, "msecs");
+
+ // Attempt to clear an existing timer in both cases -
+ // even if it will be rescheduled we don't want to leak an existing timer.
+ clearTimeout(this.#timeoutTimer!);
+
+ if (msecs === 0) {
+ if (callback !== undefined) {
+ validateFunction(callback, "callback");
+ this.removeListener("timeout", callback);
+ }
+
+ this.#timeoutTimer = undefined;
+ } else {
+ this.#timeoutTimer = setTimeout(this.#onTimeout.bind(this), msecs).unref();
+
+ if (callback !== undefined) {
+ validateFunction(callback, "callback");
+ this.once("timeout", callback);
+ }
+ }
+
+ return this;
+ }
+}
+
+let OriginalWriteHeadFn, OriginalImplicitHeadFn;
+export class ServerResponse extends Writable {
+ declare _writableState: any;
+
+ constructor({ req, reply }) {
+ super();
+ this.req = req;
+ this._reply = reply;
+ this.sendDate = true;
+ this.statusCode = 200;
+ this.headersSent = false;
+ this.statusMessage = undefined;
+ this.#controller = undefined;
+ this.#firstWrite = undefined;
+ this._writableState.decodeStrings = false;
+ this.#deferred = undefined;
+
+ // this is matching node's behaviour
+ // https://github.com/nodejs/node/blob/cf8c6994e0f764af02da4fa70bc5962142181bf3/lib/_http_server.js#L192
+ if (req.method === "HEAD") this._hasBody = false;
+ }
+
+ req;
+ _reply;
+ sendDate;
+ statusCode;
+ #headers;
+ headersSent = false;
+ statusMessage;
+ #controller;
+ #firstWrite;
+ _sent100 = false;
+ _defaultKeepAlive = false;
+ _removedConnection = false;
+ _removedContLen = false;
+ _hasBody = true;
+ #deferred: (() => void) | undefined = undefined;
+ #finished = false;
+ // Express "compress" package uses this
+ _implicitHeader() {
+ // @ts-ignore
+ this.writeHead(this.statusCode);
+ }
+
+ _write(chunk, encoding, callback) {
+ if (!this.#firstWrite && !this.headersSent) {
+ this.#firstWrite = chunk;
+ callback();
+ return;
+ }
+
+ this.#ensureReadableStreamController(controller => {
+ controller.write(chunk);
+ callback();
+ });
+ }
+
+ _writev(chunks, callback) {
+ if (chunks.length === 1 && !this.headersSent && !this.#firstWrite) {
+ this.#firstWrite = chunks[0].chunk;
+ callback();
+ return;
+ }
+
+ this.#ensureReadableStreamController(controller => {
+ for (const chunk of chunks) {
+ controller.write(chunk.chunk);
+ }
+
+ callback();
+ });
+ }
+
+ #ensureReadableStreamController(run) {
+ var thisController = this.#controller;
+ if (thisController) return run(thisController);
+ this.headersSent = true;
+ var firstWrite = this.#firstWrite;
+ this.#firstWrite = undefined;
+ this._reply(
+ new Response(
+ new ReadableStream({
+ type: "direct",
+ pull: controller => {
+ this.#controller = controller;
+ if (firstWrite) controller.write(firstWrite);
+ firstWrite = undefined;
+ run(controller);
+ if (!this.#finished) {
+ return new Promise(resolve => {
+ this.#deferred = resolve;
+ });
+ }
+ },
+ }),
+ {
+ headers: this.#headers,
+ status: this.statusCode,
+ statusText: this.statusMessage ?? STATUS_CODES[this.statusCode],
+ },
+ ),
+ );
+ }
+
+ #drainHeadersIfObservable() {
+ if (this._implicitHeader === OriginalImplicitHeadFn && this.writeHead === OriginalWriteHeadFn) {
+ return;
+ }
+
+ this._implicitHeader();
+ }
+
+ _final(callback) {
+ if (!this.headersSent) {
+ var data = this.#firstWrite || "";
+ this.#firstWrite = undefined;
+ this.#finished = true;
+ this.#drainHeadersIfObservable();
+ this._reply(
+ new Response(data, {
+ headers: this.#headers,
+ status: this.statusCode,
+ statusText: this.statusMessage ?? STATUS_CODES[this.statusCode],
+ }),
+ );
+ callback && callback();
+ return;
+ }
+
+ this.#finished = true;
+ this.#ensureReadableStreamController(controller => {
+ controller.end();
+
+ callback();
+ var deferred = this.#deferred;
+ if (deferred) {
+ this.#deferred = undefined;
+ deferred();
+ }
+ });
+ }
+
+ writeProcessing() {
+ throw new Error("not implemented");
+ }
+
+ addTrailers(headers) {
+ throw new Error("not implemented");
+ }
+
+ assignSocket(socket) {
+ throw new Error("not implemented");
+ }
+
+ detachSocket(socket) {
+ throw new Error("not implemented");
+ }
+
+ writeContinue(callback) {
+ throw new Error("not implemented");
+ }
+
+ setTimeout(msecs, callback) {
+ throw new Error("not implemented");
+ }
+
+ get shouldKeepAlive() {
+ return true;
+ }
+
+ get chunkedEncoding() {
+ return false;
+ }
+
+ set chunkedEncoding(value) {
+ // throw new Error('not implemented');
+ }
+
+ set shouldKeepAlive(value) {
+ // throw new Error('not implemented');
+ }
+
+ get useChunkedEncodingByDefault() {
+ return true;
+ }
+
+ set useChunkedEncodingByDefault(value) {
+ // throw new Error('not implemented');
+ }
+
+ appendHeader(name, value) {
+ var headers = (this.#headers ??= new Headers());
+ headers.append(name, value);
+ }
+
+ flushHeaders() {}
+
+ getHeader(name) {
+ return getHeader(this.#headers, name);
+ }
+
+ getHeaders() {
+ var headers = this.#headers;
+ if (!headers) return kEmptyObject;
+ return headers.toJSON();
+ }
+
+ getHeaderNames() {
+ var headers = this.#headers;
+ if (!headers) return [];
+ return Array.from(headers.keys());
+ }
+
+ removeHeader(name) {
+ if (!this.#headers) return;
+ this.#headers.delete(name);
+ }
+
+ setHeader(name, value) {
+ var headers = (this.#headers ??= new Headers());
+ headers.set(name, value);
+ return this;
+ }
+
+ hasHeader(name) {
+ if (!this.#headers) return false;
+ return this.#headers.has(name);
+ }
+
+ writeHead(statusCode, statusMessage, headers) {
+ _writeHead(statusCode, statusMessage, headers, this);
+
+ return this;
+ }
+}
+
+OriginalWriteHeadFn = ServerResponse.prototype.writeHead;
+OriginalImplicitHeadFn = ServerResponse.prototype._implicitHeader;
+
+export class ClientRequest extends OutgoingMessage {
+ #timeout;
+ #res: IncomingMessage | null = null;
+ #upgradeOrConnect = false;
+ #parser = null;
+ #maxHeadersCount = null;
+ #reusedSocket = false;
+ #host;
+ #protocol;
+ #method;
+ #port;
+ #useDefaultPort;
+ #joinDuplicateHeaders;
+ #maxHeaderSize;
+ #agent = globalAgent;
+ #path;
+ #socketPath;
+
+ #body: string | null = null;
+ #fetchRequest;
+ #signal: AbortSignal | null = null;
+ [kAbortController]: AbortController | null = null;
+ #timeoutTimer?: Timer = undefined;
+ #options;
+ #finished;
+
+ get path() {
+ return this.#path;
+ }
+
+ get port() {
+ return this.#port;
+ }
+
+ get method() {
+ return this.#method;
+ }
+
+ get host() {
+ return this.#host;
+ }
+
+ get protocol() {
+ return this.#protocol;
+ }
+
+ _write(chunk, encoding, callback) {
+ var body = this.#body;
+ if (!body) {
+ this.#body = chunk;
+ callback();
+ return;
+ }
+ this.#body = body + chunk;
+ callback();
+ }
+
+ _writev(chunks, callback) {
+ var body = this.#body;
+ if (!body) {
+ this.#body = chunks.join();
+ callback();
+ return;
+ }
+ this.#body = body + chunks.join();
+ callback();
+ }
+
+ _final(callback) {
+ this.#finished = true;
+ this[kAbortController] = new AbortController();
+ this[kAbortController].signal.addEventListener("abort", () => {
+ this[kClearTimeout]();
+ });
+ if (this.#signal?.aborted) {
+ this[kAbortController].abort();
+ }
+
+ var method = this.#method,
+ body = this.#body;
+
+ try {
+ this.#fetchRequest = fetch(
+ `${this.#protocol}//${this.#host}${this.#useDefaultPort ? "" : ":" + this.#port}${this.#path}`,
+ {
+ method,
+ headers: this.getHeaders(),
+ body: body && method !== "GET" && method !== "HEAD" && method !== "OPTIONS" ? body : undefined,
+ redirect: "manual",
+ verbose: Boolean(__DEBUG__),
+ signal: this[kAbortController].signal,
+
+ // Timeouts are handled via this.setTimeout.
+ timeout: false,
+ },
+ )
+ .then(response => {
+ var res = (this.#res = new IncomingMessage(response, {
+ type: "response",
+ [kInternalRequest]: this,
+ }));
+ this.emit("response", res);
+ })
+ .catch(err => {
+ if (__DEBUG__) globalReportError(err);
+ this.emit("error", err);
+ })
+ .finally(() => {
+ this.#fetchRequest = null;
+ this[kClearTimeout]();
+ });
+ } catch (err) {
+ if (__DEBUG__) globalReportError(err);
+ this.emit("error", err);
+ } finally {
+ callback();
+ }
+ }
+
+ get aborted() {
+ return this.#signal?.aborted || !!this[kAbortController]?.signal.aborted;
+ }
+
+ abort() {
+ if (this.aborted) return;
+ this[kAbortController]!.abort();
+ // TODO: Close stream if body streaming
+ }
+
+ constructor(input, options, cb) {
+ super();
+
+ if (typeof input === "string") {
+ const urlStr = input;
+ try {
+ var urlObject = new URL(urlStr);
+ } catch (e) {
+ throw new TypeError(`Invalid URL: ${urlStr}`);
+ }
+ input = urlToHttpOptions(urlObject);
+ } else if (input && typeof input === "object" && input instanceof URL) {
+ // url.URL instance
+ input = urlToHttpOptions(input);
+ } else {
+ cb = options;
+ options = input;
+ input = null;
+ }
+
+ if (typeof options === "function") {
+ cb = options;
+ options = input || kEmptyObject;
+ } else {
+ options = ObjectAssign(input || {}, options);
+ }
+
+ var defaultAgent = options._defaultAgent || Agent.globalAgent;
+
+ let protocol = options.protocol;
+ if (!protocol) {
+ if (options.port === 443) {
+ protocol = "https:";
+ } else {
+ protocol = defaultAgent.protocol || "http:";
+ }
+ }
+ this.#protocol = protocol;
+
+ switch (this.#agent?.protocol) {
+ case undefined: {
+ break;
+ }
+ case "http:": {
+ if (protocol === "https:") {
+ defaultAgent = this.#agent = getDefaultHTTPSAgent();
+ break;
+ }
+ }
+ case "https:": {
+ if (protocol === "https") {
+ defaultAgent = this.#agent = Agent.globalAgent;
+ break;
+ }
+ }
+ default: {
+ break;
+ }
+ }
+
+ if (options.path) {
+ const path = String(options.path);
+ if (RegExpPrototypeExec.call(INVALID_PATH_REGEX, path) !== null) {
+ debug('Path contains unescaped characters: "%s"', path);
+ throw new Error("Path contains unescaped characters");
+ // throw new ERR_UNESCAPED_CHARACTERS("Request path");
+ }
+ }
+
+ // Since we don't implement Agent, we don't need this
+ if (protocol !== "http:" && protocol !== "https:" && protocol) {
+ const expectedProtocol = defaultAgent?.protocol ?? "http:";
+ throw new Error(`Protocol mismatch. Expected: ${expectedProtocol}. Got: ${protocol}`);
+ // throw new ERR_INVALID_PROTOCOL(protocol, expectedProtocol);
+ }
+
+ const defaultPort = protocol === "https:" ? 443 : 80;
+
+ this.#port = options.port || options.defaultPort || this.#agent?.defaultPort || defaultPort;
+ this.#useDefaultPort = this.#port === defaultPort;
+ const host =
+ (this.#host =
+ options.host =
+ validateHost(options.hostname, "hostname") || validateHost(options.host, "host") || "localhost");
+
+ // const setHost = options.setHost === undefined || Boolean(options.setHost);
+
+ this.#socketPath = options.socketPath;
+
+ const signal = options.signal;
+ if (signal) {
+ //We still want to control abort function and timeout so signal call our AbortController
+ signal.addEventListener("abort", () => {
+ this[kAbortController]?.abort();
+ });
+ this.#signal = signal;
+ }
+ let method = options.method;
+ const methodIsString = typeof method === "string";
+ if (method !== null && method !== undefined && !methodIsString) {
+ // throw new ERR_INVALID_ARG_TYPE("options.method", "string", method);
+ throw new Error("ERR_INVALID_ARG_TYPE: options.method");
+ }
+
+ if (methodIsString && method) {
+ if (!checkIsHttpToken(method)) {
+ // throw new ERR_INVALID_HTTP_TOKEN("Method", method);
+ throw new Error("ERR_INVALID_HTTP_TOKEN: Method");
+ }
+ method = this.#method = StringPrototypeToUpperCase.call(method);
+ } else {
+ method = this.#method = "GET";
+ }
+
+ const _maxHeaderSize = options.maxHeaderSize;
+ // TODO: Validators
+ // if (maxHeaderSize !== undefined)
+ // validateInteger(maxHeaderSize, "maxHeaderSize", 0);
+ this.#maxHeaderSize = _maxHeaderSize;
+
+ // const insecureHTTPParser = options.insecureHTTPParser;
+ // if (insecureHTTPParser !== undefined) {
+ // validateBoolean(insecureHTTPParser, 'options.insecureHTTPParser');
+ // }
+
+ // this.insecureHTTPParser = insecureHTTPParser;
+ var _joinDuplicateHeaders = options.joinDuplicateHeaders;
+ if (_joinDuplicateHeaders !== undefined) {
+ // TODO: Validators
+ // validateBoolean(
+ // options.joinDuplicateHeaders,
+ // "options.joinDuplicateHeaders",
+ // );
+ }
+
+ this.#joinDuplicateHeaders = _joinDuplicateHeaders;
+
+ this.#path = options.path || "/";
+ if (cb) {
+ this.once("response", cb);
+ }
+
+ __DEBUG__ &&
+ debug(`new ClientRequest: ${this.#method} ${this.#protocol}//${this.#host}:${this.#port}${this.#path}`);
+
+ // if (
+ // method === "GET" ||
+ // method === "HEAD" ||
+ // method === "DELETE" ||
+ // method === "OPTIONS" ||
+ // method === "TRACE" ||
+ // method === "CONNECT"
+ // ) {
+ // this.useChunkedEncodingByDefault = false;
+ // } else {
+ // this.useChunkedEncodingByDefault = true;
+ // }
+
+ this.#finished = false;
+ this.#res = null;
+ this.#upgradeOrConnect = false;
+ this.#parser = null;
+ this.#maxHeadersCount = null;
+ this.#reusedSocket = false;
+ this.#host = host;
+ this.#protocol = protocol;
+
+ var timeout = options.timeout;
+ if (timeout !== undefined && timeout !== 0) {
+ this.setTimeout(timeout, undefined);
+ }
+
+ const headersArray = ArrayIsArray(headers);
+ if (!headersArray) {
+ var headers = options.headers;
+ if (headers) {
+ for (let key in headers) {
+ this.setHeader(key, headers[key]);
+ }
+ }
+
+ // if (host && !this.getHeader("host") && setHost) {
+ // let hostHeader = host;
+
+ // // For the Host header, ensure that IPv6 addresses are enclosed
+ // // in square brackets, as defined by URI formatting
+ // // https://tools.ietf.org/html/rfc3986#section-3.2.2
+ // const posColon = StringPrototypeIndexOf.call(hostHeader, ":");
+ // if (
+ // posColon !== -1 &&
+ // StringPrototypeIncludes(hostHeader, ":", posColon + 1) &&
+ // StringPrototypeCharCodeAt(hostHeader, 0) !== 91 /* '[' */
+ // ) {
+ // hostHeader = `[${hostHeader}]`;
+ // }
+
+ // if (port && +port !== defaultPort) {
+ // hostHeader += ":" + port;
+ // }
+ // this.setHeader("Host", hostHeader);
+ // }
+
+ var auth = options.auth;
+ if (auth && !this.getHeader("Authorization")) {
+ this.setHeader("Authorization", "Basic " + Buffer.from(auth).toString("base64"));
+ }
+
+ // if (this.getHeader("expect")) {
+ // if (this._header) {
+ // throw new ERR_HTTP_HEADERS_SENT("render");
+ // }
+
+ // this._storeHeader(
+ // this.method + " " + this.path + " HTTP/1.1\r\n",
+ // this[kOutHeaders],
+ // );
+ // }
+ // } else {
+ // this._storeHeader(
+ // this.method + " " + this.path + " HTTP/1.1\r\n",
+ // options.headers,
+ // );
+ }
+
+ // this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders);
+
+ var { signal: _signal, ...optsWithoutSignal } = options;
+ this.#options = optsWithoutSignal;
+ }
+
+ setSocketKeepAlive(enable = true, initialDelay = 0) {
+ __DEBUG__ && debug(`${NODE_HTTP_WARNING}\n`, "WARN: ClientRequest.setSocketKeepAlive is a no-op");
+ }
+
+ setNoDelay(noDelay = true) {
+ __DEBUG__ && debug(`${NODE_HTTP_WARNING}\n`, "WARN: ClientRequest.setNoDelay is a no-op");
+ }
+ [kClearTimeout]() {
+ if (this.#timeoutTimer) {
+ clearTimeout(this.#timeoutTimer);
+ this.#timeoutTimer = undefined;
+ this.removeAllListeners("timeout");
+ }
+ }
+
+ #onTimeout() {
+ this.#timeoutTimer = undefined;
+ this[kAbortController]?.abort();
+ this.emit("timeout");
+ }
+
+ setTimeout(msecs, callback) {
+ if (this.destroyed) return this;
+
+ this.timeout = msecs = validateMsecs(msecs, "msecs");
+
+ // Attempt to clear an existing timer in both cases -
+ // even if it will be rescheduled we don't want to leak an existing timer.
+ clearTimeout(this.#timeoutTimer!);
+
+ if (msecs === 0) {
+ if (callback !== undefined) {
+ validateFunction(callback, "callback");
+ this.removeListener("timeout", callback);
+ }
+
+ this.#timeoutTimer = undefined;
+ } else {
+ this.#timeoutTimer = setTimeout(this.#onTimeout.bind(this), msecs).unref();
+
+ if (callback !== undefined) {
+ validateFunction(callback, "callback");
+ this.once("timeout", callback);
+ }
+ }
+
+ return this;
+ }
+}
+
+function urlToHttpOptions(url) {
+ var { protocol, hostname, hash, search, pathname, href, port, username, password } = url;
+ return {
+ protocol,
+ hostname:
+ typeof hostname === "string" && StringPrototypeStartsWith.call(hostname, "[")
+ ? StringPrototypeSlice.call(hostname, 1, -1)
+ : hostname,
+ hash,
+ search,
+ pathname,
+ path: `${pathname || ""}${search || ""}`,
+ href,
+ port: port ? Number(port) : protocol === "https:" ? 443 : protocol === "http:" ? 80 : undefined,
+ auth: username || password ? `${decodeURIComponent(username)}:${decodeURIComponent(password)}` : undefined,
+ };
+}
+
+function validateHost(host, name) {
+ if (host !== null && host !== undefined && typeof host !== "string") {
+ // throw new ERR_INVALID_ARG_TYPE(
+ // `options.${name}`,
+ // ["string", "undefined", "null"],
+ // host,
+ // );
+ throw new Error("Invalid arg type in options");
+ }
+ return host;
+}
+
+const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
+/**
+ * Verifies that the given val is a valid HTTP token
+ * per the rules defined in RFC 7230
+ * See https://tools.ietf.org/html/rfc7230#section-3.2.6
+ */
+function checkIsHttpToken(val) {
+ return RegExpPrototypeExec.call(tokenRegExp, val) !== null;
+}
+
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+export const METHODS = [
+ "ACL",
+ "BIND",
+ "CHECKOUT",
+ "CONNECT",
+ "COPY",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "LINK",
+ "LOCK",
+ "M-SEARCH",
+ "MERGE",
+ "MKACTIVITY",
+ "MKCALENDAR",
+ "MKCOL",
+ "MOVE",
+ "NOTIFY",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PROPFIND",
+ "PROPPATCH",
+ "PURGE",
+ "PUT",
+ "REBIND",
+ "REPORT",
+ "SEARCH",
+ "SOURCE",
+ "SUBSCRIBE",
+ "TRACE",
+ "UNBIND",
+ "UNLINK",
+ "UNLOCK",
+ "UNSUBSCRIBE",
+];
+
+export const STATUS_CODES = {
+ 100: "Continue",
+ 101: "Switching Protocols",
+ 102: "Processing",
+ 103: "Early Hints",
+ 200: "OK",
+ 201: "Created",
+ 202: "Accepted",
+ 203: "Non-Authoritative Information",
+ 204: "No Content",
+ 205: "Reset Content",
+ 206: "Partial Content",
+ 207: "Multi-Status",
+ 208: "Already Reported",
+ 226: "IM Used",
+ 300: "Multiple Choices",
+ 301: "Moved Permanently",
+ 302: "Found",
+ 303: "See Other",
+ 304: "Not Modified",
+ 305: "Use Proxy",
+ 307: "Temporary Redirect",
+ 308: "Permanent Redirect",
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 402: "Payment Required",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 406: "Not Acceptable",
+ 407: "Proxy Authentication Required",
+ 408: "Request Timeout",
+ 409: "Conflict",
+ 410: "Gone",
+ 411: "Length Required",
+ 412: "Precondition Failed",
+ 413: "Payload Too Large",
+ 414: "URI Too Long",
+ 415: "Unsupported Media Type",
+ 416: "Range Not Satisfiable",
+ 417: "Expectation Failed",
+ 418: "I'm a Teapot",
+ 421: "Misdirected Request",
+ 422: "Unprocessable Entity",
+ 423: "Locked",
+ 424: "Failed Dependency",
+ 425: "Too Early",
+ 426: "Upgrade Required",
+ 428: "Precondition Required",
+ 429: "Too Many Requests",
+ 431: "Request Header Fields Too Large",
+ 451: "Unavailable For Legal Reasons",
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 502: "Bad Gateway",
+ 503: "Service Unavailable",
+ 504: "Gateway Timeout",
+ 505: "HTTP Version Not Supported",
+ 506: "Variant Also Negotiates",
+ 507: "Insufficient Storage",
+ 508: "Loop Detected",
+ 509: "Bandwidth Limit Exceeded",
+ 510: "Not Extended",
+ 511: "Network Authentication Required",
+};
+
+function _normalizeArgs(args) {
+ let arr;
+
+ if (args.length === 0) {
+ arr = [{}, null];
+ // arr[normalizedArgsSymbol] = true;
+ return arr;
+ }
+
+ const arg0 = args[0];
+ let options: any = {};
+ if (typeof arg0 === "object" && arg0 !== null) {
+ // (options[...][, cb])
+ options = arg0;
+ // } else if (isPipeName(arg0)) {
+ // (path[...][, cb])
+ // options.path = arg0;
+ } else {
+ // ([port][, host][...][, cb])
+ options.port = arg0;
+ if (args.length > 1 && typeof args[1] === "string") {
+ options.host = args[1];
+ }
+ }
+
+ const cb = args[args.length - 1];
+ if (typeof cb !== "function") arr = [options, null];
+ else arr = [options, cb];
+
+ // arr[normalizedArgsSymbol] = true;
+ return arr;
+}
+
+function _writeHead(statusCode, reason, obj, response) {
+ statusCode |= 0;
+ if (statusCode < 100 || statusCode > 999) {
+ throw new Error("status code must be between 100 and 999");
+ }
+
+ if (typeof reason === "string") {
+ // writeHead(statusCode, reasonPhrase[, headers])
+ response.statusMessage = reason;
+ } else {
+ // writeHead(statusCode[, headers])
+ if (!response.statusMessage) response.statusMessage = STATUS_CODES[statusCode] || "unknown";
+ obj = reason;
+ }
+ response.statusCode = statusCode;
+
+ {
+ // Slow-case: when progressive API and header fields are passed.
+ let k;
+ if (Array.isArray(obj)) {
+ if (obj.length % 2 !== 0) {
+ throw new Error("raw headers must have an even number of elements");
+ }
+
+ for (let n = 0; n < obj.length; n += 2) {
+ k = obj[n + 0];
+ if (k) response.setHeader(k, obj[n + 1]);
+ }
+ } else if (obj) {
+ const keys = Object.keys(obj);
+ // Retain for(;;) loop for performance reasons
+ // Refs: https://github.com/nodejs/node/pull/30958
+ for (let i = 0; i < keys.length; i++) {
+ k = keys[i];
+ if (k) response.setHeader(k, obj[k]);
+ }
+ }
+ }
+
+ if (statusCode === 204 || statusCode === 304 || (statusCode >= 100 && statusCode <= 199)) {
+ // RFC 2616, 10.2.5:
+ // The 204 response MUST NOT include a message-body, and thus is always
+ // terminated by the first empty line after the header fields.
+ // RFC 2616, 10.3.5:
+ // The 304 response MUST NOT contain a message-body, and thus is always
+ // terminated by the first empty line after the header fields.
+ // RFC 2616, 10.1 Informational 1xx:
+ // This class of status code indicates a provisional response,
+ // consisting only of the Status-Line and optional headers, and is
+ // terminated by an empty line.
+ response._hasBody = false;
+ }
+}
+
+/**
+ * Makes an HTTP request.
+ * @param {string | URL} url
+ * @param {HTTPRequestOptions} [options]
+ * @param {Function} [cb]
+ * @returns {ClientRequest}
+ */
+export function request(url, options, cb) {
+ return new ClientRequest(url, options, cb);
+}
+
+/**
+ * Makes a `GET` HTTP request.
+ * @param {string | URL} url
+ * @param {HTTPRequestOptions} [options]
+ * @param {Function} [cb]
+ * @returns {ClientRequest}
+ */
+export function get(url, options, cb) {
+ const req = request(url, options, cb);
+ req.end();
+ return req;
+}
+
+export var globalAgent = new Agent();
+var defaultObject = {
+ Agent,
+ Server,
+ METHODS,
+ STATUS_CODES,
+ createServer,
+ ServerResponse,
+ IncomingMessage,
+ request,
+ get,
+ maxHeaderSize: 16384,
+ validateHeaderName,
+ validateHeaderValue,
+ setMaxIdleHTTPParsers(max) {
+ debug(`${NODE_HTTP_WARNING}\n`, "setMaxIdleHTTPParsers() is a no-op");
+ },
+ globalAgent,
+ ClientRequest,
+ OutgoingMessage,
+ [Symbol.for("CommonJS")]: 0,
+};
+
+export default defaultObject;