/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/node/polyfills/_http_server.js
1 663 строки
47 KB
Kenta Moriuchi
chore: make prefer-primordials lint an internal plugin (#35978)
23 июл 2026, 11:53
Не верифицирован
23 июл 2026, 11:53
5b55d08
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. // 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. // Ported from Node.js lib/_http_server.js import { core, primordials } from "ext:core/mod.js"; const { ArrayIsArray, ArrayPrototypeIncludes, ArrayPrototypePush, ArrayPrototypeShift, ArrayPrototypeSlice, Error, ErrorPrototype, FunctionPrototypeApply, FunctionPrototypeBind, FunctionPrototypeCall, MathMin, NumberIsFinite, ObjectKeys, ObjectPrototypeIsPrototypeOf, ObjectSetPrototypeOf, SafeArrayIterator, SafeMap, SafeMapIterator, SafeSet, SafeSetIterator, String, StringPrototypeIncludes, StringPrototypeIndexOf, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeStartsWith, Symbol, TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetByteLength, TypedArrayPrototypeGetByteOffset, } = primordials; import net from "node:net"; const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs"); const { ok: assert } = core.loadExtScript("ext:deno_node/assert.ts"); import { _checkInvalidHeaderChar as checkInvalidHeaderChar, chunkExpression, continueExpression, freeParser, HTTPParser, isLenient, kIncomingMessage, parsers, prepareError, } from "node:_http_common"; import { kUniqueHeaders, OutgoingMessage, parseUniqueHeadersOption, validateHeaderName, validateHeaderValue, } from "node:_http_outgoing"; const { kNeedDrain, kOutHeaders } = core.loadExtScript( "ext:deno_node/internal/http.ts", ); import { IncomingMessage } from "node:_http_incoming"; const { connResetException, ERR_HTTP_HEADERS_SENT, ERR_HTTP_SOCKET_ASSIGNED, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_INVALID_CHAR, ERR_OUT_OF_RANGE, } = core.loadExtScript("ext:deno_node/internal/errors.ts"); const { kEmptyObject } = core.loadExtScript("ext:deno_node/internal/util.mjs"); const { kDestroy, kTimeout, suspendTimeout, } = core.loadExtScript("ext:deno_node/internal/timers.mjs"); const { validateBoolean, validateFunction, validateInteger, validateLinkHeaderValue, validateObject, } = core.loadExtScript("ext:deno_node/internal/validators.mjs"); const { nextTick } = core.loadExtScript("ext:deno_node/_next_tick.ts"); const { enterAsyncResourceIfActive, exitAsyncResourceIfActive, } = core.loadExtScript("ext:deno_node/internal/async_hooks.ts"); const { enqueueNodePerformanceEntry, hasNodeObserverForType } = core .loadExtScript( "ext:deno_node/perf_hooks.js", ); import { applyAddressOverride, notifyAddressOverrideServing, SERVER_KIND_NODE_HTTP2, startOverrideListener, } from "ext:deno_node/internal/http/address_override.js"; const { otelState, builtinTracer, ContextManager, telemetry, } = core.loadExtScript("ext:deno_telemetry/telemetry.ts"); const { channel } = core.loadExtScript("ext:deno_node/diagnostics_channel.js"); const onServerRequestStartChannel = channel("http.server.request.start"); const onServerResponseCreatedChannel = channel("http.server.response.created"); const onServerResponseFinishChannel = channel("http.server.response.finish"); const kServerResponse = Symbol("ServerResponse"); const kConnectionsKey = Symbol("http.server.connections"); const kConnectionsCheckingInterval = Symbol( "http.server.connectionsCheckingInterval", ); const kOtelSpan = Symbol("kOtelSpan"); const kOtelStartTime = Symbol("kOtelStartTime"); const kOtelReqBodySize = Symbol("kOtelReqBodySize"); const kPerfStartTime = Symbol("kPerfStartTime"); // OTel server metrics - lazy initialized let otelMetrics = null; function getOtelMetrics() { if (otelMetrics) return otelMetrics; const meter = telemetry.meterProvider.getMeter("deno.http.server"); otelMetrics = { activeRequests: meter.createUpDownCounter("http.server.active_requests", { description: "Number of active HTTP server requests.", unit: "{request}", }), requestDuration: meter.createHistogram("http.server.request.duration", { description: "Duration of HTTP server requests.", unit: "s", advice: { explicitBucketBoundaries: [ 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ], }, }), requestBodySize: meter.createHistogram("http.server.request.body.size", { description: "Size of HTTP server request bodies.", unit: "By", }), responseBodySize: meter.createHistogram("http.server.response.body.size", { description: "Size of HTTP server response bodies.", unit: "By", }), }; return otelMetrics; } const kLenientAll = HTTPParser.kLenientAll | 0; const kLenientNone = HTTPParser.kLenientNone | 0; const kOnExecute = HTTPParser.kOnExecute | 0; const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0; const _kOnTimeout = HTTPParser.kOnTimeout | 0; // JS-based ConnectionsList matching Node's native ConnectionsList. // Tracks active connections and their request start times for // headersTimeout / requestTimeout enforcement. class ConnectionsList { constructor() { this._all = new SafeSet(); this._active = new SafeMap(); // socket -> { headersCompleted, startTime, req } } add(socket) { this._all.add(socket); } remove(socket) { this._all.delete(socket); this._active.delete(socket); } pushActive(socket) { this._active.set(socket, { headersCompleted: false, startTime: performance.now(), req: null, }); } popActive(socket) { this._active.delete(socket); } // For pipelined requests the parser fires kOnMessageBegin for the next // request before the previous response finishes, replacing the active // entry. resOnFinish must only clear the entry if it still tracks the // request whose response just finished, not a pipelined successor. popActiveIfReq(socket, req) { const entry = this._active.get(socket); if (entry && entry.req === req) { this._active.delete(socket); } } markHeadersCompleted(socket, req) { const entry = this._active.get(socket); if (entry) { entry.headersCompleted = true; entry.req = req; } } expired(headersTimeout, requestTimeout) { const now = performance.now(); const result = []; for (const { 0: socket, 1: entry } of new SafeMapIterator(this._active)) { const elapsed = now - entry.startTime; if (!entry.headersCompleted && headersTimeout > 0) { if (elapsed >= headersTimeout) { ArrayPrototypePush(result, { socket }); continue; } } if (requestTimeout > 0 && elapsed >= requestTimeout) { // requestTimeout only covers receiving the entire request from the // client. Once the full request message has been parsed off the wire // (req.complete is set in parserOnMessageComplete), the clock stops, // mirroring Node resetting last_message_start_ in on_message_complete. // Without this an actively streaming response (SSE/proxy) gets aborted // at requestTimeout even though the request was long since received. if (entry.req?.complete) { continue; } ArrayPrototypePush(result, { socket }); } } return result; } } function onRequestTimeout(socket) { const err = new Error("ERR_HTTP_REQUEST_TIMEOUT"); err.code = "ERR_HTTP_REQUEST_TIMEOUT"; FunctionPrototypeCall(socketOnError, socket, err); } 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", }; // ---- ServerResponse ---- function ServerResponse(req, options) { FunctionPrototypeCall(OutgoingMessage, 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 (onServerResponseCreatedChannel.hasSubscribers) { onServerResponseCreatedChannel.publish({ request: req, response: this, }); } } ObjectSetPrototypeOf(ServerResponse.prototype, OutgoingMessage.prototype); ObjectSetPrototypeOf(ServerResponse, OutgoingMessage); 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; if (socket._parent) { socket._parent._httpMessage = this; socket._parent._httpMessageDetached = false; } 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; socket._httpMessageDetached = true; if (socket._parent) { socket._parent._httpMessage = null; socket._parent._httpMessageDetached = true; } 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.writeInformation = function writeInformation( statusCode, headers, cb, ) { if (this._header) { throw new ERR_HTTP_HEADERS_SENT("write"); } if (typeof headers === "function") { cb = headers; headers = undefined; } validateInteger(statusCode, "statusCode", 100, 199); let head = `HTTP/1.1 ${statusCode} ${ STATUS_CODES[statusCode] || "unknown" }\r\n`; if (headers !== null && headers !== undefined) { const keys = ObjectKeys(headers); for (let i = 0; i < keys.length; i++) { const key = keys[i]; validateHeaderName(key); const value = headers[key]; validateHeaderValue(key, value); head += key + ": " + value + "\r\n"; } } head += "\r\n"; this._writeRaw(head, "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; } if (checkInvalidHeaderChar(link)) { throw new ERR_INVALID_CHAR("header content", "Link"); } head += "Link: " + link + "\r\n"; const keys = ObjectKeys(hints); for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (key !== "link") { validateHeaderName(key); const value = hints[key]; validateHeaderValue(key, value); head += key + ": " + value + "\r\n"; } } head += "\r\n"; this._writeRaw(head, "ascii", cb); }; ServerResponse.prototype._implicitHeader = function _implicitHeader() { this.writeHead(this.statusCode); }; ServerResponse.prototype.writeHead = function writeHead( statusCode, reason, obj, ) { if (this._header) { throw new ERR_HTTP_HEADERS_SENT("write"); } statusCode |= 0; if (statusCode < 100 || statusCode > 999) { throw new ERR_INVALID_ARG_TYPE( "statusCode", "integer [100, 999]", statusCode, ); } if (typeof reason === "string") { this.statusMessage = reason; } else { this.statusMessage ||= STATUS_CODES[statusCode] || "unknown"; obj ??= reason; } this.statusCode = statusCode; // Enforce no body for 204 and 304 responses if (statusCode === 204 || statusCode === 304) { this._hasBody = false; } let headers; if (this[kOutHeaders]) { // Slow-case: progressive API and header fields are passed. 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) { const k = obj[n + 0]; if (k) this.removeHeader(k); } for (let n = 0; n < obj.length; n += 2) { const k = obj[n + 0]; if (k) this.appendHeader(k, obj[n + 1]); } } else if (obj) { const keys = ObjectKeys(obj); for (let i = 0; i < keys.length; i++) { const k = keys[i]; if (k) this.setHeader(k, obj[k]); } } headers = this[kOutHeaders]; } else { // Only writeHead() called - pass raw headers to _storeHeader headers = obj; } if (checkInvalidHeaderChar(this.statusMessage)) { throw new ERR_INVALID_CHAR("statusMessage"); } const statusLine = "HTTP/1.1 " + statusCode + " " + this.statusMessage + "\r\n"; this._storeHeader(statusLine, headers); return this; }; function emitCloseNT(self) { self.destroyed = true; self._closed = true; self.emit("close"); } // ---- Server ---- function connectionListener(socket) { connectionListenerInternal(this, socket); } function connectionListenerInternal(server, socket) { socket.server = server; // Track connections via ConnectionsList for timeout enforcement if (!server[kConnectionsKey]) server[kConnectionsKey] = new ConnectionsList(); const connections = server[kConnectionsKey]; connections.add(socket); const onConnectionClose = () => connections.remove(socket); socket.on("close", onConnectionClose); 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, {}, server.maxHeaderSize || 0, lenient ? kLenientAll : kLenientNone, ); parser.socket = socket; socket.parser = parser; if (typeof server.maxHeadersCount === "number") { parser.maxHeaderPairs = server.maxHeadersCount << 1; } const state = { onData: null, onEnd: null, onClose: null, onConnectionClose: onConnectionClose, onDrain: null, outgoing: [], incoming: [], outgoingData: 0, requestsCount: 0, keepAliveTimeoutSet: false, keepAliveTimeout: null, keepAliveTimeoutMsecs: 0, keepAliveTimeoutSuspended: false, }; state.onData = FunctionPrototypeBind( socketOnData, undefined, server, socket, parser, state, ); state.onEnd = FunctionPrototypeBind( socketOnEnd, undefined, server, socket, parser, state, ); state.onClose = FunctionPrototypeBind( socketOnClose, undefined, socket, state, ); state.onDrain = FunctionPrototypeBind( socketOnDrain, 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 = FunctionPrototypeBind( parserOnIncoming, undefined, server, socket, state, ); // Mark this connection as active for timeout tracking connections.pushActive(socket); // Try to consume the socket handle for direct parser reads if ( socket._handle?.isStreamBase && !socket._handle._consumed ) { parser._consumed = true; socket._handle._consumed = true; parser.consume(socket._handle); } parser[kOnExecute] = FunctionPrototypeBind( onParserExecute, undefined, server, socket, parser, state, ); // Reset timeout-tracking state at the start of each HTTP message so // headersTimeout/requestTimeout are measured per-request on keepalive // connections (mirrors Node's native on_message_begin behavior). parser[kOnMessageBegin] = FunctionPrototypeBind( onParserMessageBegin, undefined, server, socket, ); socket._paused = false; } function onParserMessageBegin(server, socket) { const connections = server[kConnectionsKey]; if (connections) { connections.popActive(socket); connections.pushActive(socket); } } function onParserExecute(server, socket, parser, state, ret, d) { // Don't refresh the socket timeout while the connection is idling in // keep-alive mode (waiting for the next request). Stray bytes like // `\r\n` between requests must not reset keepAliveTimeout. The timer // is reset explicitly via resetSocketTimeout once a new request // actually begins. if (!state.keepAliveTimeoutSet) { socket._unrefTimer?.(); } // The consume path (parser.consume(handle)) passes `d` as a bare // Uint8Array from the C++ binding. onParserExecuteCommon's upgrade // branch does `d.slice(bytesParsed).toString()` and expects the // Buffer `.toString(encoding)` semantics, not the plain Uint8Array // behavior. Wrap to match the non-consume path where `d` came from // `socket.on('data')` as a Buffer. if (d !== undefined && !Buffer.isBuffer(d)) { d = Buffer.from( TypedArrayPrototypeGetBuffer(d), TypedArrayPrototypeGetByteOffset(d), TypedArrayPrototypeGetByteLength(d), ); } if (d !== undefined) { parser._lastRawPacket = d; } onParserExecuteCommon(server, socket, parser, state, ret, d); } function socketOnTimeout() { const req = 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) { destroySuspendedKeepAliveTimeout(state); freeParser(socket.parser, undefined, socket); abortIncoming(state.incoming); } function abortIncoming(incoming) { while (incoming.length) { const req = ArrayPrototypeShift(incoming); req.destroy(connResetException("aborted")); } } function socketOnEnd(server, socket, parser, state) { const ret = parser.finish(); if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, ret)) { prepareError(ret, parser, parser._lastRawPacket); FunctionPrototypeCall(socketOnError, socket, ret); return; } if (!server.httpAllowHalfOpen) { abortIncoming(state.incoming); if (socket.writable) socket.end(); } else if (state.outgoing.length) { state.outgoing[state.outgoing.length - 1]._last = true; } else if (socket._httpMessage) { socket._httpMessage._last = true; } else if (socket.writable) { socket.end(); } } function socketOnData(server, socket, parser, state, d) { assert(googLength(d)); parser._lastRawPacket = d; const ret = parser.execute(d); onParserExecuteCommon(server, socket, parser, state, ret, d); } function googLength(d) { return d.length || TypedArrayPrototypeGetByteLength(d); } function onParserExecuteCommon(server, socket, parser, state, ret, d) { resetSocketTimeout(server, socket, state); if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, ret)) { prepareError(ret, parser, d); FunctionPrototypeCall(socketOnError, socket, ret); return; } // If the parser is paused after headers, unpause on next tick if (parser.incoming?.upgrade) { const bytesParsed = ret; const req = parser.incoming; const eventName = req.method === "CONNECT" ? "connect" : "upgrade"; if (server.listenerCount(eventName) === 0) { socket.destroy(); return; } // Detach the socket from the server by removing all server-added listeners. // After this point the socket is fully owned by the connect/upgrade handler. socket.removeListener("data", state.onData); socket.removeListener("end", state.onEnd); socket.removeListener("close", state.onClose); socket.removeListener("close", state.onConnectionClose); socket.removeListener("drain", state.onDrain); socket.removeListener("error", socketOnError); socket.removeListener("timeout", socketOnTimeout); // Remove from connection tracking (normally done by the close listener) const connections = server[kConnectionsKey]; if (connections) connections.remove(socket); parser.finish(); freeParser(parser, req, socket); // deno-lint-ignore deno-internal/prefer-primordials -- d is a Node Buffer; Buffer.prototype.slice returns a Buffer view const bodyHead = d.slice(bytesParsed); socket.readableFlowing = null; server.emit(eventName, req, socket, bodyHead); } } function resetSocketTimeout(server, socket, state, allowKeepAliveReuse = true) { if (!state.keepAliveTimeoutSet) return; const keepAliveTimeout = state.keepAliveTimeout; if ( allowKeepAliveReuse && server.timeout === 0 && socket.setTimeout === net.Socket.prototype.setTimeout && socket[kTimeout] === keepAliveTimeout && ArrayPrototypeIncludes(socket.listeners("timeout"), socketOnTimeout) ) { suspendTimeout(keepAliveTimeout); socket[kTimeout] = null; socket.timeout = 0; state.keepAliveTimeoutSet = false; state.keepAliveTimeoutSuspended = true; return; } socket.setTimeout(server.timeout || 0); state.keepAliveTimeoutSet = false; state.keepAliveTimeout = null; state.keepAliveTimeoutMsecs = 0; state.keepAliveTimeoutSuspended = false; } function socketOnDrain(socket, state) { const needPause = state.outgoingData > socket.writableHighWaterMark; if (socket._paused && !needPause) { socket._paused = false; if (socket.parser) { socket.resume(); } } const msg = socket._httpMessage; if (msg && !msg.finished && msg[kNeedDrain]) { msg[kNeedDrain] = false; msg.emit("drain"); } } function destroySuspendedKeepAliveTimeout(state) { if (!state.keepAliveTimeoutSuspended) return; const keepAliveTimeout = state.keepAliveTimeout; if (keepAliveTimeout !== null) { keepAliveTimeout[kDestroy](); } state.keepAliveTimeout = null; state.keepAliveTimeoutMsecs = 0; state.keepAliveTimeoutSuspended = false; } const badRequestResponse = "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"; const requestHeaderFieldsTooLargeResponse = "HTTP/1.1 431 Request Header Fields Too Large\r\nConnection: close\r\n\r\n"; const requestTimeoutResponse = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n"; function socketOnError(e) { // Ignore further errors this.removeListener("error", socketOnError); this.on("error", noop); if (this.parser) { this.parser.finish(); freeParser(this.parser, undefined, this); } if (!this.server.emit("clientError", e, this)) { // No clientError listener - send an appropriate error response if ( this.writable && (!this._httpMessage || !this._httpMessage._headerSent) ) { let response; switch (e.code) { case "HPE_HEADER_OVERFLOW": response = requestHeaderFieldsTooLargeResponse; break; case "ERR_HTTP_REQUEST_TIMEOUT": response = requestTimeoutResponse; break; default: response = badRequestResponse; break; } this.write(response); } this.destroy(e); } } function noop() {} function updateOutgoingData(socket, state, delta) { state.outgoingData += delta; if ( socket._paused && state.outgoingData < socket.writableHighWaterMark ) { return; } socketOnDrain(socket, state); } // ---- parserOnIncoming: creates ServerResponse, emits 'request' ---- function parserOnIncoming(server, socket, state, req, keepAlive) { // Connections accepted via the DENO_SERVE_ADDRESS override carry // absolute-form request targets (the control plane forwards the full // public URL, which is how Deno.serve() reconstructs request.url). // Node applications expect origin-form, so strip scheme and authority. if ( socket.isDenoServeAddressOverride && (StringPrototypeStartsWith(req.url, "http://") || StringPrototypeStartsWith(req.url, "https://")) ) { const schemeEnd = StringPrototypeIndexOf(req.url, "://") + 3; const pathStart = StringPrototypeIndexOf(req.url, "/", schemeEnd); req.url = pathStart === -1 ? "/" : StringPrototypeSlice(req.url, pathStart); } resetSocketTimeout(server, socket, state, !req.upgrade); // Headers have been fully parsed; clear the headersTimeout watchdog and // bind the active entry to this request so resOnFinish can identify it // even if a pipelined successor has already replaced it. const connections = server[kConnectionsKey]; if (connections) connections.markHeadersCompleted(socket, req); if (req.upgrade && req.method !== "CONNECT") { if ( server.shouldUpgradeCallback !== undefined && !server.shouldUpgradeCallback(req) ) { req.upgrade = false; } } if (req.upgrade) { req.upgrade = req.method === "CONNECT" || true; if (req.upgrade) return 0; } ArrayPrototypePush(state.incoming, req); if (hasNodeObserverForType("http")) { req[kPerfStartTime] = performance.now(); } 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, { rejectNonStandardBodyWrites: server.rejectNonStandardBodyWrites, highWaterMark: server.highWaterMark, }); res._keepAliveTimeout = server.keepAliveTimeout; res._maxRequestsPerSocket = server.maxRequestsPerSocket; res._onPendingData = FunctionPrototypeBind( updateOutgoingData, undefined, socket, state, ); res.shouldKeepAlive = keepAlive; res[kUniqueHeaders] = server[kUniqueHeaders]; if (server.optimizeEmptyRequests && isRequestKnownEmpty(req)) { req._dumpAndCloseReadable(); } // Start OTel server span and metrics if (otelState.TRACING_ENABLED) { // Extract trace context from incoming request headers let context = ContextManager.active(); if (otelState.PROPAGATORS.length > 0) { for ( const propagator of new SafeArrayIterator(otelState.PROPAGATORS) ) { context = propagator.extract(context, req.headers, { get(carrier, key) { return carrier[key]; }, keys(carrier) { return ObjectKeys(carrier); }, }); } } // Create server span within extracted context, without modifying async context const span = builtinTracer().startSpan(req.method, { kind: 1 }, context); // SpanKind.SERVER = 1 const url = req.url || "/"; const host = req.headers?.host || "localhost"; const scheme = socket.encrypted ? "https" : "http"; span.setAttribute("http.request.method", req.method); span.setAttribute("url.full", `${scheme}://${host}${url}`); span.setAttribute("url.scheme", scheme); span.setAttribute("url.path", StringPrototypeSplit(url, "?")[0]); span.setAttribute( "url.query", StringPrototypeIncludes(url, "?") ? StringPrototypeSplit(url, "?")[1] : "", ); res[kOtelSpan] = span; } if (otelState.METRICS_ENABLED) { res[kOtelStartTime] = performance.now(); res[kOtelReqBodySize] = 0; const metrics = getOtelMetrics(); const scheme = socket.encrypted ? "https" : "http"; metrics.activeRequests.add(1, { "http.request.method": req.method, "url.scheme": scheme, }); } if (socket._httpMessage) { ArrayPrototypePush(state.outgoing, res); } else { res.assignSocket(socket); } res.on( "finish", FunctionPrototypeBind( resOnFinish, undefined, req, res, socket, state, server, ), ); if (onServerRequestStartChannel.hasSubscribers) { onServerRequestStartChannel.publish({ request: req, response: res, socket, server, }); } // Enter a new async-hooks resource scope for the duration of the request // emission. Each request gets its own resource (the IncomingMessage), so // executionAsyncResource() returns a per-request object that is preserved // across timers, await transitions, etc. Without this, every request would // share the top-level resource and concurrent requests would race on any // state stashed there. const prevAsyncResource = enterAsyncResourceIfActive(req); try { let handled = false; if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) { if ( server.requireHostHeader !== false && 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); } } finally { exitAsyncResourceIfActive(prevAsyncResource); } return 0; } function isRequestKnownEmpty(req) { return req.headers["content-length"] === undefined && req.headers["transfer-encoding"] === undefined; } function resOnFinish(req, res, socket, state, server) { if (onServerResponseFinishChannel.hasSubscribers) { onServerResponseFinishChannel.publish({ request: req, response: res, socket, server, }); } assert(state.incoming.length === 0 || state.incoming[0] === req); // End OTel server span const span = res[kOtelSpan]; if (span) { span.setAttribute("http.response.status_code", String(res.statusCode)); if (res.statusCode >= 500) { span.setAttribute("error.type", String(res.statusCode)); span.setStatus({ code: 2 }); } span.end(); res[kOtelSpan] = null; } // Emit HttpRequest perf entry const perfStartTime = req[kPerfStartTime]; if (perfStartTime !== undefined && hasNodeObserverForType("http")) { enqueueNodePerformanceEntry({ name: "HttpRequest", entryType: "http", startTime: perfStartTime, duration: performance.now() - perfStartTime, detail: { req: { method: req.method, url: req.url || "/", headers: req.headers, }, res: { statusCode: res.statusCode, statusMessage: res.statusMessage || STATUS_CODES[res.statusCode] || "", headers: res.getHeaders(), }, }, }); } // Record OTel server metrics if (res[kOtelStartTime] !== undefined) { const durationS = (performance.now() - res[kOtelStartTime]) / 1000; const scheme = socket.encrypted ? "https" : "http"; const metricAttrs = { "http.request.method": req.method, "http.response.status_code": res.statusCode, "network.protocol.version": `${req.httpVersionMajor}.${req.httpVersionMinor}`, "url.scheme": scheme, }; const metrics = getOtelMetrics(); metrics.activeRequests.add(-1, { "http.request.method": req.method, "url.scheme": scheme, }); metrics.requestDuration.record(durationS, metricAttrs); metrics.requestBodySize.record(res[kOtelReqBodySize] || 0, metricAttrs); metrics.responseBodySize.record(0, metricAttrs); res[kOtelStartTime] = undefined; } ArrayPrototypeShift(state.incoming); if (!req._consuming && !req._readableState?.resumeScheduled) { req._dump(); } res.detachSocket(socket); clearIncoming(req); nextTick(emitCloseNT, res); // The request is done; stop tracking it for headersTimeout/requestTimeout. // Only pop if the entry still belongs to this completed request: when // requests are pipelined, the parser has already pushed a fresh entry // for the next request via kOnMessageBegin. const connections = server[kConnectionsKey]; if (connections) connections.popActiveIfReq(socket, req); if (res._last) { if (typeof socket.destroySoon === "function") { socket.destroySoon(); } else { socket.end(); } } else if (state.outgoing.length === 0) { // If the server is closing, destroy the socket instead of // setting a keep-alive timeout (prevents timer leaks). if (!server.listening) { socket.destroy(); } else { const keepAliveTimeout = NumberIsFinite(server.keepAliveTimeout) && server.keepAliveTimeout >= 0 ? server.keepAliveTimeout : 0; if (keepAliveTimeout) { const timeoutMsecs = keepAliveTimeout + 1000; const suspendedTimeout = state.keepAliveTimeout; if ( state.keepAliveTimeoutSuspended && socket.setTimeout === net.Socket.prototype.setTimeout && socket[kTimeout] === null && state.keepAliveTimeoutMsecs === timeoutMsecs ) { socket[kTimeout] = suspendedTimeout; socket.timeout = timeoutMsecs; suspendedTimeout.refresh(); } else { if (state.keepAliveTimeoutSuspended) { suspendedTimeout[kDestroy](); } socket.setTimeout(timeoutMsecs); state.keepAliveTimeout = socket[kTimeout]; state.keepAliveTimeoutMsecs = timeoutMsecs; } state.keepAliveTimeoutSet = true; state.keepAliveTimeoutSuspended = false; } else if ( state.keepAliveTimeoutSuspended ) { state.keepAliveTimeout[kDestroy](); state.keepAliveTimeout = null; state.keepAliveTimeoutMsecs = 0; state.keepAliveTimeoutSuspended = false; } } } else { const m = ArrayPrototypeShift(state.outgoing); if (m) { m.assignSocket(socket); } } } function clearIncoming(req) { req ||= this; const parser = req.socket?.parser; // Reset the .incoming property so that the request object can be gc'ed. if (parser && parser.incoming === req) { if (req.readableEnded) { parser.incoming = null; } else { req.on("end", clearIncoming); } } } function Server(options, requestListener) { if (!ObjectPrototypeIsPrototypeOf(Server.prototype, this)) { return new Server(options, requestListener); } if (typeof options === "function") { requestListener = options; options = kEmptyObject; } else if (options == null) { options = kEmptyObject; } else { validateObject(options, "options"); } FunctionPrototypeCall(storeHTTPOptions, this, options); FunctionPrototypeCall(net.Server, 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); } // Wraps a deno_core system interval ID so the stored handle exposes a // Node-compatible `_destroyed` flag. Tests (and Node user code) read // `server[kConnectionsCheckingInterval]._destroyed` to confirm the // interval was cleared. class ConnectionsCheckingInterval { _timerId = 0; _destroyed = false; } function setupConnectionsTracking() { this[kConnectionsKey] ||= new ConnectionsList(); destroyConnectionsCheckingInterval(this[kConnectionsCheckingInterval]); const interval = this.connectionsCheckingInterval || 30_000; const handle = new ConnectionsCheckingInterval(); handle._timerId = core.createSystemInterval( FunctionPrototypeBind(checkConnections, this), interval, ); this[kConnectionsCheckingInterval] = handle; } function destroyConnectionsCheckingInterval(handle) { if (handle && !handle._destroyed) { core.cancelTimer(handle._timerId); handle._destroyed = true; } } function checkConnections() { if (this.headersTimeout === 0 && this.requestTimeout === 0) { return; } const expired = this[kConnectionsKey].expired( this.headersTimeout, this.requestTimeout, ); for (let i = 0; i < expired.length; i++) { const socket = expired[i].socket; if (socket) { onRequestTimeout(socket); } } } function httpServerPreClose(server) { server.closeIdleConnections(); destroyConnectionsCheckingInterval(server[kConnectionsCheckingInterval]); } function storeHTTPOptions(options) { this[kIncomingMessage] = options.IncomingMessage || IncomingMessage; this[kServerResponse] = options.ServerResponse || ServerResponse; const highWaterMark = options.highWaterMark; if (highWaterMark !== undefined) { validateInteger(highWaterMark, "options.highWaterMark", 1); } this.highWaterMark = highWaterMark; 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 optimizeEmptyRequests = options.optimizeEmptyRequests; if (optimizeEmptyRequests !== undefined) { validateBoolean( optimizeEmptyRequests, "options.optimizeEmptyRequests", ); } this.optimizeEmptyRequests = optimizeEmptyRequests; 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); } if ( this.requestTimeout > 0 && this.headersTimeout > 0 && this.headersTimeout > this.requestTimeout ) { throw new 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", 1, ); 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; } const shouldUpgradeCallback = options.shouldUpgradeCallback; if (shouldUpgradeCallback !== undefined) { validateFunction( shouldUpgradeCallback, "options.shouldUpgradeCallback", ); this.shouldUpgradeCallback = shouldUpgradeCallback; } else { this.shouldUpgradeCallback = function () { return this.listenerCount("upgrade") > 0; }; } } ObjectSetPrototypeOf(Server.prototype, net.Server.prototype); ObjectSetPrototypeOf(Server, net.Server); // Applies the DENO_SERVE_ADDRESS override before delegating to // net.Server.prototype.listen. // // TCP overrides rewrite the address passed to listen(). Non-TCP and // duplicate overrides spin up a separate Deno listener that feeds // synthetic "connection" events into this server. Server.prototype.listen = function listen(...args) { const applied = applyAddressOverride(); switch (applied.mode) { case "none": return FunctionPrototypeApply(net.Server.prototype.listen, this, args); case "tcp": { // Rewrite the listen args so the normal net.Server code binds // to the override address instead of what the app requested. let cb; const last = args[args.length - 1]; if (typeof last === "function") { cb = last; args = ArrayPrototypeSlice(args, 0, -1); } const rewritten = [{ host: applied.host, port: applied.port }]; if (cb) ArrayPrototypePush(rewritten, cb); this.once("listening", notifyAddressOverrideServing); return FunctionPrototypeApply( net.Server.prototype.listen, this, rewritten, ); } case "override-only": { // Don't bind the app-supplied TCP address at all -- the override // listener is the only way into this server. `listening` on // net.Server is derived from `_handle`, so install a stub that // reports listening without owning a real OS handle. let cb; const last = args[args.length - 1]; if (typeof last === "function") cb = last; if (cb) this.once("listening", cb); this._handle = { close() {}, ref() {}, unref() {}, }; startOverrideListener(this, applied.override, connectionListener); nextTick(() => this.emit("listening")); return this; } case "duplicate": { startOverrideListener(this, applied.override, connectionListener); return FunctionPrototypeApply(net.Server.prototype.listen, this, args); } } }; Server.prototype.setTimeout = function setTimeout(msecs, callback) { this.timeout = msecs; if (callback) { this.on("timeout", callback); } return this; }; Server.prototype.close = function close(cb) { httpServerPreClose(this); return FunctionPrototypeCall(net.Server.prototype.close, this, cb); }; Server.prototype.closeAllConnections = function closeAllConnections() { const connections = this[kConnectionsKey]; if (connections) { for (const socket of new SafeSetIterator(connections._all)) { socket.destroy(); } } }; Server.prototype.closeIdleConnections = function closeIdleConnections() { const connections = this[kConnectionsKey]; if (connections) { for (const socket of new SafeSetIterator(connections._all)) { // A socket is idle if it completed a request-response cycle and // currently has no active HTTP response being written. Sockets // that have never finished a response (e.g. still receiving // headers) are not idle. if (!socket._httpMessage && socket._httpMessageDetached) { socket.destroy(); } } } }; export { applyAddressOverride, connectionListener as _connectionListener, httpServerPreClose, kConnectionsCheckingInterval, kIncomingMessage, kServerResponse, notifyAddressOverrideServing, Server, SERVER_KIND_NODE_HTTP2, ServerResponse, setupConnectionsTracking, startOverrideListener, STATUS_CODES, storeHTTPOptions, }; export default { _connectionListener: connectionListener, httpServerPreClose, kConnectionsCheckingInterval, kIncomingMessage, kServerResponse, Server, ServerResponse, setupConnectionsTracking, STATUS_CODES, storeHTTPOptions, };