'use strict';
const {
ArrayIsArray,
Error,
MathMin,
ObjectKeys,
ObjectSetPrototypeOf,
ReflectApply,
Symbol,
SymbolFor,
} = primordials;
const net = require('net');
const EE = require('events');
const assert = require('internal/assert');
const {
parsers,
freeParser,
continueExpression,
chunkExpression,
kIncomingMessage,
HTTPParser,
isLenient,
_checkInvalidHeaderChar: checkInvalidHeaderChar,
prepareError,
} = require('_http_common');
const { ConnectionsList } = internalBinding('http_parser');
const {
kUniqueHeaders,
parseUniqueHeadersOption,
OutgoingMessage,
} = require('_http_outgoing');
const {
kOutHeaders,
kNeedDrain,
isTraceHTTPEnabled,
traceBegin,
traceEnd,
getNextTraceEventId,
} = require('internal/http');
const {
defaultTriggerAsyncIdScope,
getOrSetAsyncId,
} = require('internal/async_hooks');
const { IncomingMessage } = require('_http_incoming');
const {
ConnResetException,
codes,
} = require('internal/errors');
const {
ERR_HTTP_REQUEST_TIMEOUT,
ERR_HTTP_HEADERS_SENT,
ERR_HTTP_INVALID_STATUS_CODE,
ERR_HTTP_SOCKET_ENCODING,
ERR_HTTP_SOCKET_ASSIGNED,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CHAR,
} = codes;
const {
kEmptyObject,
promisify,
SymbolAsyncDispose,
} = require('internal/util');
const {
validateInteger,
validateBoolean,
validateLinkHeaderValue,
validateObject,
} = require('internal/validators');
const Buffer = require('buffer').Buffer;
const { setInterval, clearInterval } = require('timers');
let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
debug = fn;
});
const dc = require('diagnostics_channel');
const onRequestStartChannel = dc.channel('http.server.request.start');
const onResponseFinishChannel = dc.channel('http.server.response.finish');
const kServerResponse = Symbol('ServerResponse');
const kServerResponseStatistics = Symbol('ServerResponseStatistics');
const {
hasObserver,
startPerf,
stopPerf,
} = require('internal/perf/observe');
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',
};
const kOnExecute = HTTPParser.kOnExecute | 0;
const kOnTimeout = HTTPParser.kOnTimeout | 0;
const kLenientAll = HTTPParser.kLenientAll | 0;
const kLenientNone = HTTPParser.kLenientNone | 0;
const kConnections = Symbol('http.server.connections');
const kConnectionsCheckingInterval = Symbol('http.server.connectionsCheckingInterval');
const HTTP_SERVER_TRACE_EVENT_NAME = 'http.server.request';
const HTTP_SERVER_KEEP_ALIVE_TIMEOUT_BUFFER = 1000;
class HTTPServerAsyncResource {
constructor(type, socket) {
this.type = type;
this.socket = socket;
}
}
function ServerResponse(req, options) {
OutgoingMessage.call(this, options);
if (req.method === 'HEAD') this._hasBody = false;
this.req = req;
this.sendDate = true;
this._sent100 = false;
this._expect_continue = false;
if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {
this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te);
this.shouldKeepAlive = false;
}
if (hasObserver('http')) {
startPerf(this, kServerResponseStatistics, {
type: 'http',
name: 'HttpRequest',
detail: {
req: {
method: req.method,
url: req.url,
headers: req.headers,
},
},
});
}
if (isTraceHTTPEnabled()) {
this._traceEventId = getNextTraceEventId();
traceBegin(HTTP_SERVER_TRACE_EVENT_NAME, this._traceEventId);
}
}
ObjectSetPrototypeOf(ServerResponse.prototype, OutgoingMessage.prototype);
ObjectSetPrototypeOf(ServerResponse, OutgoingMessage);
ServerResponse.prototype._finish = function _finish() {
if (this[kServerResponseStatistics] && hasObserver('http')) {
stopPerf(this, kServerResponseStatistics, {
detail: {
res: {
statusCode: this.statusCode,
statusMessage: this.statusMessage,
headers: typeof this.getHeaders === 'function' ? this.getHeaders() : {},
},
},
});
}
OutgoingMessage.prototype._finish.call(this);
if (isTraceHTTPEnabled() && typeof this._traceEventId === 'number') {
const data = {
url: this.req?.url,
statusCode: this.statusCode,
};
traceEnd(HTTP_SERVER_TRACE_EVENT_NAME, this._traceEventId, data);
}
};
ServerResponse.prototype.statusCode = 200;
ServerResponse.prototype.statusMessage = undefined;
function onServerResponseClose() {
if (this._httpMessage) {
emitCloseNT(this._httpMessage);
}
}
ServerResponse.prototype.assignSocket = function assignSocket(socket) {
if (socket._httpMessage) {
throw new ERR_HTTP_SOCKET_ASSIGNED();
}
socket._httpMessage = this;
socket.on('close', onServerResponseClose);
this.socket = socket;
this.emit('socket', socket);
this._flush();
};
ServerResponse.prototype.detachSocket = function detachSocket(socket) {
assert(socket._httpMessage === this);
socket.removeListener('close', onServerResponseClose);
socket._httpMessage = null;
this.socket = null;
};
ServerResponse.prototype.writeContinue = function writeContinue(cb) {
this._writeRaw('HTTP/1.1 100 Continue\r\n\r\n', 'ascii', cb);
this._sent100 = true;
};
ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
this._writeRaw('HTTP/1.1 102 Processing\r\n\r\n', 'ascii', cb);
};
ServerResponse.prototype.writeEarlyHints = function writeEarlyHints(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._implicitHeader = function _implicitHeader() {
this.writeHead(this.statusCode);
};
ServerResponse.prototype.writeHead = writeHead;
function writeHead(statusCode, reason, obj) {
if (this._header) {
throw new ERR_HTTP_HEADERS_SENT('write');
}
const originalStatusCode = statusCode;
statusCode |= 0;
if (statusCode < 100 || statusCode > 999) {
throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode);
}
if (typeof reason === 'string') {
this.statusMessage = reason;
} else {
if (!this.statusMessage)
this.statusMessage = STATUS_CODES[statusCode] || 'unknown';
obj ??= reason;
}
this.statusCode = statusCode;
let headers;
if (this[kOutHeaders]) {
let k;
if (ArrayIsArray(obj)) {
if (obj.length % 2 !== 0) {
throw new ERR_INVALID_ARG_VALUE('headers', obj);
}
for (let n = 0; n < obj.length; n += 2) {
k = obj[n + 0];
if (k) this.setHeader(k, obj[n + 1]);
}
} else if (obj) {
const keys = ObjectKeys(obj);
for (let i = 0; i < keys.length; i++) {
k = keys[i];
if (k) this.setHeader(k, obj[k]);
}
}
headers = this[kOutHeaders];
} else {
headers = obj;
}
if (checkInvalidHeaderChar(this.statusMessage))
throw new ERR_INVALID_CHAR('statusMessage');
const statusLine = `HTTP/1.1 ${statusCode} ${this.statusMessage}\r\n`;
if (statusCode === 204 || statusCode === 304 ||
(statusCode >= 100 && statusCode <= 199)) {
this._hasBody = false;
}
if (this._expect_continue && !this._sent100) {
this.shouldKeepAlive = false;
}
this._storeHeader(statusLine, headers);
return this;
}
ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;
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;
}
const headersTimeout = options.headersTimeout;
if (headersTimeout !== undefined) {
validateInteger(headersTimeout, 'headersTimeout', 0);
this.headersTimeout = headersTimeout;
} else {
this.headersTimeout = MathMin(60_000, this.requestTimeout);
}
if (this.requestTimeout > 0 && this.headersTimeout > 0 && this.headersTimeout > this.requestTimeout) {
throw new codes.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;
}
const connectionsCheckingInterval = options.connectionsCheckingInterval;
if (connectionsCheckingInterval !== undefined) {
validateInteger(connectionsCheckingInterval, 'connectionsCheckingInterval', 0);
this.connectionsCheckingInterval = connectionsCheckingInterval;
} else {
this.connectionsCheckingInterval = 30_000;
}
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 setupConnectionsTracking() {
if (!this[kConnections]) {
this[kConnections] = new ConnectionsList();
}
if (this[kConnectionsCheckingInterval]) {
clearInterval(this[kConnectionsCheckingInterval]);
}
this[kConnectionsCheckingInterval] =
setInterval(checkConnections.bind(this), this.connectionsCheckingInterval).unref();
}
function httpServerPreClose(server) {
server.closeIdleConnections();
clearInterval(server[kConnectionsCheckingInterval]);
}
function Server(options, requestListener) {
if (!(this instanceof Server)) return new Server(options, requestListener);
if (typeof options === 'function') {
requestListener = options;
options = kEmptyObject;
} else if (options == null) {
options = kEmptyObject;
} else {
validateObject(options, 'options');
}
storeHTTPOptions.call(this, options);
net.Server.call(
this,
{ allowHalfOpen: true, noDelay: options.noDelay ?? true,
keepAlive: options.keepAlive,
keepAliveInitialDelay: options.keepAliveInitialDelay,
highWaterMark: options.highWaterMark });
if (requestListener) {
this.on('request', requestListener);
}
this.httpAllowHalfOpen = false;
this.on('connection', connectionListener);
this.on('listening', setupConnectionsTracking);
this.timeout = 0;
this.maxHeadersCount = null;
this.maxRequestsPerSocket = 0;
this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders);
}
ObjectSetPrototypeOf(Server.prototype, net.Server.prototype);
ObjectSetPrototypeOf(Server, net.Server);
Server.prototype.close = function() {
httpServerPreClose(this);
ReflectApply(net.Server.prototype.close, this, arguments);
return this;
};
Server.prototype[SymbolAsyncDispose] = async function() {
return promisify(this.close).call(this);
};
Server.prototype.closeAllConnections = function() {
if (!this[kConnections]) {
return;
}
const connections = this[kConnections].all();
for (let i = 0, l = connections.length; i < l; i++) {
connections[i].socket.destroy();
}
};
Server.prototype.closeIdleConnections = function() {
if (!this[kConnections]) {
return;
}
const connections = this[kConnections].idle();
for (let i = 0, l = connections.length; i < l; i++) {
if (connections[i].socket._httpMessage && !connections[i].socket._httpMessage.finished) {
continue;
}
connections[i].socket.destroy();
}
};
Server.prototype.setTimeout = function setTimeout(msecs, callback) {
this.timeout = msecs;
if (callback)
this.on('timeout', callback);
return this;
};
Server.prototype[EE.captureRejectionSymbol] = function(err, event, ...args) {
switch (event) {
case 'request': {
const { 1: res } = args;
if (!res.headersSent && !res.writableEnded) {
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[SymbolFor('nodejs.rejection')]
.apply(this, arguments);
}
};
function checkConnections() {
if (this.headersTimeout === 0 && this.requestTimeout === 0) {
return;
}
const expired = this[kConnections].expired(this.headersTimeout, this.requestTimeout);
for (let i = 0; i < expired.length; i++) {
const socket = expired[i].socket;
if (socket) {
onRequestTimeout(socket);
}
}
}
function connectionListener(socket) {
defaultTriggerAsyncIdScope(
getOrSetAsyncId(socket), connectionListenerInternal, this, socket,
);
}
function connectionListenerInternal(server, socket) {
debug('SERVER new http connection');
socket.server = server;
if (server.timeout && typeof socket.setTimeout === 'function')
socket.setTimeout(server.timeout);
socket.on('timeout', socketOnTimeout);
const parser = parsers.alloc();
const lenient = server.insecureHTTPParser === undefined ?
isLenient() : server.insecureHTTPParser;
parser.initialize(
HTTPParser.REQUEST,
new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket),
server.maxHeaderSize || 0,
lenient ? kLenientAll : kLenientNone,
server[kConnections],
);
parser.socket = socket;
socket.parser = parser;
if (typeof server.maxHeadersCount === 'number') {
parser.maxHeaderPairs = server.maxHeadersCount << 1;
}
const state = {
onData: null,
onEnd: null,
onClose: null,
onDrain: null,
outgoing: [],
incoming: [],
outgoingData: 0,
requestsCount: 0,
keepAliveTimeoutSet: false,
};
state.onData = socketOnData.bind(undefined,
server, socket, parser, state);
state.onEnd = socketOnEnd.bind(undefined,
server, socket, parser, state);
state.onClose = socketOnClose.bind(undefined,
socket, state);
state.onDrain = socketOnDrain.bind(undefined,
socket, state);
socket.on('data', state.onData);
socket.on('error', socketOnError);
socket.on('end', state.onEnd);
socket.on('close', state.onClose);
socket.on('drain', state.onDrain);
parser.onIncoming = parserOnIncoming.bind(undefined,
server, socket, state);
socket.on('resume', onSocketResume);
socket.on('pause', onSocketPause);
socket.on = generateSocketListenerWrapper('on');
socket.addListener = generateSocketListenerWrapper('addListener');
socket.prependListener = generateSocketListenerWrapper('prependListener');
socket.setEncoding = socketSetEncoding;
if (socket._handle && socket._handle.isStreamBase &&
!socket._handle._consumed) {
parser._consumed = true;
socket._handle._consumed = true;
parser.consume(socket._handle);
}
parser[kOnExecute] =
onParserExecute.bind(undefined,
server, socket, parser, state);
parser[kOnTimeout] =
onParserTimeout.bind(undefined,
server, socket);
socket._paused = false;
}
function socketSetEncoding() {
throw new ERR_HTTP_SOCKET_ENCODING();
}
function updateOutgoingData(socket, state, delta) {
state.outgoingData += delta;
socketOnDrain(socket, state);
}
function socketOnDrain(socket, state) {
const needPause = state.outgoingData > socket.writableHighWaterMark;
if (socket._paused && !needPause) {
socket._paused = false;
if (socket.parser)
socket.parser.resume();
socket.resume();
}
const msg = socket._httpMessage;
if (msg && !msg.finished && msg[kNeedDrain]) {
msg[kNeedDrain] = false;
msg.emit('drain');
}
}
function socketOnTimeout() {
const req = this.parser && this.parser.incoming;
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 socketOnClose(socket, state) {
debug('server socket close');
freeParser(socket.parser, null, socket);
abortIncoming(state.incoming);
}
function abortIncoming(incoming) {
while (incoming.length) {
const req = incoming.shift();
req.destroy(new ConnResetException('aborted'));
}
}
function socketOnEnd(server, socket, parser, state) {
const ret = parser.finish();
if (ret instanceof Error) {
debug('parse error');
socketOnError.call(socket, ret);
} else if (!server.httpAllowHalfOpen) {
socket.end();
} else if (state.outgoing.length) {
state.outgoing[state.outgoing.length - 1]._last = true;
} else if (socket._httpMessage) {
socket._httpMessage._last = true;
} else {
socket.end();
}
}
function socketOnData(server, socket, parser, state, d) {
assert(!socket._paused);
debug('SERVER socketOnData %d', d.length);
const ret = parser.execute(d);
onParserExecuteCommon(server, socket, parser, state, ret, d);
}
function onRequestTimeout(socket) {
socketOnError.call(socket, new ERR_HTTP_REQUEST_TIMEOUT());
}
function onParserExecute(server, socket, parser, state, ret) {
socket._unrefTimer();
debug('SERVER socketOnParserExecute %d', ret);
onParserExecuteCommon(server, socket, parser, state, ret, undefined);
}
function onParserTimeout(server, socket) {
const serverTimeout = server.emit('timeout', socket);
if (!serverTimeout)
socket.destroy();
}
const noop = () => {};
const badRequestResponse = Buffer.from(
`HTTP/1.1 400 ${STATUS_CODES[400]}\r\n` +
'Connection: close\r\n\r\n', 'ascii',
);
const requestTimeoutResponse = Buffer.from(
`HTTP/1.1 408 ${STATUS_CODES[408]}\r\n` +
'Connection: close\r\n\r\n', 'ascii',
);
const requestHeaderFieldsTooLargeResponse = Buffer.from(
`HTTP/1.1 431 ${STATUS_CODES[431]}\r\n` +
'Connection: close\r\n\r\n', 'ascii',
);
const requestChunkExtensionsTooLargeResponse = Buffer.from(
`HTTP/1.1 413 ${STATUS_CODES[413]}\r\n` +
'Connection: close\r\n\r\n', 'ascii',
);
function socketOnError(e) {
this.removeListener('error', socketOnError);
if (this.listenerCount('error', noop) === 0) {
this.on('error', noop);
}
if (!this.server.emit('clientError', e, this)) {
if (this.writable &&
(!this._httpMessage || !this._httpMessage._headerSent)) {
let response;
switch (e.code) {
case 'HPE_HEADER_OVERFLOW':
response = requestHeaderFieldsTooLargeResponse;
break;
case 'HPE_CHUNK_EXTENSIONS_OVERFLOW':
response = requestChunkExtensionsTooLargeResponse;
break;
case 'ERR_HTTP_REQUEST_TIMEOUT':
response = requestTimeoutResponse;
break;
default:
response = badRequestResponse;
break;
}
this.write(response);
}
this.destroy(e);
}
}
function onParserExecuteCommon(server, socket, parser, state, ret, d) {
resetSocketTimeout(server, socket, state);
if (ret instanceof Error) {
prepareError(ret, parser, d);
debug('parse error', ret);
socketOnError.call(socket, ret);
} else if (parser.incoming && parser.incoming.upgrade) {
const req = parser.incoming;
debug('SERVER upgrade or connect', req.method);
if (!d)
d = parser.getCurrentBuffer();
socket.removeListener('data', state.onData);
socket.removeListener('end', state.onEnd);
socket.removeListener('close', state.onClose);
socket.removeListener('drain', state.onDrain);
socket.removeListener('error', socketOnError);
socket.removeListener('timeout', socketOnTimeout);
unconsume(parser, socket);
parser.finish();
freeParser(parser, req, socket);
parser = null;
const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
if (eventName === 'upgrade' || server.listenerCount(eventName) > 0) {
debug('SERVER have listener for %s', eventName);
const bodyHead = d.slice(ret, d.length);
socket.readableFlowing = null;
server.emit(eventName, req, socket, bodyHead);
} else {
socket.destroy();
}
} else if (parser.incoming && parser.incoming.method === 'PRI') {
debug('SERVER got PRI request');
socket.destroy();
}
if (socket._paused && socket.parser) {
debug('pause parser');
socket.parser.pause();
}
}
function clearIncoming(req) {
req = req || this;
const parser = req.socket && req.socket.parser;
if (parser && parser.incoming === req) {
if (req.readableEnded) {
parser.incoming = null;
} else {
req.on('end', clearIncoming);
}
}
}
function resOnFinish(req, res, socket, state, server) {
if (onResponseFinishChannel.hasSubscribers) {
onResponseFinishChannel.publish({
request: req,
response: res,
socket,
server,
});
}
assert(state.incoming.length === 0 || state.incoming[0] === req);
state.incoming.shift();
if (!req._consuming && !req._readableState.resumeScheduled)
req._dump();
res.detachSocket(socket);
clearIncoming(req);
process.nextTick(emitCloseNT, res);
if (res._last) {
if (typeof socket.destroySoon === 'function') {
socket.destroySoon();
} else {
socket.end();
}
} else if (state.outgoing.length === 0) {
if (server.keepAliveTimeout && typeof socket.setTimeout === 'function') {
socket.setTimeout(server.keepAliveTimeout + HTTP_SERVER_KEEP_ALIVE_TIMEOUT_BUFFER);
state.keepAliveTimeoutSet = true;
}
} else {
const m = state.outgoing.shift();
if (m) {
m.assignSocket(socket);
}
}
}
function emitCloseNT(self) {
if (!self._closed) {
self.destroyed = true;
self._closed = true;
self.emit('close');
}
}
function parserOnIncoming(server, socket, state, req, keepAlive) {
resetSocketTimeout(server, socket, state);
if (req.upgrade) {
req.upgrade = req.method === 'CONNECT' ||
server.listenerCount('upgrade') > 0;
if (req.upgrade)
return 2;
}
state.incoming.push(req);
if (!socket._paused) {
const ws = socket._writableState;
if (ws.needDrain || state.outgoingData >= socket.writableHighWaterMark) {
socket._paused = true;
socket.pause();
}
}
const res = new server[kServerResponse](req,
{
highWaterMark: socket.writableHighWaterMark,
rejectNonStandardBodyWrites: server.rejectNonStandardBodyWrites,
});
res._keepAliveTimeout = server.keepAliveTimeout;
res._maxRequestsPerSocket = server.maxRequestsPerSocket;
res._onPendingData = updateOutgoingData.bind(undefined,
socket, state);
res.shouldKeepAlive = keepAlive;
res[kUniqueHeaders] = server[kUniqueHeaders];
if (onRequestStartChannel.hasSubscribers) {
onRequestStartChannel.publish({
request: req,
response: res,
socket,
server,
});
}
if (socket._httpMessage) {
state.outgoing.push(res);
} else {
res.assignSocket(socket);
}
res.on('finish',
resOnFinish.bind(undefined,
req, res, socket, state, server));
let handled = false;
if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) {
if (server.requireHostHeader && req.headers.host === undefined) {
res.writeHead(400, ['Connection', 'close']);
res.end();
return 0;
}
const isRequestsLimitSet = (
typeof server.maxRequestsPerSocket === 'number' &&
server.maxRequestsPerSocket > 0
);
if (isRequestsLimitSet) {
state.requestsCount++;
res.maxRequestsOnConnectionReached = (
server.maxRequestsPerSocket <= state.requestsCount);
}
if (isRequestsLimitSet &&
(server.maxRequestsPerSocket < state.requestsCount)) {
handled = true;
server.emit('dropRequest', req, socket);
res.writeHead(503);
res.end();
} else if (req.headers.expect !== undefined) {
handled = true;
if (continueExpression.test(req.headers.expect)) {
res._expect_continue = true;
if (server.listenerCount('checkContinue') > 0) {
server.emit('checkContinue', req, res);
} else {
res.writeContinue();
server.emit('request', req, res);
}
} else if (server.listenerCount('checkExpectation') > 0) {
server.emit('checkExpectation', req, res);
} else {
res.writeHead(417);
res.end();
}
}
}
if (!handled) {
server.emit('request', req, res);
}
return 0;
}
function resetSocketTimeout(server, socket, state) {
if (!state.keepAliveTimeoutSet)
return;
socket.setTimeout(server.timeout || 0);
state.keepAliveTimeoutSet = false;
}
function onSocketResume() {
if (this._paused) {
this.pause();
return;
}
if (this._handle && !this._handle.reading) {
this._handle.reading = true;
this._handle.readStart();
}
}
function onSocketPause() {
if (this._handle && this._handle.reading) {
this._handle.reading = false;
this._handle.readStop();
}
}
function unconsume(parser, socket) {
if (socket._handle) {
if (parser._consumed)
parser.unconsume();
parser._consumed = false;
socket.removeListener('pause', onSocketPause);
socket.removeListener('resume', onSocketResume);
}
}
function generateSocketListenerWrapper(originalFnName) {
return function socketListenerWrap(ev, fn) {
const res = net.Socket.prototype[originalFnName].call(this,
ev, fn);
if (!this.parser) {
this.on = net.Socket.prototype.on;
this.addListener = net.Socket.prototype.addListener;
this.prependListener = net.Socket.prototype.prependListener;
return res;
}
if (ev === 'data' || ev === 'readable')
unconsume(this.parser, this);
return res;
};
}
module.exports = {
STATUS_CODES,
Server,
ServerResponse,
setupConnectionsTracking,
storeHTTPOptions,
_connectionListener: connectionListener,
kServerResponse,
httpServerPreClose,
kConnectionsCheckingInterval,
};