Files
bun.sh/src/js/node/_http_server.ts
robobun 2b7fc18092 fix(lint): resolve no-unused-expressions errors (#23127)
## Summary

Fixes all oxlint `no-unused-expressions` violations across the codebase
by:
- Adding an oxlint override to disable the rule for
`src/js/builtins/**`, where special syntax markers like `$getter`,
`$constructor`, etc. are intentionally used as standalone expressions
- Converting short-circuit expressions (`condition && fn()`) to proper
if statements for improved code clarity
- Wrapping intentional property access side effects (e.g.,
`this.stdio;`, `err.stack;`) with the `void` operator
- Converting ternary expressions used for control flow to if/else
statements

## Test plan

- [x] `bun lint` passes with no errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-30 04:45:34 -07:00

1902 lines
54 KiB
TypeScript

// Hardcoded module "node:_http_server"
const EventEmitter: typeof import("node:events").EventEmitter = require("node:events");
const { Duplex, Stream } = require("node:stream");
const { _checkInvalidHeaderChar: checkInvalidHeaderChar } = require("node:_http_common");
const { validateObject, validateLinkHeaderValue, validateBoolean, validateInteger } = require("internal/validators");
const { isPrimary } = require("internal/cluster/isPrimary");
const { throwOnInvalidTLSArray } = require("internal/tls");
const {
kInternalSocketData,
serverSymbol,
kHandle,
kRealListen,
tlsSymbol,
optionsSymbol,
kDeferredTimeouts,
kDeprecatedReplySymbol,
headerStateSymbol,
NodeHTTPHeaderState,
kPendingCallbacks,
kRequest,
kCloseCallback,
NodeHTTPResponseFlags,
headersSymbol,
emitErrorNextTickIfErrorListenerNT,
getIsNextIncomingMessageHTTPS,
setIsNextIncomingMessageHTTPS,
callCloseCallback,
emitCloseNT,
ConnResetException,
NodeHTTPResponseAbortEvent,
STATUS_CODES,
isTlsSymbol,
hasServerResponseFinished,
OutgoingMessagePrototype,
NodeHTTPBodyReadState,
controllerSymbol,
firstWriteSymbol,
deferredSymbol,
eofInProgress,
runSymbol,
drainMicrotasks,
setServerIdleTimeout,
setServerCustomOptions,
getMaxHTTPHeaderSize,
} = require("internal/http");
const NumberIsNaN = Number.isNaN;
const { format } = require("internal/util/inspect");
const { IncomingMessage } = require("node:_http_incoming");
const { OutgoingMessage } = require("node:_http_outgoing");
const { kIncomingMessage } = require("node:_http_common");
const kConnectionsCheckingInterval = Symbol("http.server.connectionsCheckingInterval");
const getBunServerAllClosedPromise = $newZigFunction("node_http_binding.zig", "getBunServerAllClosedPromise", 1);
const sendHelper = $newZigFunction("node_cluster_binding.zig", "sendHelperChild", 3);
const kServerResponse = Symbol("ServerResponse");
const kRejectNonStandardBodyWrites = Symbol("kRejectNonStandardBodyWrites");
const GlobalPromise = globalThis.Promise;
const kEmptyBuffer = Buffer.alloc(0);
const ObjectKeys = Object.keys;
const MathMin = Math.min;
let cluster;
function emitCloseServer(self: Server) {
callCloseCallback(self);
self.emit("close");
}
function emitCloseNTServer(this: Server) {
process.nextTick(emitCloseServer, this);
}
function setCloseCallback(self, callback) {
if (callback === self[kCloseCallback]) {
return;
}
if (self[kCloseCallback]) {
throw new Error("Close callback already set");
}
self[kCloseCallback] = callback;
}
function assignSocketInternal(self, socket) {
if (socket._httpMessage) {
throw $ERR_HTTP_SOCKET_ASSIGNED("Socket already assigned");
}
socket._httpMessage = self;
setCloseCallback(socket, onServerResponseClose);
self.socket = socket;
self.emit("socket", socket);
}
function onServerResponseClose() {
// EventEmitter.emit makes a copy of the 'close' listeners array before
// calling the listeners. detachSocket() unregisters onServerResponseClose
// but if detachSocket() is called, directly or indirectly, by a 'close'
// listener, onServerResponseClose is still in that copy of the listeners
// array. That is, in the example below, b still gets called even though
// it's been removed by a:
//
// const EventEmitter = require('events');
// const obj = new EventEmitter();
// obj.on('event', a);
// obj.on('event', b);
// function a() { obj.removeListener('event', b) }
// function b() { throw "BAM!" }
// obj.emit('event'); // throws
//
// Ergo, we need to deal with stale 'close' events and handle the case
// where the ServerResponse object has already been deconstructed.
// Fortunately, that requires only a single if check. :-)
const httpMessage = this._httpMessage;
if (httpMessage) {
emitCloseNT(httpMessage);
}
}
function strictContentLength(response) {
if (response.strictContentLength) {
let contentLength = response._contentLength ?? response.getHeader("content-length");
if (
contentLength &&
response._hasBody &&
!response._removedContLen &&
!response.chunkedEncoding &&
!response.hasHeader("transfer-encoding")
) {
if (typeof contentLength === "number") {
return contentLength;
} else if (typeof contentLength === "string") {
contentLength = parseInt(contentLength, 10);
if (NumberIsNaN(contentLength)) {
return;
}
return contentLength;
}
}
}
}
const ServerResponse_writeDeprecated = function _write(chunk, encoding, callback) {
if ($isCallable(encoding)) {
callback = encoding;
encoding = undefined;
}
if (!$isCallable(callback)) {
callback = undefined;
}
if (encoding && encoding !== "buffer") {
chunk = Buffer.from(chunk, encoding);
}
if (this.destroyed || this.finished) {
if (chunk) {
emitErrorNextTickIfErrorListenerNT(this, $ERR_STREAM_WRITE_AFTER_END(), callback);
}
return false;
}
if (this[firstWriteSymbol] === undefined && !this.headersSent) {
this[firstWriteSymbol] = chunk;
if (callback) callback();
return;
}
ensureReadableStreamController.$call(this, controller => {
controller.write(chunk);
if (callback) callback();
});
};
function onNodeHTTPServerSocketTimeout() {
const req = this[kRequest];
const reqTimeout = req && !req.complete && req.emit("timeout", this);
const res = this._httpMessage;
const resTimeout = res && res.emit("timeout", this);
const serverTimeout = this.server.emit("timeout", this);
if (!reqTimeout && !resTimeout && !serverTimeout) this.destroy();
}
function emitRequestCloseNT(self) {
callCloseCallback(self);
self.emit("close");
}
function emitListeningNextTick(self, hostname, port) {
if ((self.listening = !!self[serverSymbol])) {
// TODO: remove the arguments
// Note does not pass any arguments.
self.emit("listening", null, hostname, port);
}
}
function Server(options, callback): void {
if (!(this instanceof Server)) return new Server(options, callback);
EventEmitter.$call(this);
this[kConnectionsCheckingInterval] = { _destroyed: false };
this.listening = false;
this._unref = false;
this.maxRequestsPerSocket = 0;
this[kInternalSocketData] = undefined;
this[tlsSymbol] = null;
this.noDelay = true;
if (typeof options === "function") {
callback = options;
options = {};
} else if (options == null) {
options = {};
} else {
validateObject(options, "options");
options = { ...options };
let cert = options.cert;
if (cert) {
throwOnInvalidTLSArray("options.cert", cert);
this[isTlsSymbol] = true;
}
let key = options.key;
if (key) {
throwOnInvalidTLSArray("options.key", key);
this[isTlsSymbol] = true;
}
let ca = options.ca;
if (ca) {
throwOnInvalidTLSArray("options.ca", ca);
this[isTlsSymbol] = true;
}
let passphrase = options.passphrase;
if (passphrase && typeof passphrase !== "string") {
throw $ERR_INVALID_ARG_TYPE("options.passphrase", "string", passphrase);
}
let serverName = options.servername;
if (serverName && typeof serverName !== "string") {
throw $ERR_INVALID_ARG_TYPE("options.servername", "string", serverName);
}
let secureOptions = options.secureOptions || 0;
if (secureOptions && typeof secureOptions !== "number") {
throw $ERR_INVALID_ARG_TYPE("options.secureOptions", "number", secureOptions);
}
if (this[isTlsSymbol]) {
this[tlsSymbol] = {
serverName,
key,
cert,
ca,
passphrase,
secureOptions,
};
} else {
this[tlsSymbol] = null;
}
}
this[optionsSymbol] = options;
storeHTTPOptions.$call(this, options);
if (callback) this.on("request", callback);
return this;
}
$toClass(Server, "Server", EventEmitter);
Server.prototype[kIncomingMessage] = undefined;
Server.prototype[kServerResponse] = undefined;
Server.prototype[kConnectionsCheckingInterval] = undefined;
Server.prototype.ref = function () {
this._unref = false;
this[serverSymbol]?.ref?.();
return this;
};
Server.prototype.unref = function () {
this._unref = true;
this[serverSymbol]?.unref?.();
return this;
};
Server.prototype.closeAllConnections = function () {
const server = this[serverSymbol];
if (!server) {
return;
}
this[serverSymbol] = undefined;
const connectionsCheckingInterval = this[kConnectionsCheckingInterval];
if (connectionsCheckingInterval) {
connectionsCheckingInterval._destroyed = true;
}
this.listening = false;
server.stop(true);
};
Server.prototype.closeIdleConnections = function () {
const server = this[serverSymbol];
server.closeIdleConnections();
};
Server.prototype.close = function (optionalCallback?) {
const server = this[serverSymbol];
if (!server) {
if (typeof optionalCallback === "function") process.nextTick(optionalCallback, $ERR_SERVER_NOT_RUNNING());
return;
}
this[serverSymbol] = undefined;
const connectionsCheckingInterval = this[kConnectionsCheckingInterval];
if (connectionsCheckingInterval) {
connectionsCheckingInterval._destroyed = true;
}
if (typeof optionalCallback === "function") setCloseCallback(this, optionalCallback);
this.listening = false;
server.closeIdleConnections();
server.stop();
};
Server.prototype[EventEmitter.captureRejectionSymbol] = function (err, event, ...args) {
switch (event) {
case "request": {
const { 1: res } = args;
if (!res.headersSent && !res.writableEnded) {
// Don't leak headers.
const names = res.getHeaderNames();
for (let i = 0; i < names.length; i++) {
res.removeHeader(names[i]);
}
res.statusCode = 500;
res.end(STATUS_CODES[500]);
} else {
res.destroy();
}
break;
}
default:
// net.Server.prototype[EventEmitter.captureRejectionSymbol].apply(this, arguments);
// .apply(this, arguments);
const { 1: res } = args;
res?.socket?.destroy();
break;
}
};
Server.prototype[Symbol.asyncDispose] = function () {
const { resolve, reject, promise } = Promise.withResolvers();
this.close(function (err, ...args) {
if (err) {
reject(err);
} else resolve(...args);
});
return promise;
};
Server.prototype.address = function () {
if (!this[serverSymbol]) return null;
return this[serverSymbol].address;
};
Server.prototype.listen = function () {
const server = this;
let port, host, onListen;
let socketPath;
let tls = this[tlsSymbol];
// This logic must align with:
// - https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/net.js#L274-L307
if (arguments.length > 0) {
if (($isObject(arguments[0]) || $isCallable(arguments[0])) && arguments[0] !== null) {
// (options[...][, cb])
port = arguments[0].port;
host = arguments[0].host;
socketPath = arguments[0].path;
const otherTLS = arguments[0].tls;
if (otherTLS && $isObject(otherTLS)) {
tls = otherTLS;
}
} else if (typeof arguments[0] === "string" && !(Number(arguments[0]) >= 0)) {
// (path[...][, cb])
socketPath = arguments[0];
} else {
// ([port][, host][...][, cb])
port = arguments[0];
if (arguments.length > 1 && typeof arguments[1] === "string") {
host = arguments[1];
}
}
}
// Bun defaults to port 3000.
// Node defaults to port 0.
if (port === undefined && !socketPath) {
port = 0;
}
if (typeof port === "string") {
const portNumber = parseInt(port);
if (!Number.isNaN(portNumber)) {
port = portNumber;
}
}
if ($isCallable(arguments[arguments.length - 1])) {
onListen = arguments[arguments.length - 1];
}
try {
// listenInCluster
if (isPrimary) {
server[kRealListen](tls, port, host, socketPath, false, onListen);
return this;
}
if (cluster === undefined) cluster = require("node:cluster");
// TODO: our net.Server and http.Server use different Bun APIs and our IPC doesnt support sending and receiving handles yet. use reusePort instead for now.
// const serverQuery = {
// // address: address,
// port: port,
// addressType: 4,
// // fd: fd,
// // flags,
// // backlog,
// // ...options,
// };
// cluster._getServer(server, serverQuery, function listenOnPrimaryHandle(err, handle) {
// // err = checkBindError(err, port, handle);
// // if (err) {
// // throw new ExceptionWithHostPort(err, "bind", address, port);
// // }
// if (err) {
// throw err;
// }
// server[kRealListen](port, host, socketPath, onListen);
// });
server.once("listening", () => {
cluster.worker.state = "listening";
const address = server.address();
const message = {
act: "listening",
port: (address && address.port) || port,
data: null,
addressType: 4,
};
sendHelper(message, null);
});
server[kRealListen](tls, port, host, socketPath, true, onListen);
} catch (err) {
setTimeout(() => server.emit("error", err), 1);
}
return this;
};
Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort, onListen) {
{
const ResponseClass = this[optionsSymbol].ServerResponse || ServerResponse;
const RequestClass = this[optionsSymbol].IncomingMessage || IncomingMessage;
const canUseInternalAssignSocket = ResponseClass?.prototype.assignSocket === ServerResponse.prototype.assignSocket;
let isHTTPS = false;
let server = this;
if (tls) {
this.serverName = tls.serverName || host || "localhost";
}
this[serverSymbol] = Bun.serve<any>({
idleTimeout: 0, // nodejs dont have a idleTimeout by default
tls,
port,
hostname: host,
unix: socketPath,
reusePort,
// 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);
},
ping(ws, data) {
ws.data.ping(ws, data);
},
pong(ws, data) {
ws.data.pong(ws, data);
},
},
maxRequestBodySize: Number.MAX_SAFE_INTEGER,
onNodeHTTPRequest(
bunServer,
url: string,
method: string,
headersObject: Record<string, string>,
headersArray: string[],
handle,
hasBody: boolean,
socketHandle,
isSocketNew,
socket,
isAncientHTTP: boolean,
) {
const prevIsNextIncomingMessageHTTPS = getIsNextIncomingMessageHTTPS();
setIsNextIncomingMessageHTTPS(isHTTPS);
if (!socket) {
socket = new NodeHTTPServerSocket(server, socketHandle, !!tls);
}
const http_req = new RequestClass(kHandle, url, method, headersObject, headersArray, handle, hasBody, socket);
if (isAncientHTTP) {
http_req.httpVersion = "1.0";
}
if (method === "CONNECT") {
// Handle CONNECT method for HTTP tunneling/proxy
if (server.listenerCount("connect") > 0) {
// For CONNECT, emit the event and let the handler respond
// Don't assign the socket to a response for CONNECT
// The handler should write the raw response
socket[kEnableStreaming](true);
const { promise, resolve } = $newPromiseCapability(Promise);
socket.once("close", resolve);
server.emit("connect", http_req, socket, kEmptyBuffer);
return promise;
} else {
// Node.js will close the socket and will NOT respond with 400 Bad Request
socketHandle.close();
}
return;
}
socket[kEnableStreaming](false);
const http_res = new ResponseClass(http_req, {
[kHandle]: handle,
[kRejectNonStandardBodyWrites]: server.rejectNonStandardBodyWrites,
});
setIsNextIncomingMessageHTTPS(prevIsNextIncomingMessageHTTPS);
handle.onabort = onServerRequestEvent.bind(socket);
// start buffering data if any, the user will need to resume() or .on("data") to read it
if (hasBody) {
handle.pause();
}
drainMicrotasks();
let resolveFunction;
let didFinish = false;
const isRequestsLimitSet = typeof server.maxRequestsPerSocket === "number" && server.maxRequestsPerSocket > 0;
let reachedRequestsLimit = false;
if (isRequestsLimitSet) {
const requestCount = (socket._requestCount || 0) + 1;
socket._requestCount = requestCount;
if (server.maxRequestsPerSocket < requestCount) {
reachedRequestsLimit = true;
}
}
if (isSocketNew && !reachedRequestsLimit) {
server.emit("connection", socket);
}
socket[kRequest] = http_req;
const is_upgrade = http_req.headers.upgrade;
if (!is_upgrade) {
if (canUseInternalAssignSocket) {
// ~10% performance improvement in JavaScriptCore due to avoiding .once("close", ...) and removing a listener
assignSocketInternal(http_res, socket);
} else {
http_res.assignSocket(socket);
}
}
function onClose() {
didFinish = true;
if (resolveFunction) resolveFunction();
}
setCloseCallback(http_res, onClose);
if (reachedRequestsLimit) {
server.emit("dropRequest", http_req, socket);
http_res.writeHead(503);
http_res.end();
socket.destroy();
} else if (is_upgrade) {
server.emit("upgrade", http_req, socket, kEmptyBuffer);
if (!socket._httpMessage) {
if (canUseInternalAssignSocket) {
// ~10% performance improvement in JavaScriptCore due to avoiding .once("close", ...) and removing a listener
assignSocketInternal(http_res, socket);
} else {
http_res.assignSocket(socket);
}
}
} else if (http_req.headers.expect !== undefined) {
if (http_req.headers.expect === "100-continue") {
if (server.listenerCount("checkContinue") > 0) {
server.emit("checkContinue", http_req, http_res);
} else {
http_res.writeContinue();
server.emit("request", http_req, http_res);
}
} else if (server.listenerCount("checkExpectation") > 0) {
server.emit("checkExpectation", http_req, http_res);
} else {
http_res.writeHead(417);
http_res.end();
}
} else {
server.emit("request", http_req, http_res);
}
socket.cork();
if (handle.finished || didFinish) {
handle = undefined;
http_res[kCloseCallback] = undefined;
http_res.detachSocket(socket);
return;
}
if (http_res.socket) {
http_res.on("finish", http_res.detachSocket.bind(http_res, socket));
}
const { resolve, promise } = $newPromiseCapability(Promise);
resolveFunction = resolve;
return promise;
},
// Be very careful not to access (web) Request object
// properties:
// - request.url
// - request.headers
//
// We want to avoid triggering the getter for these properties because
// that will cause the data to be cloned twice, which costs memory & performance.
// fetch(req, _server) {
// var pendingResponse;
// var pendingError;
// 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 prevIsNextIncomingMessageHTTPS = isNextIncomingMessageHTTPS;
// isNextIncomingMessageHTTPS = isHTTPS;
// const http_req = new RequestClass(req, {
// [typeSymbol]: NodeHTTPIncomingRequestType.FetchRequest,
// });
// assignEventCallback(req, onRequestEvent.bind(http_req));
// isNextIncomingMessageHTTPS = prevIsNextIncomingMessageHTTPS;
// const upgrade = http_req.headers.upgrade;
// const http_res = new ResponseClass(http_req, { [kDeprecatedReplySymbol]: reply });
// http_req.socket[kInternalSocketData] = [server, http_res, req];
// server.emit("connection", http_req.socket);
// const rejectFn = err => reject(err);
// http_req.once("error", rejectFn);
// http_res.once("error", rejectFn);
// if (upgrade) {
// server.emit("upgrade", http_req, http_req.socket, kEmptyBuffer);
// } else {
// server.emit("request", http_req, http_res);
// }
// if (pendingError) {
// throw pendingError;
// }
// if (pendingResponse) {
// return pendingResponse;
// }
// var { promise, resolve: resolveFunction, reject: rejectFunction } = $newPromiseCapability(GlobalPromise);
// return promise;
// },
});
getBunServerAllClosedPromise(this[serverSymbol]).$then(emitCloseNTServer.bind(this));
isHTTPS = this[serverSymbol].protocol === "https";
// always set strict method validation to true for node.js compatibility
setServerCustomOptions(
this[serverSymbol],
this.requireHostHeader,
true,
typeof this.maxHeaderSize !== "undefined" ? this.maxHeaderSize : getMaxHTTPHeaderSize(),
onServerClientError.bind(this),
);
if (this?._unref) {
this[serverSymbol]?.unref?.();
}
if ($isCallable(onListen)) {
this.once("listening", onListen);
}
if (this[kDeferredTimeouts]) {
for (const { msecs, callback } of this[kDeferredTimeouts]) {
this.setTimeout(msecs, callback);
}
delete this[kDeferredTimeouts];
}
setTimeout(emitListeningNextTick, 1, this, this[serverSymbol]?.hostname, this[serverSymbol]?.port);
}
};
Server.prototype.setTimeout = function (msecs, callback) {
const server = this[serverSymbol];
if (server) {
setServerIdleTimeout(server, Math.ceil(msecs / 1000));
if (typeof callback === "function") this.once("timeout", callback);
} else {
(this[kDeferredTimeouts] ??= []).push({ msecs, callback });
}
return this;
};
function onServerRequestEvent(this: NodeHTTPServerSocket, event: NodeHTTPResponseAbortEvent) {
const socket: NodeHTTPServerSocket = this;
switch (event) {
case NodeHTTPResponseAbortEvent.abort: {
if (!socket.destroyed) {
socket.destroy();
}
break;
}
case NodeHTTPResponseAbortEvent.timeout: {
socket.emit("timeout");
break;
}
}
}
// uWS::HttpParserError
enum HttpParserError {
HTTP_PARSER_ERROR_NONE = 0,
HTTP_PARSER_ERROR_INVALID_CHUNKED_ENCODING = 1,
HTTP_PARSER_ERROR_INVALID_CONTENT_LENGTH = 2,
HTTP_PARSER_ERROR_INVALID_TRANSFER_ENCODING = 3,
HTTP_PARSER_ERROR_MISSING_HOST_HEADER = 4,
HTTP_PARSER_ERROR_INVALID_REQUEST = 5,
HTTP_PARSER_ERROR_REQUEST_HEADER_FIELDS_TOO_LARGE = 6,
HTTP_PARSER_ERROR_INVALID_HTTP_VERSION = 7,
HTTP_PARSER_ERROR_INVALID_EOF = 8,
HTTP_PARSER_ERROR_INVALID_METHOD = 9,
HTTP_PARSER_ERROR_INVALID_HEADER_TOKEN = 10,
}
function onServerClientError(ssl: boolean, socket: unknown, errorCode: number, rawPacket: ArrayBuffer) {
const self = this as Server;
let err;
switch (errorCode) {
case HttpParserError.HTTP_PARSER_ERROR_INVALID_CONTENT_LENGTH:
err = $HPE_UNEXPECTED_CONTENT_LENGTH("Parse Error");
break;
case HttpParserError.HTTP_PARSER_ERROR_INVALID_TRANSFER_ENCODING:
err = $HPE_INVALID_TRANSFER_ENCODING("Parse Error");
break;
case HttpParserError.HTTP_PARSER_ERROR_INVALID_EOF:
err = $HPE_INVALID_EOF_STATE("Parse Error");
break;
case HttpParserError.HTTP_PARSER_ERROR_INVALID_METHOD:
err = $HPE_INVALID_METHOD("Parse Error: Invalid method encountered");
err.bytesParsed = 1; // always 1 for now because is the first byte of the request line
break;
case HttpParserError.HTTP_PARSER_ERROR_INVALID_HEADER_TOKEN:
err = $HPE_INVALID_HEADER_TOKEN("Parse Error: Invalid header token encountered");
break;
case HttpParserError.HTTP_PARSER_ERROR_REQUEST_HEADER_FIELDS_TOO_LARGE:
err = $HPE_HEADER_OVERFLOW("Parse Error: Header overflow");
err.bytesParsed = rawPacket.byteLength;
break;
default:
err = $HPE_INTERNAL("Parse Error");
break;
}
err.rawPacket = rawPacket;
const nodeSocket = new NodeHTTPServerSocket(self, socket, ssl);
self.emit("connection", nodeSocket);
self.emit("clientError", err, nodeSocket);
if (nodeSocket.listenerCount("error") > 0) {
nodeSocket.emit("error", err);
}
}
const kBytesWritten = Symbol("kBytesWritten");
const kEnableStreaming = Symbol("kEnableStreaming");
const NodeHTTPServerSocket = class Socket extends Duplex {
bytesRead = 0;
connecting = false;
timeout = 0;
[kBytesWritten] = 0;
[kHandle];
server: Server;
_httpMessage;
_secureEstablished = false;
#pendingCallback = null;
constructor(server: Server, handle, encrypted) {
super();
this.server = server;
this[kHandle] = handle;
this._secureEstablished = !!handle?.secureEstablished;
handle.onclose = this.#onClose.bind(this);
handle.duplex = this;
this.encrypted = encrypted;
this.on("timeout", onNodeHTTPServerSocketTimeout);
}
get bytesWritten() {
const handle = this[kHandle];
return handle
? (handle.response?.getBytesWritten?.() ?? handle.bytesWritten ?? this[kBytesWritten] ?? 0)
: (this[kBytesWritten] ?? 0);
}
set bytesWritten(value) {
this[kBytesWritten] = value;
}
[kEnableStreaming](enable: boolean) {
const handle = this[kHandle];
if (handle) {
if (enable) {
handle.ondata = this.#onData.bind(this);
handle.ondrain = this.#onDrain.bind(this);
} else {
handle.ondata = undefined;
handle.ondrain = undefined;
}
}
}
#onDrain() {
const handle = this[kHandle];
this[kBytesWritten] = handle ? (handle.response?.getBytesWritten?.() ?? handle.bytesWritten ?? 0) : 0;
const callback = this.#pendingCallback;
if (callback) {
this.#pendingCallback = null;
(callback as Function)();
}
this.emit("drain");
}
#onData(chunk, last) {
if (chunk) {
this.push(chunk);
}
if (last) {
const handle = this[kHandle];
if (handle) {
handle.ondata = undefined;
}
this.push(null);
}
}
#closeHandle(handle, callback) {
this[kHandle] = undefined;
handle.onclose = this.#onCloseForDestroy.bind(this, callback);
handle.close();
// lets sync check and destroy the request if it's not complete
const message = this._httpMessage;
const req = message?.req;
if (req && !req.complete) {
// at this point the handle is not destroyed yet, lets destroy the request
req.destroy();
}
}
#onClose() {
this[kHandle] = null;
const message = this._httpMessage;
const req = message?.req;
if (req && !req.complete && !req[kHandle]?.upgraded) {
// At this point the socket is already destroyed; let's avoid UAF
req[kHandle] = undefined;
if (req.listenerCount("error") > 0) {
req.destroy(new ConnResetException("aborted"));
} else {
req.destroy();
}
}
this.emit("close");
}
#onCloseForDestroy(closeCallback) {
this.#onClose();
if ($isCallable(closeCallback)) closeCallback();
}
_onTimeout() {
const handle = this[kHandle];
const response = handle?.response;
// If there is a response, and it has pending data,
// we suppress the timeout because a write is in progress.
if (response && response.writableLength > 0) {
return;
}
this.emit("timeout");
}
_unrefTimer() {
// for compatibility
}
address() {
return this[kHandle]?.remoteAddress || null;
}
get bufferSize() {
return this.writableLength;
}
connect(_port, _host, _connectListener) {
return this;
}
_destroy(err, callback) {
const handle = this[kHandle];
if (!handle) {
if ($isCallable(callback)) callback(err);
return;
}
handle.ondata = undefined;
if (handle.closed) {
const onclose = handle.onclose;
handle.onclose = undefined;
if ($isCallable(onclose)) {
onclose.$call(handle);
}
if ($isCallable(callback)) callback(err);
return;
}
this.#closeHandle(handle, callback);
}
_final(callback) {
const handle = this[kHandle];
if (!handle) {
callback();
return;
}
handle.end();
callback();
}
get localAddress() {
return this[kHandle]?.localAddress?.address;
}
get localFamily() {
return this[kHandle]?.localAddress?.family;
}
get localPort() {
return this[kHandle]?.localAddress?.port;
}
get pending() {
return this.connecting;
}
#resumeSocket() {
const handle = this[kHandle];
const response = handle?.response;
if (response) {
const resumed = response.resume();
if (resumed && resumed !== true) {
const bodyReadState = handle.hasBody;
const message = this._httpMessage;
const req = message?.req;
if ((bodyReadState & NodeHTTPBodyReadState.done) !== 0) {
emitServerSocketEOFNT(this, req);
}
if (req) {
req.push(resumed);
}
this.push(resumed);
}
}
}
_read(_size) {
// https://github.com/nodejs/node/blob/13e3aef053776be9be262f210dc438ecec4a3c8d/lib/net.js#L725-L737
this.#resumeSocket();
}
get readyState() {
if (this.connecting) return "opening";
if (this.readable) {
return this.writable ? "open" : "readOnly";
} else {
return this.writable ? "writeOnly" : "closed";
}
}
ref() {
return this;
}
get remoteAddress() {
return this.address()?.address;
}
set remoteAddress(val) {
// initialize the object so that other properties wouldn't be lost
this.address().address = val;
}
get remotePort() {
return this.address()?.port;
}
set remotePort(val) {
// initialize the object so that other properties wouldn't be lost
this.address().port = val;
}
get remoteFamily() {
return this.address()?.family;
}
set remoteFamily(val) {
// initialize the object so that other properties wouldn't be lost
this.address().family = val;
}
resetAndDestroy() {}
setKeepAlive(_enable = false, _initialDelay = 0) {}
setNoDelay(_noDelay = true) {
return this;
}
setTimeout(_timeout, _callback) {
return this;
}
setEncoding(_encoding) {
const err = new Error("Changing the socket encoding is not allowed per RFC7230 Section 3.");
err.code = "ERR_HTTP_SOCKET_ENCODING";
throw err;
}
unref() {
return this;
}
_write(_chunk, _encoding, _callback) {
const handle = this[kHandle];
// only enable writting if we can drain
let err;
try {
if (handle && handle.ondrain && !handle.write(_chunk, _encoding)) {
this.#pendingCallback = _callback;
return false;
}
} catch (e) {
err = e;
}
if (err) _callback(err);
else _callback();
}
pause() {
const handle = this[kHandle];
const response = handle?.response;
if (response) {
response.pause();
}
return super.pause();
}
resume() {
this.#resumeSocket();
return super.resume();
}
get [kInternalSocketData]() {
return this[kHandle]?.response;
}
} as unknown as typeof import("node:net").Socket;
function _writeHead(statusCode, reason, obj, response) {
const originalStatusCode = statusCode;
let hasContentLength = response.hasHeader("content-length");
statusCode |= 0;
if (statusCode < 100 || statusCode > 999) {
throw $ERR_HTTP_INVALID_STATUS_CODE(format("%s", originalStatusCode));
}
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;
}
if (checkInvalidHeaderChar(response.statusMessage)) throw $ERR_INVALID_CHAR("statusMessage");
response.statusCode = statusCode;
{
// Slow-case: when progressive API and header fields are passed.
let k;
if ($isArray(obj)) {
const length = obj.length;
// Append all the headers provided in the array:
if (length && $isArray(obj[0])) {
for (let i = 0; i < length; i++) {
const k = obj[i];
if (k) response.appendHeader(k[0], k[1]);
}
} else {
if (length % 2 !== 0) {
throw $ERR_INVALID_ARG_VALUE("headers", obj);
}
// Test non-chunked message does not have trailer header set,
// message will be terminated by the first empty line after the
// header fields, regardless of the header fields present in the
// message, and thus cannot contain a message body or 'trailers'.
if (
(response.chunkedEncoding !== true || response.hasHeader("content-length")) &&
(response._trailer || response.hasHeader("trailer"))
) {
throw $ERR_HTTP_TRAILER_INVALID("Trailers are invalid with this transfer encoding");
}
// Headers in obj should override previous headers but still
// allow explicit duplicates. To do so, we first remove any
// existing conflicts, then use appendHeader.
for (let n = 0; n < length; n += 2) {
k = obj[n + 0];
response.removeHeader(k);
}
for (let n = 0; n < length; n += 2) {
k = obj[n];
if (k) response.appendHeader(k, obj[n + 1]);
}
}
} else if (obj) {
const keys = Object.keys(obj);
const length = keys.length;
// Retain for(;;) loop for performance reasons
// Refs: https://github.com/nodejs/node/pull/30958
for (let i = 0; i < length; i++) {
k = keys[i];
if (k) response.setHeader(k, obj[k]);
}
}
if (
(response.chunkedEncoding !== true || response.hasHeader("content-length")) &&
(response._trailer || response.hasHeader("trailer"))
) {
// remove the invalid content-length or trailer header
if (hasContentLength) {
response.removeHeader("trailer");
} else {
response.removeHeader("content-length");
}
throw $ERR_HTTP_TRAILER_INVALID("Trailers are invalid with this transfer encoding");
}
}
updateHasBody(response, statusCode);
}
Object.defineProperty(NodeHTTPServerSocket, "name", { value: "Socket" });
function ServerResponse(req, options): void {
if (!(this instanceof ServerResponse)) return new ServerResponse(req, options);
OutgoingMessage.$call(this, options);
this.useChunkedEncodingByDefault = true;
if ((this[kDeprecatedReplySymbol] = options?.[kDeprecatedReplySymbol])) {
this[controllerSymbol] = undefined;
this[firstWriteSymbol] = undefined;
this[deferredSymbol] = undefined;
this.write = ServerResponse_writeDeprecated;
this.end = ServerResponse_finalDeprecated;
}
this.req = req;
this.sendDate = true;
this._sent100 = false;
this[headerStateSymbol] = NodeHTTPHeaderState.none;
this[kPendingCallbacks] = [];
this.finished = false;
// 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;
if (options) {
const handle = options[kHandle];
if (handle) {
this[kHandle] = handle;
} else {
this[kHandle] = req[kHandle];
}
this[kRejectNonStandardBodyWrites] = options[kRejectNonStandardBodyWrites] ?? false;
} else {
this[kHandle] = req[kHandle];
}
this.statusCode = 200;
this.statusMessage = undefined;
this.chunkedEncoding = false;
}
$toClass(ServerResponse, "ServerResponse", OutgoingMessage);
ServerResponse.prototype._removedConnection = false;
ServerResponse.prototype._removedContLen = false;
ServerResponse.prototype._hasBody = true;
ServerResponse.prototype._ended = false;
ServerResponse.prototype[kRejectNonStandardBodyWrites] = undefined;
Object.defineProperty(ServerResponse.prototype, "headersSent", {
get() {
return (
this[headerStateSymbol] === NodeHTTPHeaderState.sent || this[headerStateSymbol] === NodeHTTPHeaderState.assigned
);
},
set(value) {
this[headerStateSymbol] = value ? NodeHTTPHeaderState.sent : NodeHTTPHeaderState.none;
},
});
ServerResponse.prototype._writeRaw = function (chunk, encoding, callback) {
return this.socket.write(chunk, encoding, callback);
};
ServerResponse.prototype.writeEarlyHints = function (hints, cb) {
let head = "HTTP/1.1 103 Early Hints\r\n";
validateObject(hints, "hints");
if (hints.link === null || hints.link === undefined) {
return;
}
const link = validateLinkHeaderValue(hints.link);
if (link.length === 0) {
return;
}
head += "Link: " + link + "\r\n";
for (const key of ObjectKeys(hints)) {
if (key !== "link") {
head += key + ": " + hints[key] + "\r\n";
}
}
head += "\r\n";
this._writeRaw(head, "ascii", cb);
};
ServerResponse.prototype.writeProcessing = function (cb) {
this._writeRaw("HTTP/1.1 102 Processing\r\n\r\n", "ascii", cb);
};
ServerResponse.prototype.writeContinue = function (cb) {
this.socket[kHandle]?.response?.writeContinue();
cb?.();
};
// This end method is actually on the OutgoingMessage prototype in Node.js
// But we don't want it for the fetch() response version.
ServerResponse.prototype.end = function (chunk, encoding, callback) {
const handle = this[kHandle];
if (handle?.aborted) {
return this;
}
if ($isCallable(chunk)) {
callback = chunk;
chunk = undefined;
encoding = undefined;
} else if ($isCallable(encoding)) {
callback = encoding;
encoding = undefined;
} else if (!$isCallable(callback)) {
callback = undefined;
}
if (hasServerResponseFinished(this, chunk, callback)) {
return this;
}
if (chunk && !this._hasBody) {
if (this[kRejectNonStandardBodyWrites]) {
throw $ERR_HTTP_BODY_NOT_ALLOWED();
} else {
// node.js just ignores the write in this case
chunk = undefined;
}
}
if (!handle) {
if ($isCallable(callback)) {
process.nextTick(callback);
}
return this;
}
const headerState = this[headerStateSymbol];
callWriteHeadIfObservable(this, headerState);
const flags = handle.flags;
if (!!(flags & NodeHTTPResponseFlags.closed_or_completed)) {
// node.js will return true if the handle is closed but the internal state is not
// and will not throw or emit an error
return true;
}
if (headerState !== NodeHTTPHeaderState.sent) {
handle.cork(() => {
handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]);
// If handle.writeHead throws, we don't want headersSent to be set to true.
// So we set it here.
this[headerStateSymbol] = NodeHTTPHeaderState.sent;
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/_http_outgoing.js#L987
this._contentLength = handle.end(chunk, encoding, undefined, strictContentLength(this));
});
} else {
// If there's no data but you already called end, then you're done.
// We can ignore it in that case.
if (!(!chunk && handle.ended) && !handle.aborted) {
handle.end(chunk, encoding, undefined, strictContentLength(this));
}
}
this._header = " ";
const req = this.req;
const socket = req.socket;
if (!req._consuming && !req?._readableState?.resumeScheduled) {
req._dump();
}
this.detachSocket(socket);
this.finished = true;
process.nextTick(self => {
self._ended = true;
}, this);
this.emit("prefinish");
this._callPendingCallbacks();
if (callback) {
process.nextTick(
function (callback, self) {
// In Node.js, the "finish" event triggers the "close" event.
// So it shouldn't become closed === true until after "finish" is emitted and the callback is called.
self.emit("finish");
try {
callback();
} catch (err) {
self.emit("error", err);
}
process.nextTick(emitCloseNT, self);
},
callback,
this,
);
} else {
process.nextTick(function (self) {
self.emit("finish");
process.nextTick(emitCloseNT, self);
}, this);
}
return this;
};
Object.defineProperty(ServerResponse.prototype, "writable", {
get() {
return !this._ended || !hasServerResponseFinished(this);
},
});
ServerResponse.prototype.write = function (chunk, encoding, callback) {
const handle = this[kHandle];
if ($isCallable(chunk)) {
callback = chunk;
chunk = undefined;
encoding = undefined;
} else if ($isCallable(encoding)) {
callback = encoding;
encoding = undefined;
} else if (!$isCallable(callback)) {
callback = undefined;
}
if (hasServerResponseFinished(this, chunk, callback)) {
return false;
}
if (chunk && !this._hasBody) {
if (this[kRejectNonStandardBodyWrites]) {
throw $ERR_HTTP_BODY_NOT_ALLOWED();
} else {
// node.js just ignores the write in this case
chunk = undefined;
}
}
let result = 0;
const headerState = this[headerStateSymbol];
callWriteHeadIfObservable(this, headerState);
if (!handle) {
if (this.socket) {
return this.socket.write(chunk, encoding, callback);
} else {
return OutgoingMessagePrototype.write.$call(this, chunk, encoding, callback);
}
}
const flags = handle.flags;
if (!!(flags & NodeHTTPResponseFlags.closed_or_completed)) {
// node.js will return true if the handle is closed but the internal state is not
// and will not throw or emit an error
return true;
}
if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) {
handle.cork(() => {
handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]);
// If handle.writeHead throws, we don't want headersSent to be set to true.
// So we set it here.
this[headerStateSymbol] = NodeHTTPHeaderState.sent;
result = handle.write(chunk, encoding, allowWritesToContinue.bind(this), strictContentLength(this));
});
} else {
result = handle.write(chunk, encoding, allowWritesToContinue.bind(this), strictContentLength(this));
}
if (result < 0) {
if (callback) {
// The write was buffered due to backpressure.
// We need to defer the callback until the write actually goes through.
this[kPendingCallbacks].push(callback);
}
return false;
}
this._callPendingCallbacks();
if (callback) {
process.nextTick(callback);
}
this.emit("drain");
return true;
};
ServerResponse.prototype._callPendingCallbacks = function () {
const originalLength = this[kPendingCallbacks].length;
for (let i = 0; i < originalLength; ++i) {
process.nextTick(this[kPendingCallbacks][i]);
}
if (this[kPendingCallbacks].length == originalLength) {
// If the array wasn't somehow appended to, just set it to an empty array
this[kPendingCallbacks] = [];
} else {
// Otherwise, splice it.
this[kPendingCallbacks].splice(0, originalLength);
}
};
ServerResponse.prototype._finish = function () {
this.emit("prefinish");
};
ServerResponse.prototype.detachSocket = function (socket) {
if (socket._httpMessage === this) {
if (socket[kCloseCallback]) socket[kCloseCallback] = undefined;
socket.removeListener("close", onServerResponseClose);
socket._httpMessage = null;
}
this.socket = null;
};
ServerResponse.prototype._implicitHeader = function () {
if (this.headersSent) return;
// @ts-ignore
this.writeHead(this.statusCode);
};
Object.defineProperty(ServerResponse.prototype, "writableNeedDrain", {
get() {
return !this.destroyed && !this.finished && (this[kHandle]?.bufferedAmount ?? 1) !== 0;
},
});
Object.defineProperty(ServerResponse.prototype, "writableFinished", {
get() {
return !!(this.finished && (!this[kHandle] || this[kHandle].finished));
},
});
Object.defineProperty(ServerResponse.prototype, "writableLength", {
get() {
return this.writableFinished ? 0 : (this[kHandle]?.bufferedAmount ?? 0);
},
});
Object.defineProperty(ServerResponse.prototype, "writableHighWaterMark", {
get() {
return 64 * 1024;
},
});
Object.defineProperty(ServerResponse.prototype, "closed", {
get() {
return this._closed;
},
});
ServerResponse.prototype._send = function (data, encoding, callback, _byteLength) {
const handle = this[kHandle];
if (!handle) {
return OutgoingMessagePrototype._send.$apply(this, arguments);
}
if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) {
handle.cork(() => {
handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]);
this[headerStateSymbol] = NodeHTTPHeaderState.sent;
handle.write(data, encoding, callback, strictContentLength(this));
});
} else {
handle.write(data, encoding, callback, strictContentLength(this));
}
};
ServerResponse.prototype.writeHead = function (statusCode, statusMessage, headers) {
if (this.headersSent) {
throw $ERR_HTTP_HEADERS_SENT("writeHead");
}
_writeHead(statusCode, statusMessage, headers, this);
this[headerStateSymbol] = NodeHTTPHeaderState.assigned;
return this;
};
ServerResponse.prototype.assignSocket = function (socket) {
if (socket._httpMessage) {
throw $ERR_HTTP_SOCKET_ASSIGNED("Socket already assigned");
}
socket._httpMessage = this;
socket.once("close", onServerResponseClose);
this.socket = socket;
this.emit("socket", socket);
};
Object.defineProperty(ServerResponse.prototype, "shouldKeepAlive", {
get() {
return this[kHandle]?.shouldKeepAlive ?? true;
},
set(_value) {
// throw new Error('not implemented');
},
});
ServerResponse.prototype.destroy = function (_err?: Error) {
if (this.destroyed) return this;
const handle = this[kHandle];
this.destroyed = true;
if (handle) {
handle.abort();
}
this?.socket?.destroy();
this.emit("close");
return this;
};
ServerResponse.prototype.emit = function (event) {
if (event === "close") {
callCloseCallback(this);
}
return Stream.prototype.emit.$apply(this, arguments);
};
ServerResponse.prototype.flushHeaders = function () {
if (this[headerStateSymbol] === NodeHTTPHeaderState.sent) return; // Should be idempotent.
if (this[headerStateSymbol] !== NodeHTTPHeaderState.assigned) this._implicitHeader();
const handle = this[kHandle];
if (handle) {
if (this[headerStateSymbol] === NodeHTTPHeaderState.assigned) {
this[headerStateSymbol] = NodeHTTPHeaderState.sent;
handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]);
}
handle.flushHeaders();
}
};
function updateHasBody(response, statusCode) {
// 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.
if (statusCode === 204 || statusCode === 304 || (statusCode >= 100 && statusCode <= 199)) {
response._hasBody = false;
} else {
response._hasBody = true;
}
}
function emitServerSocketEOF(self, req) {
self.push(null);
if (req) {
req.push(null);
req.complete = true;
}
}
function emitServerSocketEOFNT(self, req) {
if (req) {
req[eofInProgress] = true;
}
process.nextTick(emitServerSocketEOF, self);
}
let OriginalWriteHeadFn, OriginalImplicitHeadFn;
function callWriteHeadIfObservable(self, headerState) {
if (
headerState === NodeHTTPHeaderState.none &&
!(self.writeHead === OriginalWriteHeadFn && self._implicitHeader === OriginalImplicitHeadFn)
) {
self.writeHead(self.statusCode, self.statusMessage, self[headersSymbol]);
}
}
function allowWritesToContinue() {
this._callPendingCallbacks();
this.emit("drain");
}
function drainHeadersIfObservable() {
if (this._implicitHeader === OriginalImplicitHeadFn && this.writeHead === OriginalWriteHeadFn) {
return;
}
this._implicitHeader();
}
function ServerResponse_finalDeprecated(chunk, encoding, callback) {
if ($isCallable(encoding)) {
callback = encoding;
encoding = undefined;
}
if (!$isCallable(callback)) {
callback = undefined;
}
if (this.destroyed || this.finished) {
if (chunk) {
emitErrorNextTickIfErrorListenerNT(this, $ERR_STREAM_WRITE_AFTER_END(), callback);
}
return false;
}
if (encoding && encoding !== "buffer") {
chunk = Buffer.from(chunk, encoding);
}
const req = this.req;
const shouldEmitClose = req && req.emit && !this.finished;
if (!this.headersSent) {
let data = this[firstWriteSymbol];
if (chunk) {
if (data) {
if (encoding) {
data = Buffer.from(data, encoding);
}
data = new Blob([data, chunk]);
} else {
data = chunk;
}
} else if (!data) {
data = undefined;
} else {
data = new Blob([data]);
}
this[firstWriteSymbol] = undefined;
this.finished = true;
this.headersSent = true; // https://github.com/oven-sh/bun/issues/3458
drainHeadersIfObservable.$call(this);
this[kDeprecatedReplySymbol](
new Response(data, {
headers: this[headersSymbol],
status: this.statusCode,
statusText: this.statusMessage ?? STATUS_CODES[this.statusCode],
}),
);
if (shouldEmitClose) {
req.complete = true;
process.nextTick(emitRequestCloseNT, req);
}
callback?.();
return;
}
this.finished = true;
ensureReadableStreamController.$call(this, controller => {
if (chunk && encoding) {
chunk = Buffer.from(chunk, encoding);
}
let prom;
if (chunk) {
controller.write(chunk);
prom = controller.end();
} else {
prom = controller.end();
}
const handler = () => {
callback();
const deferred = this[deferredSymbol];
if (deferred) {
this[deferredSymbol] = undefined;
deferred();
}
};
if ($isPromise(prom)) prom.then(handler, handler);
else handler();
});
}
// ServerResponse.prototype._final = ServerResponse_finalDeprecated;
ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;
OriginalWriteHeadFn = ServerResponse.prototype.writeHead;
OriginalImplicitHeadFn = ServerResponse.prototype._implicitHeader;
function storeHTTPOptions(options) {
this[kIncomingMessage] = options.IncomingMessage || IncomingMessage;
this[kServerResponse] = options.ServerResponse || ServerResponse;
const maxHeaderSize = options.maxHeaderSize;
if (maxHeaderSize !== undefined) validateInteger(maxHeaderSize, "maxHeaderSize", 0);
this.maxHeaderSize = maxHeaderSize;
const insecureHTTPParser = options.insecureHTTPParser;
if (insecureHTTPParser !== undefined) validateBoolean(insecureHTTPParser, "options.insecureHTTPParser");
this.insecureHTTPParser = insecureHTTPParser;
const requestTimeout = options.requestTimeout;
if (requestTimeout !== undefined) {
validateInteger(requestTimeout, "requestTimeout", 0);
this.requestTimeout = requestTimeout;
} else {
this.requestTimeout = 300_000; // 5 minutes
}
const headersTimeout = options.headersTimeout;
if (headersTimeout !== undefined) {
validateInteger(headersTimeout, "headersTimeout", 0);
this.headersTimeout = headersTimeout;
} else {
this.headersTimeout = MathMin(60_000, this.requestTimeout); // Minimum between 60 seconds or requestTimeout
}
if (this.requestTimeout > 0 && this.headersTimeout > 0 && this.headersTimeout > this.requestTimeout) {
throw $ERR_OUT_OF_RANGE("headersTimeout", "<= requestTimeout", headersTimeout);
}
const keepAliveTimeout = options.keepAliveTimeout;
if (keepAliveTimeout !== undefined) {
validateInteger(keepAliveTimeout, "keepAliveTimeout", 0);
this.keepAliveTimeout = keepAliveTimeout;
} else {
this.keepAliveTimeout = 5_000; // 5 seconds;
}
const connectionsCheckingInterval = options.connectionsCheckingInterval;
if (connectionsCheckingInterval !== undefined) {
validateInteger(connectionsCheckingInterval, "connectionsCheckingInterval", 0);
this.connectionsCheckingInterval = connectionsCheckingInterval;
} else {
this.connectionsCheckingInterval = 30_000; // 30 seconds
}
const requireHostHeader = options.requireHostHeader;
if (requireHostHeader !== undefined) {
validateBoolean(requireHostHeader, "options.requireHostHeader");
this.requireHostHeader = requireHostHeader;
} else {
this.requireHostHeader = true;
}
const joinDuplicateHeaders = options.joinDuplicateHeaders;
if (joinDuplicateHeaders !== undefined) {
validateBoolean(joinDuplicateHeaders, "options.joinDuplicateHeaders");
}
this.joinDuplicateHeaders = joinDuplicateHeaders;
const rejectNonStandardBodyWrites = options.rejectNonStandardBodyWrites;
if (rejectNonStandardBodyWrites !== undefined) {
validateBoolean(rejectNonStandardBodyWrites, "options.rejectNonStandardBodyWrites");
this.rejectNonStandardBodyWrites = rejectNonStandardBodyWrites;
} else {
this.rejectNonStandardBodyWrites = false;
}
}
function ensureReadableStreamController(run) {
const thisController = this[controllerSymbol];
if (thisController) return run(thisController);
this.headersSent = true;
let firstWrite = this[firstWriteSymbol];
const old_run = this[runSymbol];
if (old_run) {
old_run.push(run);
return;
}
this[runSymbol] = [run];
this[kDeprecatedReplySymbol](
new Response(
new ReadableStream({
type: "direct",
pull: controller => {
this[controllerSymbol] = controller;
if (firstWrite) controller.write(firstWrite);
firstWrite = undefined;
for (let run of this[runSymbol]) {
run(controller);
}
if (!this.finished) {
const { promise, resolve } = $newPromiseCapability(GlobalPromise);
this[deferredSymbol] = resolve;
return promise;
}
},
}),
{
headers: this[headersSymbol],
status: this.statusCode,
statusText: this.statusMessage ?? STATUS_CODES[this.statusCode],
},
),
);
}
export default {
Server,
ServerResponse,
kConnectionsCheckingInterval,
};