in src/js/node/http.ts [1676:1928]
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);
}
let agent = options.agent;
const defaultAgent = options._defaultAgent || Agent.globalAgent;
if (agent === false) {
agent = new defaultAgent.constructor();
} else if (agent == null) {
agent = defaultAgent;
} else if (typeof agent.addRequest !== "function") {
throw ERR_INVALID_ARG_TYPE("options.agent", "Agent-like Object, undefined, or false", agent);
}
this.#agent = agent;
const protocol = options.protocol || defaultAgent.protocol;
let expectedProtocol = defaultAgent.protocol;
if (this.agent.protocol) {
expectedProtocol = this.agent.protocol;
}
if (protocol !== expectedProtocol) {
throw ERR_INVALID_PROTOCOL(protocol, expectedProtocol);
}
this.#protocol = protocol;
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");
}
}
const defaultPort = options.defaultPort || this.#agent.defaultPort;
this.#port = options.port || defaultPort || 80;
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 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;
if (options.pfx) {
throw new Error("pfx is not supported");
}
if (options.rejectUnauthorized !== undefined) this._ensureTls().rejectUnauthorized = options.rejectUnauthorized;
if (options.ca) {
if (!isValidTLSArray(options.ca))
throw new TypeError(
"ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
this._ensureTls().ca = options.ca;
}
if (options.cert) {
if (!isValidTLSArray(options.cert))
throw new TypeError(
"cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
this._ensureTls().cert = options.cert;
}
if (options.key) {
if (!isValidTLSArray(options.key))
throw new TypeError(
"key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
this._ensureTls().key = options.key;
}
if (options.passphrase) {
if (typeof options.passphrase !== "string") throw new TypeError("passphrase argument must be a string");
this._ensureTls().passphrase = options.passphrase;
}
if (options.ciphers) {
if (typeof options.ciphers !== "string") throw new TypeError("ciphers argument must be a string");
this._ensureTls().ciphers = options.ciphers;
}
if (options.servername) {
if (typeof options.servername !== "string") throw new TypeError("servername argument must be a string");
this._ensureTls().servername = options.servername;
}
if (options.secureOptions) {
if (typeof options.secureOptions !== "number") throw new TypeError("secureOptions argument must be a string");
this._ensureTls().secureOptions = options.secureOptions;
}
this.#path = options.path || "/";
if (cb) {
this.once("response", cb);
}
$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;
const timeout = options.timeout;
if (timeout !== undefined && timeout !== 0) {
this.setTimeout(timeout, undefined);
}
const { headers } = options;
const headersArray = $isJSArray(headers);
if (!headersArray) {
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);
const { signal: _signal, ...optsWithoutSignal } = options;
this.#options = optsWithoutSignal;
this._httpMessage = this;
process.nextTick(emitContinueAndSocketNT, this);
}