Commit 12ce81920a1 for nodejs

commit 12ce81920a1f526e681153c0cb4b1456867c328d
Author: Sebastian Beltran <bjohansebas@gmail.com>
Date:   Wed Oct 14 19:52:34 2026 -0500

    lib: deprecate _http_*

    Signed-off-by: Sebastian Beltran <bjohansebas@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/58535
    Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
    Reviewed-By: Tim Perry <pimterry@gmail.com>
    Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
    Reviewed-By: Aviv Keller <me@aviv.sh>

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 2e8dac324f7..2a27e611350 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -73,6 +73,7 @@
 /lib/https.js @nodejs/crypto @nodejs/net @nodejs/http
 /src/node_http_common* @nodejs/http @nodejs/http2 @nodejs/net
 /src/node_http_parser.cc @nodejs/http @nodejs/net
+/lib/internal/http/* @nodejs/http @nodejs/net

 # http2

diff --git a/benchmark/http/check_invalid_header_char.js b/benchmark/http/check_invalid_header_char.js
index bd250cd5fe0..5d1ffe74e58 100644
--- a/benchmark/http/check_invalid_header_char.js
+++ b/benchmark/http/check_invalid_header_char.js
@@ -1,7 +1,6 @@
 'use strict';

 const common = require('../common.js');
-const _checkInvalidHeaderChar = require('_http_common')._checkInvalidHeaderChar;

 const groupedInputs = {
   // Representative set of inputs from an AcmeAir benchmark run:
@@ -51,9 +50,13 @@ const inputs = [
 const bench = common.createBenchmark(main, {
   input: inputs.concat(Object.keys(groupedInputs)),
   n: [1e6],
+}, {
+  flags: ['--expose-internals', '--no-warnings'],
 });

 function main({ n, input }) {
+  const checkInvalidHeaderChar = require('internal/http/common')._checkInvalidHeaderChar;
+
   let inputs = [input];
   if (Object.hasOwn(groupedInputs, input)) {
     inputs = groupedInputs[input];
@@ -62,7 +65,7 @@ function main({ n, input }) {
   const len = inputs.length;
   bench.start();
   for (let i = 0; i < n; i++) {
-    _checkInvalidHeaderChar(inputs[i % len]);
+    checkInvalidHeaderChar(inputs[i % len]);
   }
   bench.end(n);
 }
diff --git a/benchmark/http/check_is_http_token.js b/benchmark/http/check_is_http_token.js
index 2137a73e547..553608571aa 100644
--- a/benchmark/http/check_is_http_token.js
+++ b/benchmark/http/check_is_http_token.js
@@ -1,7 +1,6 @@
 'use strict';

 const common = require('../common.js');
-const _checkIsHttpToken = require('_http_common')._checkIsHttpToken;

 const bench = common.createBenchmark(main, {
   key: [
@@ -38,12 +37,16 @@ const bench = common.createBenchmark(main, {
     'alternate-protocol:', // slow bailout
   ],
   n: [1e6],
+}, {
+  flags: ['--expose-internals', '--no-warnings'],
 });

 function main({ n, key }) {
+  const checkIsHttpToken = require('internal/http/common')._checkIsHttpToken;
+
   bench.start();
   for (let i = 0; i < n; i++) {
-    _checkIsHttpToken(key);
+    checkIsHttpToken(key);
   }
   bench.end(n);
 }
diff --git a/benchmark/http/set_header.js b/benchmark/http/set_header.js
index f2236696f1c..0938c1e9cc2 100644
--- a/benchmark/http/set_header.js
+++ b/benchmark/http/set_header.js
@@ -1,7 +1,7 @@
 'use strict';

 const common = require('../common.js');
-const { OutgoingMessage } = require('_http_outgoing');
+const { OutgoingMessage } = require('http');

 const bench = common.createBenchmark(main, {
   value: [
diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md
index 932e0ca1c1f..09a4380cfed 100644
--- a/doc/api/deprecations.md
+++ b/doc/api/deprecations.md
@@ -4443,6 +4443,9 @@ Creating SHAKE-128 and SHAKE-256 digests without an explicit

 <!-- YAML
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/58535
+    description: Runtime deprecation.
   - version:
      - v24.6.0
      - v22.19.0
diff --git a/lib/_http_agent.js b/lib/_http_agent.js
index 3871c5b56a2..d719f4d70b6 100644
--- a/lib/_http_agent.js
+++ b/lib/_http_agent.js
@@ -21,695 +21,12 @@

 'use strict';

-const {
-  NumberIsFinite,
-  NumberParseInt,
-  ObjectKeys,
-  ObjectSetPrototypeOf,
-  ObjectValues,
-  Symbol,
-} = primordials;
-
-const net = require('net');
-const EventEmitter = require('events');
-let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
-  debug = fn;
-});
-const {
-  parseProxyConfigFromEnv,
-  kProxyConfig,
-  checkShouldUseProxy,
-  kWaitForProxyTunnel,
-  getGlobalAgent,
-} = require('internal/http');
-const { AsyncResource } = require('async_hooks');
-const {
-  async_id_symbol,
-  owner_symbol,
-} = require('internal/async_hooks').symbols;
-const {
-  deprecateInstantiation,
-  getLazy,
-  kEmptyObject,
-  once,
-} = require('internal/util');
-const {
-  onStreamRead,
-} = require('internal/stream_base_commons');
-const {
-  kReadBytesOrError,
-  streamBaseState,
-} = internalBinding('stream_wrap');
-const {
-  validateNumber,
-  validateOneOf,
-  validateString,
-} = require('internal/validators');
-const assert = require('internal/assert');
-const { getOptionValue } = require('internal/options');
-
-const kOnKeylog = Symbol('onkeylog');
-const kRequestOptions = Symbol('requestOptions');
-const kRequestAsyncResource = Symbol('requestAsyncResource');
-const kFreeSocketDataGuard = Symbol('freeSocketDataGuard');
-
-// New Agent code.
-
-// The largest departure from the previous implementation is that
-// an Agent instance holds connections for a variable number of host:ports.
-// Surprisingly, this is still API compatible as far as third parties are
-// concerned. The only code that really notices the difference is the
-// request object.
-
-// Another departure is that all code related to HTTP parsing is in
-// ClientRequest.onSocket(). The Agent is now *strictly*
-// concerned with managing a connection pool.
-
-class ReusedHandle {
-  constructor(type, handle) {
-    this.type = type;
-    this.handle = handle;
-  }
-}
-
-function freeSocketErrorListener(err) {
-  const socket = this;
-  debug('SOCKET ERROR on FREE socket:', err.message, err.stack);
-  socket.destroy();
-  socket.emit('agentRemove');
-}
-
-// Guard against unsolicited data arriving while a socket is idle in the
-// freeSockets pool.  When the HTTPParser is detached the data would sit
-// in the TCP buffer and be silently consumed as the response for the
-// *next* request that reuses the socket (response-queue poisoning).
-// See: https://hackerone.com/reports/3582376
-function freeSocketOnReadGuard() {
-  const nread = streamBaseState[kReadBytesOrError];
-  if (nread === 0) return;
-
-  debug('READ on FREE socket - destroying poisoned socket');
-  this[owner_symbol].destroy();
-}
-
-function installFreeSocketDataGuard(socket) {
-  if (socket.readableLength > 0) {
-    debug('BUFFERED DATA on FREE socket - destroying poisoned socket');
-    socket.destroy();
-    return;
-  }
-
-  if (socket.connecting) {
-    socket[kFreeSocketDataGuard] = function onConnect() {
-      socket[kFreeSocketDataGuard] = null;
-      installFreeSocketDataGuard(socket);
-    };
-    socket.once('connect', socket[kFreeSocketDataGuard]);
-    return;
-  }
-
-  const handle = socket._handle;
-  if (handle) {
-    handle.onread = freeSocketOnReadGuard;
-    if (!handle.reading) {
-      handle.reading = true;
-      const err = handle.readStart();
-      if (err) socket.destroy();
-    }
-  }
-}
-
-function removeFreeSocketDataGuard(socket) {
-  if (socket[kFreeSocketDataGuard]) {
-    socket.removeListener('connect', socket[kFreeSocketDataGuard]);
-    socket[kFreeSocketDataGuard] = null;
-  }
-
-  const handle = socket._handle;
-  if (handle?.onread === freeSocketOnReadGuard) {
-    handle.onread = onStreamRead;
-  }
-}
-
-function Agent(options) {
-  if (!(this instanceof Agent)) {
-    return deprecateInstantiation(Agent, 'DEP0195', options);
-  }
-
-  EventEmitter.call(this);
-
-  this.options = { __proto__: null, ...options };
-
-  this.defaultPort = this.options.defaultPort || 80;
-  this.protocol = this.options.protocol || 'http:';
-
-  if (this.options.noDelay === undefined)
-    this.options.noDelay = true;
-
-  // Don't confuse net and make it think that we're connecting to a pipe
-  this.options.path = null;
-  this.requests = { __proto__: null };
-  this.sockets = { __proto__: null };
-  this.freeSockets = { __proto__: null };
-  this.keepAliveMsecs = this.options.keepAliveMsecs || 1000;
-  this.keepAlive = this.options.keepAlive || false;
-  this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets;
-  this.maxFreeSockets = this.options.maxFreeSockets || 256;
-  this.scheduling = this.options.scheduling || 'lifo';
-  this.maxTotalSockets = this.options.maxTotalSockets;
-  this.totalSocketCount = 0;
-
-  this.agentKeepAliveTimeoutBuffer =
-    typeof this.options.agentKeepAliveTimeoutBuffer === 'number' &&
-    this.options.agentKeepAliveTimeoutBuffer >= 0 &&
-    NumberIsFinite(this.options.agentKeepAliveTimeoutBuffer) ?
-      this.options.agentKeepAliveTimeoutBuffer :
-      1000;
-
-  const proxyEnv = this.options.proxyEnv;
-  if (typeof proxyEnv === 'object' && proxyEnv !== null) {
-    this[kProxyConfig] = parseProxyConfigFromEnv(proxyEnv, this.protocol, this.keepAlive);
-    debug(`new ${this.protocol} agent with proxy config`, this[kProxyConfig]);
-  }
-
-  validateOneOf(this.scheduling, 'scheduling', ['fifo', 'lifo']);
-
-  if (this.maxTotalSockets !== undefined) {
-    validateNumber(this.maxTotalSockets, 'maxTotalSockets', 1);
-  } else {
-    this.maxTotalSockets = Infinity;
-  }
-
-  this.on('free', (socket, options) => {
-    const name = this.getName(options);
-    debug('agent.on(free)', name);
-
-    // TODO(ronag): socket.destroy(err) might have been called
-    // before coming here and have an 'error' scheduled. In the
-    // case of socket.destroy() below this 'error' has no handler
-    // and could cause unhandled exception.
-
-    if (!socket.writable) {
-      socket.destroy();
-      return;
-    }
-
-    const requests = this.requests[name];
-    if (requests?.length) {
-      const req = requests.shift();
-      const reqAsyncRes = req[kRequestAsyncResource];
-      if (reqAsyncRes) {
-        // Run request within the original async context.
-        reqAsyncRes.runInAsyncScope(() => {
-          asyncResetHandle(socket);
-          setRequestSocket(this, req, socket);
-        });
-        req[kRequestAsyncResource] = null;
-      } else {
-        setRequestSocket(this, req, socket);
-      }
-      if (requests.length === 0) {
-        delete this.requests[name];
-      }
-      return;
-    }
-
-    // If there are no pending requests, then put it in
-    // the freeSockets pool, but only if we're allowed to do so.
-    const req = socket._httpMessage;
-    if (!req || !req.shouldKeepAlive || !this.keepAlive) {
-      socket.destroy();
-      return;
-    }
-
-    const freeSockets = this.freeSockets[name] || [];
-    const freeLen = freeSockets.length;
-    let count = freeLen;
-    if (this.sockets[name])
-      count += this.sockets[name].length;
-
-    if (this.totalSocketCount > this.maxTotalSockets ||
-        count > this.maxSockets ||
-        freeLen >= this.maxFreeSockets ||
-        !this.keepSocketAlive(socket)) {
-      socket.destroy();
-      return;
-    }
-
-    this.freeSockets[name] = freeSockets;
-    socket[async_id_symbol] = -1;
-    socket._httpMessage = null;
-    this.removeSocket(socket, options);
-
-    socket.once('error', freeSocketErrorListener);
-    installFreeSocketDataGuard(socket);
-    freeSockets.push(socket);
-  });
-
-  // Don't emit keylog events unless there is a listener for them.
-  this.on('newListener', maybeEnableKeylog);
-}
-ObjectSetPrototypeOf(Agent.prototype, EventEmitter.prototype);
-ObjectSetPrototypeOf(Agent, EventEmitter);
-
-function maybeEnableKeylog(eventName) {
-  if (eventName === 'keylog') {
-    this.removeListener('newListener', maybeEnableKeylog);
-    // Future sockets will listen on keylog at creation.
-    const agent = this;
-    this[kOnKeylog] = function onkeylog(keylog) {
-      agent.emit('keylog', keylog, this);
-    };
-    // Existing sockets will start listening on keylog now. Both maps hold
-    // arrays of sockets keyed by name, so each bucket has to be walked.
-    const sets = [this.freeSockets, this.sockets];
-    for (let s = 0; s < sets.length; s++) {
-      const buckets = ObjectValues(sets[s]);
-      for (let b = 0; b < buckets.length; b++) {
-        const sockets = buckets[b];
-        for (let n = 0; n < sockets.length; n++) {
-          sockets[n].on('keylog', this[kOnKeylog]);
-        }
-      }
-    }
-  }
-}
-
-const lazyTLS = getLazy(() => require('tls'));
-
-Agent.defaultMaxSockets = Infinity;
-
-// See ProxyConfig in internal/http.js for how the connection should be handled
-// when the agent is configured to use a proxy server.
-Agent.prototype.createConnection = function createConnection(...args) {
-  const normalized = net._normalizeArgs(args);
-  const options = normalized[0];
-  const cb = normalized[1];
-
-  // Check if this specific request should bypass the proxy
-  const shouldUseProxy = checkShouldUseProxy(this[kProxyConfig], options);
-  debug(`http createConnection should use proxy for ${options.host}:${options.port}:`, shouldUseProxy);
-  if (!shouldUseProxy) {  // Forward to net.createConnection if no proxying is needed.
-    return net.createConnection(...args);
-  }
-
-  // Create a copy of the shared proxy connection options and connect
-  // to the proxy server instead of the endpoint. For Agent.prototype.createConnection
-  // which is used by the http agent, this is enough
-  const connectOptions = {
-    ...this[kProxyConfig].proxyConnectionOptions,
-  };
-  const proxyProtocol = this[kProxyConfig].protocol;
-  if (proxyProtocol === 'http:') {
-    return net.connect(connectOptions, cb);
-  } else if (proxyProtocol === 'https:') {
-    return lazyTLS().connect(connectOptions, cb);
-  }
-  // This should be unreachable because proxy config should be null for other protocols.
-  assert.fail(`Unexpected proxy protocol ${proxyProtocol}`);
-
-};
-
-// Get the key for a given set of request options
-Agent.prototype.getName = function getName(options = kEmptyObject) {
-  let name = options.host || 'localhost';
-
-  name += ':';
-  if (options.port)
-    name += options.port;
-
-  name += ':';
-  if (options.localAddress)
-    name += options.localAddress;
-
-  // Pacify parallel/test-http-agent-getname by only appending
-  // the ':' when options.family is set.
-  if (options.family === 4 || options.family === 6)
-    name += `:${options.family}`;
-
-  if (options.socketPath)
-    name += `:${options.socketPath}`;
-
-  return name;
-};
-
-function handleSocketAfterProxy(err, req) {
-  if (err.code === 'ERR_PROXY_TUNNEL') {
-    if (err.proxyTunnelTimeout) {
-      req.emit('timeout');  // Propagate the timeout from the tunnel to the request.
-    }
-    req.emit('error', err);
-  }
-}
-
-Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
-                                                 localAddress/* legacy */) {
-  // Legacy API: addRequest(req, host, port, localAddress)
-  if (typeof options === 'string') {
-    options = {
-      __proto__: null,
-      host: options,
-      port,
-      localAddress,
-    };
-  }
-
-  // Here the agent options will override per-request options.
-  options = { __proto__: null, ...options, ...this.options };
-  if (options.socketPath)
-    options.path = options.socketPath;
-
-  normalizeServerName(options, req);
-
-  const name = this.getName(options);
-  this.sockets[name] ||= [];
-
-  const freeSockets = this.freeSockets[name];
-  let socket;
-  if (freeSockets) {
-    while (freeSockets.length && freeSockets[0].destroyed) {
-      freeSockets.shift();
-    }
-    socket = this.scheduling === 'fifo' ?
-      freeSockets.shift() :
-      freeSockets.pop();
-    if (!freeSockets.length)
-      delete this.freeSockets[name];
-  }
-
-  const freeLen = freeSockets ? freeSockets.length : 0;
-  const sockLen = freeLen + this.sockets[name].length;
-
-  // Reusing a socket from the pool.
-  // If the caller specified a highWaterMark that differs from the pooled
-  // socket's writableHighWaterMark, sync the socket's HWM so that
-  // backpressure semantics match what the caller requested.
-  if (socket && options.highWaterMark != null &&
-      socket.writableHighWaterMark !== options.highWaterMark) {
-    debug('sync reused socket HWM (socket=%d, request=%d)',
-          socket.writableHighWaterMark, options.highWaterMark);
-    socket._writableState.highWaterMark = options.highWaterMark;
-  }
-
-  if (socket) {
-    asyncResetHandle(socket);
-    this.reuseSocket(socket, req);
-    setRequestSocket(this, req, socket);
-    this.sockets[name].push(socket);
-  } else if (sockLen < this.maxSockets &&
-             this.totalSocketCount < this.maxTotalSockets) {
-    // If we are under maxSockets create a new one.
-    this.createSocket(req, options, (err, socket) => {
-      if (err) {
-        handleSocketAfterProxy(err, req);
-        debug('call onSocket', sockLen, freeLen);
-        req.onSocket(socket, err);
-        return;
-      }
-
-      setRequestSocket(this, req, socket);
-    });
-  } else {
-    debug('wait for socket');
-    // We are over limit so we'll add it to the queue.
-    this.requests[name] ||= [];
-
-    // Used to create sockets for pending requests from different origin
-    req[kRequestOptions] = options;
-    // Used to capture the original async context.
-    req[kRequestAsyncResource] = new AsyncResource('QueuedRequest');
-
-    this.requests[name].push(req);
-  }
-};
-
-Agent.prototype.createSocket = function createSocket(req, options, cb) {
-  // Here the agent options will override per-request options.
-  options = { __proto__: null, ...options, ...this.options };
-  if (options.socketPath)
-    options.path = options.socketPath;
-
-  normalizeServerName(options, req);
-
-  // Make sure per-request timeout is respected.
-  const timeout = req.timeout || this.options.timeout || undefined;
-  if (timeout) {
-    options.timeout = timeout;
-  }
-
-  const name = this.getName(options);
-  options._agentKey = name;
-
-  debug('createConnection', name);
-  options.encoding = null;
-
-  const oncreate = once((err, s) => {
-    if (err)
-      return cb(err);
-    this.sockets[name] ||= [];
-    this.sockets[name].push(s);
-    this.totalSocketCount++;
-    debug('sockets', name, this.sockets[name].length, this.totalSocketCount);
-    installListeners(this, s, options);
-    cb(null, s);
-  });
-  // When keepAlive is true, pass the related options to createConnection
-  if (this.keepAlive) {
-    options.keepAlive = this.keepAlive;
-    options.keepAliveInitialDelay = this.keepAliveMsecs;
-  }
-
-  const newSocket = this.createConnection(options, oncreate);
-  // In the case where we are proxying through a tunnel for HTTPS, only add
-  // the socket to the pool and install/invoke the listeners after
-  // the tunnel is successfully established, so that actual operations
-  // on the socket all go through the tunnel. Errors emitted during
-  // tunnel establishment will be handled in the createConnection method
-  // in lib/https.js.
-  if (newSocket && !newSocket[kWaitForProxyTunnel])
-    oncreate(null, newSocket);
-};
-
-function normalizeServerName(options, req) {
-  if (!options.servername && options.servername !== '')
-    options.servername = calculateServerName(options, req);
-}
-
-function calculateServerName(options, req) {
-  let servername = options.host;
-  const hostHeader = req.getHeader('host');
-  if (hostHeader) {
-    validateString(hostHeader, 'options.headers.host');
-
-    // abc => abc
-    // abc:123 => abc
-    // [::1] => ::1
-    // [::1]:123 => ::1
-    if (hostHeader[0] === '[') {
-      const index = hostHeader.indexOf(']');
-      if (index === -1) {
-        // Leading '[', but no ']'. Need to do something...
-        servername = hostHeader;
-      } else {
-        servername = hostHeader.substring(1, index);
-      }
-    } else {
-      servername = hostHeader.split(':', 1)[0];
-    }
-  }
-  // Don't implicitly set invalid (IP) servernames.
-  if (net.isIP(servername))
-    servername = '';
-  return servername;
-}
-
-function installListeners(agent, s, options) {
-  function onFree() {
-    debug('CLIENT socket onFree');
-    agent.emit('free', s, options);
-  }
-  s.on('free', onFree);
-
-  function onClose(err) {
-    debug('CLIENT socket onClose');
-    // This is the only place where sockets get removed from the Agent.
-    // If you want to remove a socket from the pool, just close it.
-    // All socket errors end in a close event anyway.
-    agent.totalSocketCount--;
-    agent.removeSocket(s, options);
-  }
-  s.on('close', onClose);
-
-  function onTimeout() {
-    debug('CLIENT socket onTimeout');
-
-    // Destroy if in free list.
-    // TODO(ronag): Always destroy, even if not in free list.
-    const sockets = agent.freeSockets;
-    if (ObjectKeys(sockets).some((name) => sockets[name].includes(s))) {
-      return s.destroy();
-    }
-  }
-  s.on('timeout', onTimeout);
-
-  function onRemove() {
-    // We need this function for cases like HTTP 'upgrade'
-    // (defined by WebSockets) where we need to remove a socket from the
-    // pool because it'll be locked up indefinitely
-    debug('CLIENT socket onRemove');
-    agent.totalSocketCount--;
-    agent.removeSocket(s, options);
-    s.removeListener('close', onClose);
-    s.removeListener('free', onFree);
-    s.removeListener('timeout', onTimeout);
-    s.removeListener('agentRemove', onRemove);
-  }
-  s.on('agentRemove', onRemove);
-
-  if (agent[kOnKeylog]) {
-    s.on('keylog', agent[kOnKeylog]);
-  }
-}
-
-Agent.prototype.removeSocket = function removeSocket(s, options) {
-  const name = this.getName(options);
-  debug('removeSocket', name, 'writable:', s.writable);
-  const sets = [this.sockets];
-
-  // If the socket was destroyed, remove it from the free buffers too.
-  if (!s.writable)
-    sets.push(this.freeSockets);
-
-  for (let sk = 0; sk < sets.length; sk++) {
-    const sockets = sets[sk];
-
-    if (sockets[name]) {
-      const index = sockets[name].indexOf(s);
-      if (index !== -1) {
-        sockets[name].splice(index, 1);
-        // Don't leak
-        if (sockets[name].length === 0)
-          delete sockets[name];
-      }
-    }
-  }
-
-  let req;
-  if (this.requests[name]?.length) {
-    debug('removeSocket, have a request, make a socket');
-    req = this.requests[name][0];
-  } else {
-    // TODO(rickyes): this logic will not be FIFO across origins.
-    // There might be older requests in a different origin, but
-    // if the origin which releases the socket has pending requests
-    // that will be prioritized.
-    const keys = ObjectKeys(this.requests);
-    for (let i = 0; i < keys.length; i++) {
-      const prop = keys[i];
-      // Check whether this specific origin is already at maxSockets
-      if (this.sockets[prop]?.length) break;
-      debug('removeSocket, have a request with different origin,' +
-        ' make a socket');
-      req = this.requests[prop][0];
-      options = req[kRequestOptions];
-      break;
-    }
-  }
-
-  if (req && options) {
-    req[kRequestOptions] = undefined;
-    // If we have pending requests and a socket gets closed make a new one
-    this.createSocket(req, options, (err, socket) => {
-      if (err) {
-        handleSocketAfterProxy(err, req);
-        req.onSocket(null, err);
-        return;
-      }
-
-      socket.emit('free');
-    });
-  }
-
-};
-
-Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
-  socket.setKeepAlive(true, this.keepAliveMsecs);
-  socket.unref();
-
-  let agentTimeout = this.options.timeout || 0;
-  let canKeepSocketAlive = true;
-
-  if (socket._httpMessage?.res) {
-    const keepAliveHint = socket._httpMessage.res.headers['keep-alive'];
-
-    if (keepAliveHint) {
-      const hint = /^timeout=(\d+)/.exec(keepAliveHint)?.[1];
-
-      if (hint) {
-        // Let the timer expire before the announced timeout to reduce
-        // the likelihood of ECONNRESET errors
-        let serverHintTimeout = (NumberParseInt(hint) * 1000) - this.agentKeepAliveTimeoutBuffer;
-        serverHintTimeout = serverHintTimeout > 0 ? serverHintTimeout : 0;
-        if (serverHintTimeout === 0) {
-          // Cannot safely reuse the socket because the server timeout is
-          // too short
-          canKeepSocketAlive = false;
-        } else if (serverHintTimeout < agentTimeout) {
-          agentTimeout = serverHintTimeout;
-        }
-      }
-    }
-  }
-
-  if (socket.timeout !== agentTimeout) {
-    socket.setTimeout(agentTimeout);
-  }
-
-  return canKeepSocketAlive;
-};
-
-Agent.prototype.reuseSocket = function reuseSocket(socket, req) {
-  debug('have free socket');
-  socket.removeListener('error', freeSocketErrorListener);
-  removeFreeSocketDataGuard(socket);
-  req.reusedSocket = true;
-  socket.ref();
-};
-
-Agent.prototype.destroy = function destroy() {
-  const sets = [this.freeSockets, this.sockets];
-  for (let s = 0; s < sets.length; s++) {
-    const set = sets[s];
-    const keys = ObjectKeys(set);
-    for (let v = 0; v < keys.length; v++) {
-      const setName = set[keys[v]];
-      for (let n = 0; n < setName.length; n++) {
-        setName[n].destroy();
-      }
-    }
-  }
-};
-
-function setRequestSocket(agent, req, socket) {
-  req.onSocket(socket);
-  const agentTimeout = agent.options.timeout || 0;
-  if (req.timeout === undefined || req.timeout === agentTimeout) {
-    return;
-  }
-  socket.setTimeout(req.timeout);
-}
-
-function asyncResetHandle(socket) {
-  // Guard against an uninitialized or user supplied Socket.
-  const handle = socket._handle;
-  if (handle && typeof handle.asyncReset === 'function') {
-    // Assign the handle a new asyncId and run any destroy()/init() hooks.
-    handle.asyncReset(new ReusedHandle(handle.getProviderType(), handle));
-    socket[async_id_symbol] = handle.getAsyncId();
-  }
-}
+const { Agent, globalAgent } = require('internal/http/agent');

 module.exports = {
   Agent,
-  globalAgent: getGlobalAgent(getOptionValue('--use-env-proxy') ? process.env : undefined, Agent),
+  globalAgent,
 };
+
+process.emitWarning('The _http_agent module is deprecated. Use `node:http` instead.',
+                    'DeprecationWarning', 'DEP0199');
diff --git a/lib/_http_client.js b/lib/_http_client.js
index 21cf9e8d407..de13dc247aa 100644
--- a/lib/_http_client.js
+++ b/lib/_http_client.js
@@ -21,1283 +21,11 @@

 'use strict';

-const {
-  ArrayIsArray,
-  Boolean,
-  Error,
-  NumberIsFinite,
-  ObjectAssign,
-  ObjectDefineProperty,
-  ObjectHasOwn,
-  ObjectKeys,
-  ObjectSetPrototypeOf,
-  ReflectApply,
-  String,
-  Symbol,
-} = primordials;
-
-const net = require('net');
-const assert = require('internal/assert');
-const {
-  deprecateInstantiation,
-  kEmptyObject,
-  once,
-} = require('internal/util');
-const {
-  _checkIsHttpToken: checkIsHttpToken,
-  freeParser,
-  parsers,
-  HTTPParser,
-  calculateLenientFlags,
-  prepareError,
-  kSkipPendingData,
-} = require('_http_common');
-const { kDetachAbortSignal } = require('_http_incoming');
-const {
-  kHighWaterMark,
-  kUniqueHeaders,
-  parseUniqueHeadersOption,
-  OutgoingMessage,
-} = require('_http_outgoing');
-const Agent = require('_http_agent');
-const { Buffer } = require('buffer');
-const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
-const { URL, urlToHttpOptions, isURL } = require('internal/url');
-const {
-  kOutHeaders,
-  kNeedDrain,
-  isTraceHTTPEnabled,
-  traceBegin,
-  traceEnd,
-  getNextTraceEventId,
-  kProxyConfig,
-  checkShouldUseProxy,
-} = require('internal/http');
-const {
-  ConnResetException,
-  codes: {
-    ERR_HTTP_HEADERS_SENT,
-    ERR_INVALID_ARG_TYPE,
-    ERR_INVALID_ARG_VALUE,
-    ERR_INVALID_HTTP_TOKEN,
-    ERR_INVALID_PROTOCOL,
-    ERR_UNESCAPED_CHARACTERS,
-  },
-} = require('internal/errors');
-const {
-  validateInteger,
-  validateBoolean,
-  validateOneOf,
-  validatePort,
-  validateString,
-} = require('internal/validators');
-const { getTimerDuration } = require('internal/timers');
-const {
-  hasObserver,
-  startPerf,
-  stopPerf,
-} = require('internal/perf/observe');
-
-const kClientRequestStatistics = Symbol('ClientRequestStatistics');
-
-const dc = require('diagnostics_channel');
-const onClientRequestCreatedChannel = dc.channel('http.client.request.created');
-const onClientRequestStartChannel = dc.channel('http.client.request.start');
-const onClientRequestErrorChannel = dc.channel('http.client.request.error');
-const onClientResponseFinishChannel = dc.channel('http.client.response.finish');
-
-function emitErrorEvent(request, error) {
-  if (onClientRequestErrorChannel.hasSubscribers) {
-    onClientRequestErrorChannel.publish({
-      request,
-      error,
-    });
-  }
-  request.emit('error', error);
-}
-
-const { addAbortSignal, finished } = require('stream');
-
-let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
-  debug = fn;
-});
-
-const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;
-const kError = Symbol('kError');
-const kPath = Symbol('kPath');
-const kAuthority = Symbol('kAuthority');
-const kProxyRewrittenToAbsolute = Symbol('kProxyRewrittenToAbsolute');
-
-const HTTP_CLIENT_TRACE_EVENT_NAME = 'http.client.request';
-
-function validateHost(host, name) {
-  if (host !== null && host !== undefined && typeof host !== 'string') {
-    throw new ERR_INVALID_ARG_TYPE(`options.${name}`,
-                                   ['string', 'undefined', 'null'],
-                                   host);
-  }
-  return host;
-}
-
-class HTTPClientAsyncResource {
-  constructor(type, req) {
-    this.type = type;
-    this.req = req;
-  }
-}
-
-// The only documented shape is [k, v, k, v, ...]. Here we also accept [[k, v], [k, v], ...].
-// for backward compatibility, and reject others. Also reject if there are duplicate Host entries.
-// Returns the Host header value, or undefined if absent.
-function getHostFromHeaderArray(headers) {
-  let host;
-  const isPairs = headers.length > 0 && ArrayIsArray(headers[0]);
-  if (isPairs) {
-    for (let i = 0; i < headers.length; i++) {
-      const entry = headers[i];
-      if (!ArrayIsArray(entry)) {
-        throw new ERR_INVALID_ARG_VALUE(`options.headers[${i}]`, typeof entry,
-                                        'must be an array when headers is passed as an array of pairs');
-      }
-      if (`${entry[0]}`.toLowerCase() === 'host') {
-        if (host !== undefined) {
-          throw new ERR_INVALID_ARG_VALUE('options.headers', '(redacted)',
-                                          'must not contain duplicate Host headers');
-        }
-        host = `${entry[1]}`;
-      }
-    }
-  } else {
-    for (let i = 0; i + 1 < headers.length; i += 2) {
-      if (`${headers[i]}`.toLowerCase() === 'host') {
-        if (host !== undefined) {
-          throw new ERR_INVALID_ARG_VALUE('options.headers', '(redacted)',
-                                          'must not contain duplicate Host headers');
-        }
-        host = `${headers[i + 1]}`;
-      }
-    }
-  }
-  return host;
-}
-
-function authoritiesMatch(canonicalHost, hostFromHeader) {
-  let parsed;
-  try {
-    parsed = new URL(`http://${hostFromHeader}`);
-  } catch {
-    return false;
-  }
-  if (parsed.username || parsed.password ||
-      parsed.pathname !== '/' || parsed.search || parsed.hash) {
-    return false;
-  }
-  return parsed.host === canonicalHost;
-}
-
-// https://datatracker.ietf.org/doc/html/rfc9112#section-3.2
-// When the request target is in absolute-form, ensure it is consistent with
-// the request authority: same scheme, no userinfo, and an authority
-// component agree with options.host[:port].
-function validateRequestAuthority(pathOption, proxyAuthority, userHostHeader, headerArray) {
-  validatePort(proxyAuthority.port, 'options.port', true);
-  pathOption = `${pathOption}`;
-  const requestBase = new URL(`http://${proxyAuthority.host}`);
-  requestBase.port = proxyAuthority.port;
-
-  const result = { requestBase };
-  if (headerArray !== undefined) {
-    const host = getHostFromHeaderArray(headerArray);
-    // Since we don't mutate the header array to normalize the Host value, unlike
-    // in the case of other shapes of headers provided, we check that it is identical
-    // to the authority from the requestBase.
-    if (host !== undefined && host !== requestBase.host) {
-      throw new ERR_INVALID_ARG_VALUE(
-        'Host in options.headers', host,
-        `must match the request authority (${requestBase.host})`);
-    }
-  } else if (userHostHeader !== undefined) {
-    if (!authoritiesMatch(requestBase.host, userHostHeader)) {
-      throw new ERR_INVALID_ARG_VALUE(
-        'Host in options.headers', userHostHeader,
-        `must match the request authority (${requestBase.host})`);
-    }
-  }
-
-  // Per RFC 9112 Section 3.2, if request target is in absolute-form its authority
-  // must agree with the request authority.
-  let requestURL;
-  let isAbsoluteForm = false;
-  try {
-    requestURL = new URL(pathOption);
-    isAbsoluteForm = true;
-  } catch {
-    if (pathOption.charCodeAt(0) !== 0x2F) {
-      throw new ERR_INVALID_ARG_VALUE(
-        'options.path', pathOption, 'must be in absolute-form or start with /');
-    }
-    requestURL = new URL(requestBase.origin + pathOption);
-  }
-  result.requestURL = requestURL;
-  if (!isAbsoluteForm) {
-    return result;
-  }
-
-  if (requestURL.username || requestURL.password) {
-    requestURL.username = '';
-    requestURL.password = '';
-    throw new ERR_INVALID_ARG_VALUE(
-      'options.path', requestURL.href, 'must not contain userinfo, use options.auth instead');
-  }
-
-  if (requestURL.protocol !== 'http:') {
-    throw new ERR_INVALID_ARG_VALUE(
-      'options.path', requestURL.protocol, 'must use http: scheme when specified as an absolute URL');
-  }
-
-  if (requestBase.host !== requestURL.host) {
-    throw new ERR_INVALID_ARG_VALUE(
-      'options.path', requestURL, `must match the request authority (${requestBase.host})`);
-  }
-
-  return result;
-}
-
-// When proxying a HTTP request, the following needs to be done:
-// https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.2
-// 1. Rewrite the request path to absolute-form.
-// 2. Add proxy-connection and proxy-authorization headers appropriately.
-//
-// This function checks whether the request should be rewritten for proxying
-// and modifies the headers as well as req.path if necessary.
-// The handling of the proxy server connection is done in createConnection.
-// It also validates that the Host header and absolute-form path authority match the
-// request authority specified by reqOptions.
-function rewriteForProxiedHttp(req, reqOptions, proxyAuthority, userHostHeader, headerArray) {
-  if (req._header) {
-    debug('request._header is already sent, skipping rewriteForProxiedHttp', reqOptions);
-    return false;
-  }
-  const agent = req.agent;
-  if (!agent || !agent[kProxyConfig]) {
-    return false;
-  }
-  if ((reqOptions.protocol || agent.protocol) !== 'http:') {
-    return false;
-  }
-  // TODO(joyeecheung): cache this on the req or on the agent.
-  const shouldUseProxy = checkShouldUseProxy(agent[kProxyConfig], reqOptions);
-  debug(`rewriteForProxiedHttp should use proxy for ${reqOptions.host}:${reqOptions.port}:`, shouldUseProxy);
-  if (!shouldUseProxy) {
-    return false;
-  }
-
-  // Per RFC 9112 Section 3.2.2, we don't need to rewrite CONNECT or OPTIONS * requests.
-  let requestURL;
-  if (req.method !== 'CONNECT' && !(req.method === 'OPTIONS' && req.path === '*')) {
-    // Validate Host header values agree with the request authority before mutating req,
-    // so a rejected request doesn't leave proxy-* headers stuck on the outgoing header store.
-    // XXX(joyeecheung): This validates whether the request conforms to the RFC, but here
-    // we only do it for proxied requests for backward compatibility. For non-proxied requests,
-    // ensuring that the request is well formed has been entirely left to the user.
-    const result = validateRequestAuthority(req.path, proxyAuthority, userHostHeader, headerArray);
-    if (headerArray === undefined) {
-      const currentHost = req.getHeader('host');
-      if (currentHost !== undefined && currentHost !== result.requestBase.host) {
-        req.setHeader('Host', result.requestBase.host);
-      }
-    }
-    requestURL = result.requestURL;
-  }
-
-  // Add proxy headers.
-  const { auth, href } = agent[kProxyConfig];
-  if (auth) {
-    req.setHeader('proxy-authorization', auth);
-  }
-  if (req.shouldKeepAlive) {
-    req.setHeader('proxy-connection', 'keep-alive');
-  } else {
-    req.setHeader('proxy-connection', 'close');
-  }
-
-  if (requestURL !== undefined) {
-    // Convert the path to absolute-form. The authority is built from options.
-    req.path = requestURL.href;
-    req[kProxyRewrittenToAbsolute] = true;
-  }
-  debug(`updated request for HTTP proxy ${href} with ${req.path} `, req[kOutHeaders]);
-  return true;
-};
-
-function ClientRequest(input, options, cb) {
-  if (!(this instanceof ClientRequest)) {
-    return deprecateInstantiation(ClientRequest, 'DEP0195', input, options, cb);
-  }
-
-  OutgoingMessage.call(this);
-
-  let pathIsFromURL = false;
-  if (typeof input === 'string') {
-    const urlStr = input;
-    input = urlToHttpOptions(new URL(urlStr));
-    pathIsFromURL = true;
-  } else if (isURL(input)) {
-    // url.URL instance
-    input = urlToHttpOptions(input);
-    pathIsFromURL = true;
-  } else {
-    cb = options;
-    options = input;
-    input = null;
-  }
-
-  if (typeof options === 'function') {
-    cb = options;
-    options = input || kEmptyObject;
-  } else {
-    const hasPathOverride = pathIsFromURL &&
-      options != null &&
-      ObjectHasOwn(options, 'path');
-    if (hasPathOverride) {
-      pathIsFromURL = false;
-    }
-
-    options = ObjectAssign({ __proto__: null }, input, options);
-  }
-
-  // Propagate the user's highWaterMark to OutgoingMessage so that
-  // _writeRaw() uses the correct threshold for writes buffered before
-  // the socket connects (Path B).  Without this, the OutgoingMessage
-  // defaults to 64 KB regardless of what the caller requested.
-  if (options.highWaterMark != null) {
-    this[kHighWaterMark] = options.highWaterMark;
-  }
-
-  let agent = options.agent;
-  const defaultAgent = options._defaultAgent || Agent.globalAgent;
-  if (agent === false) {
-    agent = new defaultAgent.constructor();
-  } else if (agent === null || agent === undefined) {
-    if (typeof options.createConnection !== 'function') {
-      agent = defaultAgent;
-    }
-    // Explicitly pass through this statement as agent will not be used
-    // when createConnection is provided.
-  } else if (typeof agent.addRequest !== 'function') {
-    throw new ERR_INVALID_ARG_TYPE('options.agent',
-                                   ['Agent-like Object', 'undefined', 'false'],
-                                   agent);
-  }
-  this.agent = agent;
-
-  const protocol = options.protocol || defaultAgent.protocol;
-  let expectedProtocol = defaultAgent.protocol;
-  if (this.agent?.protocol)
-    expectedProtocol = this.agent.protocol;
-
-  if (options.path) {
-    const path = String(options.path);
-    if (INVALID_PATH_REGEX.test(path)) {
-      debug('Path contains unescaped characters: "%s"', path);
-      throw new ERR_UNESCAPED_CHARACTERS('Request path');
-    }
-  }
-
-  if (protocol !== expectedProtocol) {
-    throw new ERR_INVALID_PROTOCOL(protocol, expectedProtocol);
-  }
-
-  const defaultPort = options.defaultPort ||
-                    (this.agent?.defaultPort);
-
-  const optsWithoutSignal = { __proto__: null, ...options };
-
-  const port = optsWithoutSignal.port = options.port || defaultPort || 80;
-  const host = optsWithoutSignal.host = validateHost(options.hostname, 'hostname') ||
-                                        validateHost(options.host, 'host') || 'localhost';
-
-  const setHost = options.setHost !== undefined ?
-    Boolean(options.setHost) :
-    options.setDefaultHeaders !== false;
-
-  this._removedConnection = options.setDefaultHeaders === false;
-  this._removedContLen = options.setDefaultHeaders === false;
-  this._removedTE = options.setDefaultHeaders === false;
-
-  this.socketPath = options.socketPath;
-
-  if (options.timeout !== undefined)
-    this.timeout = getTimerDuration(options.timeout, 'timeout');
-
-  const signal = options.signal;
-  if (signal) {
-    addAbortSignal(signal, this);
-    delete optsWithoutSignal.signal;
-  }
-  let method = options.method;
-  if (method != null) {
-    validateString(method, 'options.method');
-  }
-
-  if (method) {
-    if (!checkIsHttpToken(method)) {
-      throw new ERR_INVALID_HTTP_TOKEN('Method', method);
-    }
-    method = this.method = method.toUpperCase();
-  } else {
-    method = this.method = 'GET';
-  }
-
-  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 httpValidation = options.httpValidation;
-  if (httpValidation !== undefined) {
-    validateOneOf(httpValidation, 'options.httpValidation',
-                  ['strict', 'relaxed', 'insecure']);
-    if (insecureHTTPParser !== undefined) {
-      throw new ERR_INVALID_ARG_VALUE(
-        'options.httpValidation',
-        httpValidation,
-        'cannot be used together with options.insecureHTTPParser',
-      );
-    }
-  }
-
-  this.httpValidation = httpValidation;
-
-  if (options.joinDuplicateHeaders !== undefined) {
-    validateBoolean(options.joinDuplicateHeaders, 'options.joinDuplicateHeaders');
-  }
-
-  this.joinDuplicateHeaders = options.joinDuplicateHeaders;
-
-  let path = options.path || '/';
-  // Strip the leading slash added when the CONNECT target comes from a URL.
-  if (method === 'CONNECT' && pathIsFromURL && path[0] === '/') {
-    path = path.slice(1) || '/';
-  }
-
-  this[kPath] = path;
-  if (cb) {
-    this.once('response', cb);
-  }
-
-  if (method === 'GET' ||
-      method === 'HEAD' ||
-      method === 'DELETE' ||
-      method === 'OPTIONS' ||
-      method === 'TRACE' ||
-      method === 'CONNECT') {
-    this.useChunkedEncodingByDefault = false;
-  } else {
-    this.useChunkedEncodingByDefault = true;
-  }
-
-  this._ended = false;
-  this.res = null;
-  this.aborted = false;
-  this.timeoutCb = null;
-  this.upgradeOrConnect = false;
-  this.parser = null;
-  this.maxHeadersCount = null;
-  this.reusedSocket = false;
-  this.host = host;
-  this.protocol = protocol;
-  this[kProxyRewrittenToAbsolute] = false;
-
-  if (this.agent) {
-    // If there is an agent we should default to Connection:keep-alive,
-    // but only if the Agent will actually reuse the connection!
-    // If it's not a keepAlive agent, and the maxSockets==Infinity, then
-    // there's never a case where this socket will actually be reused
-    if (!this.agent.keepAlive && !NumberIsFinite(this.agent.maxSockets)) {
-      this._last = true;
-      this.shouldKeepAlive = false;
-    } else {
-      this._last = false;
-      this.shouldKeepAlive = true;
-    }
-  }
-
-  let hostHeaderFromOptions = host;
-  // For the Host header, ensure that IPv6 addresses are enclosed
-  // in square brackets, as defined by URI formatting
-  // https://tools.ietf.org/html/rfc3986#section-3.2.2
-  const posColon = hostHeaderFromOptions.indexOf(':');
-  if (posColon !== -1 &&
-      hostHeaderFromOptions.includes(':', posColon + 1) &&
-      hostHeaderFromOptions.charCodeAt(0) !== 91/* '[' */) {
-    hostHeaderFromOptions = `[${hostHeaderFromOptions}]`;
-  }
-  const proxyAuthority = { host: hostHeaderFromOptions, port };
-
-  if (port && +port !== defaultPort) {
-    hostHeaderFromOptions += ':' + port;
-  }
-  // Preserve the request authority (with the port when non-default) so that
-  // the perf_hooks entry can report a faithful URL.
-  this[kAuthority] = hostHeaderFromOptions;
-  const headersArray = ArrayIsArray(options.headers);
-  if (!headersArray) {
-    if (options.headers) {
-      const keys = ObjectKeys(options.headers);
-      // Retain for(;;) loop for performance reasons
-      // Refs: https://github.com/nodejs/node/pull/30958
-      for (let i = 0; i < keys.length; i++) {
-        const key = keys[i];
-        this.setHeader(key, options.headers[key]);
-      }
-    }
-
-    // Save the Host header before the implicit auto-set below, so the
-    // proxy validator can tell user-explicit values from Node-generated ones.
-    const userHostHeader = this.getHeader('host');
-
-    if (host && !this.getHeader('host') && setHost) {
-      const hostHeader = method === 'CONNECT' && options.path ?
-        String(this.path) : hostHeaderFromOptions;
-      this.setHeader('Host', hostHeader);
-    }
-
-    if (options.auth && !this.getHeader('Authorization')) {
-      this.setHeader('Authorization', 'Basic ' +
-                     Buffer.from(options.auth).toString('base64'));
-    }
-
-    if (this.getHeader('expect')) {
-      if (this._header) {
-        throw new ERR_HTTP_HEADERS_SENT('render');
-      }
-
-      rewriteForProxiedHttp(this, optsWithoutSignal, proxyAuthority, userHostHeader);
-      this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
-                        this[kOutHeaders]);
-    } else {
-      rewriteForProxiedHttp(this, optsWithoutSignal, proxyAuthority, userHostHeader);
-    }
-  } else {
-    rewriteForProxiedHttp(this, optsWithoutSignal, proxyAuthority, undefined, options.headers);
-    this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
-                      options.headers);
-  }
-
-  this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders);
-
-  // initiate connection
-  if (this.agent) {
-    this.agent.addRequest(this, optsWithoutSignal);
-  } else {
-    // No agent, default to Connection:close.
-    this._last = true;
-    this.shouldKeepAlive = false;
-    let opts = optsWithoutSignal;
-    if (opts.path || opts.socketPath) {
-      opts = { ...optsWithoutSignal };
-      if (opts.socketPath) {
-        opts.path = opts.socketPath;
-      } else {
-        opts.path &&= undefined;
-      }
-    }
-    if (typeof opts.createConnection === 'function') {
-      const oncreate = once((err, socket) => {
-        if (err) {
-          process.nextTick(() => emitErrorEvent(this, err));
-        } else {
-          this.onSocket(socket);
-        }
-      });
-
-      try {
-        const newSocket = opts.createConnection(opts, oncreate);
-        if (newSocket) {
-          oncreate(null, newSocket);
-        }
-      } catch (err) {
-        oncreate(err);
-      }
-    } else {
-      debug('CLIENT use net.createConnection', opts);
-      this.onSocket(net.createConnection(opts));
-    }
-  }
-  if (onClientRequestCreatedChannel.hasSubscribers) {
-    onClientRequestCreatedChannel.publish({
-      request: this,
-    });
-  }
-}
-ObjectSetPrototypeOf(ClientRequest.prototype, OutgoingMessage.prototype);
-ObjectSetPrototypeOf(ClientRequest, OutgoingMessage);
-
-ObjectDefineProperty(ClientRequest.prototype, 'path', {
-  __proto__: null,
-  get() {
-    return this[kPath];
-  },
-  set(value) {
-    const path = String(value);
-    if (INVALID_PATH_REGEX.test(path)) {
-      throw new ERR_UNESCAPED_CHARACTERS('Request path');
-    }
-    this[kPath] = path;
-  },
-  configurable: true,
-  enumerable: true,
-});
-
-ClientRequest.prototype._finish = function _finish() {
-  OutgoingMessage.prototype._finish.call(this);
-  if (hasObserver('http')) {
-    startPerf(this, kClientRequestStatistics, {
-      type: 'http',
-      name: 'HttpClient',
-      detail: {
-        req: {
-          method: this.method,
-          // If the path has been rewritten to absolute-form for proxying,
-          // it is already a full URL.
-          url: this[kProxyRewrittenToAbsolute] ?
-            this.path :
-            `${this.protocol}//${this[kAuthority]}${this.path}`,
-          headers: typeof this.getHeaders === 'function' ? this.getHeaders() : {},
-        },
-      },
-    });
-  }
-  if (onClientRequestStartChannel.hasSubscribers) {
-    onClientRequestStartChannel.publish({
-      request: this,
-    });
-  }
-  if (isTraceHTTPEnabled()) {
-    this._traceEventId = getNextTraceEventId();
-    traceBegin(HTTP_CLIENT_TRACE_EVENT_NAME, this._traceEventId);
-  }
-};
-
-ClientRequest.prototype._implicitHeader = function _implicitHeader() {
-  if (this._header) {
-    throw new ERR_HTTP_HEADERS_SENT('render');
-  }
-  this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
-                    this[kOutHeaders]);
-};
-
-ClientRequest.prototype.abort = function abort() {
-  if (this.aborted) {
-    return;
-  }
-  this.aborted = true;
-  process.nextTick(emitAbortNT, this);
-  this.destroy();
-};
-
-ClientRequest.prototype.destroy = function destroy(err) {
-  if (this.destroyed) {
-    return this;
-  }
-  this.destroyed = true;
-
-  // If we're aborting, we don't care about any more response data.
-  if (this.res) {
-    this.res._dump();
-  }
-
-  // Nothing left to cancel: the request was fully sent and the response was
-  // fully received. Destroying the socket here would emit an error on a
-  // socket that is already being released to the agent, at which point
-  // socketErrorListener has been removed and nothing would handle it.
-  if (this.writableFinished && this.res?.complete) {
-    return this;
-  }
-
-  this[kError] = err;
-  this.socket?.destroy(err);
-
-  return this;
-};
-
-function emitAbortNT(req) {
-  req.emit('abort');
-}
-
-function ondrain() {
-  const msg = this._httpMessage;
-  if (msg && !msg.finished && msg[kNeedDrain]) {
-    msg[kNeedDrain] = false;
-    msg.emit('drain');
-  }
-}
-
-function socketCloseListener() {
-  const socket = this;
-  const req = socket._httpMessage;
-  debug('HTTP socket close');
-
-  // NOTE: It's important to get parser here, because it could be freed by
-  // the `socketOnData`.
-  const parser = socket.parser;
-  const res = req.res;
-
-  req.destroyed = true;
-  if (res) {
-    // Socket closed before we emitted 'end' below.
-    if (!res.complete) {
-      res.destroy(new ConnResetException('aborted',
-                                         req[kError] ? { cause: req[kError] } : undefined));
-    }
-    req._closed = true;
-    req.emit('close');
-    if (!res.aborted && res.readable) {
-      res.push(null);
-    }
-  } else {
-    if (!req.socket._hadError) {
-      // This socket error fired before we started to
-      // receive a response. The error needs to
-      // fire on the request.
-      req.socket._hadError = true;
-      emitErrorEvent(req, new ConnResetException('socket hang up'));
-    }
-    req._closed = true;
-    req.emit('close');
-  }
-
-  // Too bad.  That output wasn't getting written.
-  // This is pretty terrible that it doesn't raise an error.
-  // Fixed better in v0.10
-  if (req.outputData)
-    req.outputData.length = 0;
-
-  if (parser) {
-    parser.finish();
-    freeParser(parser, req, socket);
-  }
-}
-
-function socketErrorListener(err) {
-  const socket = this;
-  const req = socket._httpMessage;
-  debug('SOCKET ERROR:', err.message, err.stack);
-
-  if (req) {
-    // For Safety. Some additional errors might fire later on
-    // and we need to make sure we don't double-fire the error event.
-    socket._hadError = true;
-    emitErrorEvent(req, err);
-    // Preserve the socket error for an incomplete response, including TLS
-    // errors that are emitted without setting socket.errored.
-    req[kError] ||= err;
-  }
-
-  const parser = socket.parser;
-  if (parser) {
-    parser.finish();
-    freeParser(parser, req, socket);
-  }
-
-  // Ensure that no further data will come out of the socket
-  socket.removeListener('data', socketOnData);
-  socket.removeListener('end', socketOnEnd);
-  socket.destroy();
-}
-
-function socketOnEnd() {
-  const socket = this;
-  const req = this._httpMessage;
-  const parser = this.parser;
-
-  if (!req.res && !req.socket._hadError) {
-    // If we don't have a response then we know that the socket
-    // ended prematurely and we need to emit an error on the request.
-    req.socket._hadError = true;
-    emitErrorEvent(req, new ConnResetException('socket hang up'));
-  }
-  if (parser) {
-    parser.finish();
-    freeParser(parser, req, socket);
-  }
-  socket.destroy();
-}
-
-function socketOnData(d) {
-  const socket = this;
-  const req = this._httpMessage;
-  const parser = this.parser;
-
-  assert(parser && parser.socket === socket);
-
-  const ret = parser.execute(d);
-  if (ret instanceof Error) {
-    prepareError(ret, parser, d);
-    debug('parse error', ret);
-    freeParser(parser, req, socket);
-    socket.removeListener('data', socketOnData);
-    socket.removeListener('end', socketOnEnd);
-    socket.destroy();
-    req.socket._hadError = true;
-    emitErrorEvent(req, ret);
-  } else if (parser.incoming?.upgrade) {
-    // Upgrade (if status code 101) or CONNECT
-    const bytesParsed = ret;
-    const res = parser.incoming;
-    req.res = res;
-
-    socket.removeListener('data', socketOnData);
-    socket.removeListener('end', socketOnEnd);
-    socket.removeListener('drain', ondrain);
-
-    if (req.timeoutCb) socket.removeListener('timeout', req.timeoutCb);
-    socket.removeListener('timeout', responseOnTimeout);
-
-    parser.finish();
-    freeParser(parser, req, socket);
-
-    const bodyHead = d.slice(bytesParsed, d.length);
-
-    const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
-    if (req.listenerCount(eventName) > 0) {
-      req.upgradeOrConnect = true;
-
-      // detach the socket
-      socket.emit('agentRemove');
-      socket.removeListener('close', socketCloseListener);
-      socket.removeListener('error', socketErrorListener);
-
-      socket._httpMessage = null;
-      socket.readableFlowing = null;
-
-      req.emit(eventName, res, socket, bodyHead);
-      req.destroyed = true;
-      req._closed = true;
-      req.emit('close');
-    } else {
-      // Requested Upgrade or used CONNECT method, but have no handler.
-      socket.destroy();
-    }
-  } else if (parser.incoming?.complete &&
-             // When the status code is informational (100, 102-199),
-             // the server will send a final response after this client
-             // sends a request body, so we must not free the parser.
-             // 101 (Switching Protocols) and all other status codes
-             // should be processed normally.
-             !statusIsInformational(parser.incoming.statusCode)) {
-    socket.removeListener('data', socketOnData);
-    socket.removeListener('end', socketOnEnd);
-    socket.removeListener('drain', ondrain);
-    freeParser(parser, req, socket);
-  }
-}
-
-function statusIsInformational(status) {
-  // 100 (Continue)    RFC7231 Section 6.2.1
-  // 102 (Processing)  RFC2518
-  // 103 (Early Hints) RFC8297
-  // 104-199 (Unassigned)
-  return (status < 200 && status >= 100 && status !== 101);
-}
-
-// client
-function parserOnIncomingClient(res, shouldKeepAlive) {
-  const socket = this.socket;
-  const req = socket._httpMessage;
-
-  debug('AGENT incoming response!');
-
-  if (req.res) {
-    // We already have a response object, this means the server
-    // sent a double response.
-    socket.destroy();
-    if (socket.parser) {
-      // https://github.com/nodejs/node/issues/60025
-      // Now, parser.incoming is pointed to the new IncomingMessage,
-      // we need to rewrite it to the first one and skip all the pending IncomingMessage
-      socket.parser.incoming = req.res;
-      socket.parser.incoming[kSkipPendingData] = true;
-    }
-    return 0;
-  }
-  req.res = res;
-
-  // Skip body and treat as Upgrade.
-  if (res.upgrade)
-    return 2;
-
-  // Responses to CONNECT request is handled as Upgrade.
-  const method = req.method;
-  if (method === 'CONNECT') {
-    res.upgrade = true;
-    return 2;  // Skip body and treat as Upgrade.
-  }
-
-  if (statusIsInformational(res.statusCode)) {
-    // Restart the parser, as this is a 1xx informational message.
-    req.res = null; // Clear res so that we don't hit double-responses.
-    // Maintain compatibility by sending 100-specific events
-    if (res.statusCode === 100) {
-      req.emit('continue');
-    }
-    // Send information events to all 1xx responses except 101 Upgrade.
-    req.emit('information', {
-      statusCode: res.statusCode,
-      statusMessage: res.statusMessage,
-      httpVersion: res.httpVersion,
-      httpVersionMajor: res.httpVersionMajor,
-      httpVersionMinor: res.httpVersionMinor,
-      headers: res.headers,
-      rawHeaders: res.rawHeaders,
-    });
-
-    return 1;  // Skip body but don't treat as Upgrade.
-  }
-
-  if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
-    // Server MUST respond with Connection:keep-alive for us to enable it.
-    // If we've been upgraded (via WebSockets) we also shouldn't try to
-    // keep the connection open.
-    req.shouldKeepAlive = false;
-  }
-
-  if (req[kClientRequestStatistics] && hasObserver('http')) {
-    stopPerf(req, kClientRequestStatistics, {
-      detail: {
-        res: {
-          statusCode: res.statusCode,
-          statusMessage: res.statusMessage,
-          headers: res.headers,
-        },
-      },
-    });
-  }
-  if (onClientResponseFinishChannel.hasSubscribers) {
-    onClientResponseFinishChannel.publish({
-      request: req,
-      response: res,
-    });
-  }
-  if (isTraceHTTPEnabled() && typeof req._traceEventId === 'number') {
-    traceEnd(HTTP_CLIENT_TRACE_EVENT_NAME, req._traceEventId, {
-      path: req.path,
-      statusCode: res.statusCode,
-    });
-  }
-  req.res = res;
-  res.req = req;
-
-  // Add our listener first, so that we guarantee socket cleanup
-  res.on('end', responseOnEnd);
-  req.on('finish', requestOnFinish);
-  socket.on('timeout', responseOnTimeout);
-
-  // If the user did not listen for the 'response' event, then they
-  // can't possibly read the data, so we ._dump() it into the void
-  // so that the socket doesn't hang there in a paused state.
-  if (req.aborted || !req.emit('response', res))
-    res._dump();
-
-  if (method === 'HEAD')
-    return 1;  // Skip body but don't treat as Upgrade.
-
-  if (res.statusCode === 304) {
-    res.complete = true;
-    return 1; // Skip body as there won't be any
-  }
-
-  return 0;  // No special treatment.
-}
-
-// client
-function responseKeepAlive(req) {
-  const socket = req.socket;
-
-  debug('AGENT socket keep-alive');
-  if (req.timeoutCb) {
-    socket.setTimeout(0, req.timeoutCb);
-    req.timeoutCb = null;
-  }
-  socket.removeListener('close', socketCloseListener);
-  socket.removeListener('error', socketErrorListener);
-  socket.removeListener('data', socketOnData);
-  socket.removeListener('end', socketOnEnd);
-
-  // TODO(ronag): Between here and emitFreeNT the socket
-  // has no 'error' handler.
-
-  // There are cases where _handle === null. Avoid those. Passing undefined to
-  // nextTick() will call getDefaultTriggerAsyncId() to retrieve the id.
-  const asyncId = socket._handle ? socket._handle.getAsyncId() : undefined;
-  // Mark this socket as available, AFTER user-added end
-  // handlers have a chance to run.
-  defaultTriggerAsyncIdScope(asyncId, process.nextTick, emitFreeNT, req);
-
-  req.destroyed = true;
-  if (req.res) {
-    // Detach socket from IncomingMessage to avoid destroying the freed
-    // socket in IncomingMessage.destroy().
-    req.res.socket = null;
-  }
-}
-
-function responseOnEnd() {
-  const req = this.req;
-  const socket = req.socket;
-
-  this[kDetachAbortSignal]();
-
-  if (socket) {
-    if (req.timeoutCb) socket.removeListener('timeout', emitRequestTimeout);
-    socket.removeListener('timeout', responseOnTimeout);
-  }
-
-  req._ended = true;
-
-  if (!req.shouldKeepAlive) {
-    if (socket.writable) {
-      debug('AGENT socket.destroySoon()');
-      if (typeof socket.destroySoon === 'function')
-        socket.destroySoon();
-      else
-        socket.end();
-    }
-    assert(!socket.writable);
-  } else if (req.writableFinished && !this.aborted) {
-    assert(req.finished);
-    // We can assume `req.finished` means all data has been written since:
-    // - `'responseOnEnd'` means we have been assigned a socket.
-    // - when we have a socket we write directly to it without buffering.
-    // - `req.finished` means `end()` has been called and no further data.
-    //   can be written
-    // In addition, `req.writableFinished` means all data written has been
-    // accepted by the kernel. (i.e. the `req.socket` is drained).Without
-    // this constraint, we may assign a non drained socket to a request.
-    responseKeepAlive(req);
-  }
-}
-
-function responseOnTimeout() {
-  const req = this._httpMessage;
-  if (!req) return;
-  const res = req.res;
-  if (!res) return;
-  res.emit('timeout');
-}
-
-// This function is necessary in the case where we receive the entire response
-// from the server before we finish sending out the request.
-function requestOnFinish() {
-  const req = this;
-
-  // If the response ends before this request finishes writing, `responseOnEnd()`
-  // already released the socket. When `finish` fires later, that socket may
-  // belong to a different request, so only call `responseKeepAlive()` when the
-  // original request is still alive (`!req.destroyed`).
-  if (req.shouldKeepAlive && req._ended && !req.destroyed)
-    responseKeepAlive(req);
-}
-
-function emitFreeNT(req) {
-  req._closed = true;
-  req.emit('close');
-  if (req.socket) {
-    req.socket.emit('free');
-  }
-}
-
-function tickOnSocket(req, socket) {
-  const parser = parsers.alloc();
-  req.socket = socket;
-  const lenientFlags = calculateLenientFlags(req.httpValidation, req.insecureHTTPParser);
-  // Propagate headers limit from request object to parser
-  if (typeof req.maxHeadersCount === 'number') {
-    parser.maxHeaderPairs = req.maxHeadersCount << 1;
-  }
-  parser.initialize(HTTPParser.RESPONSE,
-                    new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req),
-                    req.maxHeaderSize || 0,
-                    lenientFlags,
-                    undefined,
-                    parser.maxHeaderPairs);
-  parser.socket = socket;
-  parser.outgoing = req;
-  req.parser = parser;
-
-  socket.parser = parser;
-  socket._httpMessage = req;
-
-  parser.joinDuplicateHeaders = req.joinDuplicateHeaders;
-
-  parser.onIncoming = parserOnIncomingClient;
-  socket.on('data', socketOnData);
-  socket.on('end', socketOnEnd);
-  socket.on('close', socketCloseListener);
-  socket.on('drain', ondrain);
-
-  if (
-    req.timeout !== undefined ||
-    (req.agent?.options?.timeout)
-  ) {
-    listenSocketTimeout(req);
-  }
-  req.emit('socket', socket);
-}
-
-function emitRequestTimeout() {
-  const req = this._httpMessage;
-  if (req) {
-    req.emit('timeout');
-  }
-}
-
-function listenSocketTimeout(req) {
-  if (req.timeoutCb) {
-    return;
-  }
-  // Set timeoutCb so it will get cleaned up on request end.
-  req.timeoutCb = emitRequestTimeout;
-  // Delegate socket timeout event.
-  if (req.socket) {
-    req.socket.once('timeout', emitRequestTimeout);
-  } else {
-    req.on('socket', (socket) => {
-      socket.once('timeout', emitRequestTimeout);
-    });
-  }
-}
-
-ClientRequest.prototype.onSocket = function onSocket(socket, err) {
-  // Attach the error listener synchronously so that any errors emitted on
-  // the socket before onSocketNT runs (e.g. from a blocklist check or other
-  // next-tick error) are forwarded to the request and can be caught by the
-  // user's error handler. socketErrorListener requires socket._httpMessage
-  // to be set so we set it here too.
-  if (socket && !err) {
-    socket._httpMessage = this;
-    socket.on('error', socketErrorListener);
-  }
-  process.nextTick(onSocketNT, this, socket, err);
-};
-
-function onSocketNT(req, socket, err) {
-  if (req.destroyed || err) {
-    req.destroyed = true;
-
-    function _destroy(req, err) {
-      if (!req.aborted && !err) {
-        err = new ConnResetException('socket hang up');
-      }
-      // ERR_PROXY_TUNNEL is handled by the proxying logic.
-      // Skip if the error was already emitted by the early socketErrorListener.
-      if (err && err.code !== 'ERR_PROXY_TUNNEL' &&
-          !socket?._hadError) {
-        emitErrorEvent(req, err);
-      }
-      req._closed = true;
-      req.emit('close');
-    }
-
-    if (socket) {
-      if (!err && req.agent && !socket.destroyed) {
-        socket.emit('free');
-        socket.removeListener('error', socketErrorListener);
-      } else {
-        finished(socket.destroy(err || req[kError]), (er) => {
-          if (er?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
-            er = null;
-          }
-          _destroy(req, er || err);
-        });
-        return;
-      }
-    }
-
-    _destroy(req, err || req[kError]);
-  } else {
-    tickOnSocket(req, socket);
-    req._flush();
-  }
-}
-
-ClientRequest.prototype._deferToConnect = _deferToConnect;
-function _deferToConnect(method, arguments_) {
-  // This function is for calls that need to happen once the socket is
-  // assigned to this request and writable. It's an important promisy
-  // thing for all the socket calls that happen either now
-  // (when a socket is assigned) or in the future (when a socket gets
-  // assigned out of the pool and is eventually writable).
-
-  const callSocketMethod = () => {
-    if (method)
-      ReflectApply(this.socket[method], this.socket, arguments_);
-  };
-
-  const onSocket = () => {
-    if (this.socket.writable) {
-      callSocketMethod();
-    } else {
-      this.socket.once('connect', callSocketMethod);
-    }
-  };
-
-  if (!this.socket) {
-    this.once('socket', onSocket);
-  } else {
-    onSocket();
-  }
-}
-
-ClientRequest.prototype.setTimeout = function setTimeout(msecs, callback) {
-  if (this._ended) {
-    return this;
-  }
-
-  listenSocketTimeout(this);
-  msecs = getTimerDuration(msecs, 'msecs');
-  if (callback) this.once('timeout', callback);
-
-  if (this.socket) {
-    setSocketTimeout(this.socket, msecs);
-  } else {
-    this.once('socket', (sock) => setSocketTimeout(sock, msecs));
-  }
-
-  return this;
-};
-
-function setSocketTimeout(sock, msecs) {
-  if (sock.connecting) {
-    sock.once('connect', function() {
-      sock.setTimeout(msecs);
-    });
-  } else {
-    sock.setTimeout(msecs);
-  }
-}
-
-ClientRequest.prototype.setNoDelay = function setNoDelay(noDelay) {
-  this._deferToConnect('setNoDelay', [noDelay]);
-};
-
-ClientRequest.prototype.setSocketKeepAlive =
-    function setSocketKeepAlive(enable, initialDelay) {
-      this._deferToConnect('setKeepAlive', [enable, initialDelay]);
-    };
-
-ClientRequest.prototype.clearTimeout = function clearTimeout(cb) {
-  this.setTimeout(0, cb);
-};
+const { ClientRequest } = require('internal/http/client');

 module.exports = {
   ClientRequest,
 };
+
+process.emitWarning('The _http_client module is deprecated. Use `node:http` instead.',
+                    'DeprecationWarning', 'DEP0199');
diff --git a/lib/_http_common.js b/lib/_http_common.js
index d5e7bdedee3..59b08153d13 100644
--- a/lib/_http_common.js
+++ b/lib/_http_common.js
@@ -22,317 +22,29 @@
 'use strict';

 const {
-  MathMin,
-  Symbol,
-  Uint8Array,
-} = primordials;
-const { setImmediate } = require('timers');
-
-const { methods, allMethods, HTTPParser } = internalBinding('http_parser');
-const { getOptionValue } = require('internal/options');
-const insecureHTTPParser = getOptionValue('--insecure-http-parser');
-
-const FreeList = require('internal/freelist');
-const incoming = require('_http_incoming');
-const {
-  IncomingMessage,
-  readStart,
-  readStop,
-} = incoming;
-
-const kIncomingMessage = Symbol('IncomingMessage');
-const kSkipPendingData = Symbol('SkipPendingData');
-const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0;
-const kOnHeaders = HTTPParser.kOnHeaders | 0;
-const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
-const kOnBody = HTTPParser.kOnBody | 0;
-const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
-const kOnExecute = HTTPParser.kOnExecute | 0;
-const kOnTimeout = HTTPParser.kOnTimeout | 0;
-
-const MAX_HEADER_PAIRS = 2000;
-
-// Only called in the slow case where slow means
-// that the request headers were either fragmented
-// across multiple TCP packets or too large to be
-// processed in a single run. This method is also
-// called to process trailing HTTP headers.
-function parserOnHeaders(headers, url) {
-  // Once we exceeded headers limit - stop collecting them
-  const capacity = this.maxHeaderPairs - this._headers.length;
-  if (this.maxHeaderPairs <= 0 || capacity >= headers.length) {
-    this._headers.push(...headers);
-  } else if (capacity > 0) {
-    this._headers.push(...headers.slice(0, capacity));
-  }
-  this._url += url;
-}
-
-const HTTP_VERSION_1_1 = '1.1';
-
-// `headers` and `url` are set only if .onHeaders() has not been called for
-// this request.
-// `url` is not set for response parsers but that's not applicable here since
-// all our parsers are request parsers.
-function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
-                                 url, statusCode, statusMessage, upgrade,
-                                 shouldKeepAlive) {
-  const parser = this;
-  const { socket } = parser;
-
-  if (headers === undefined) {
-    headers = parser._headers;
-    parser._headers = [];
-  }
-
-  if (url === undefined) {
-    url = parser._url;
-    parser._url = '';
-  }
-
-  // Parser is also used by http client
-  const ParserIncomingMessage = (socket?.server?.[kIncomingMessage]) ||
-                                 IncomingMessage;
-
-  const incoming = parser.incoming = new ParserIncomingMessage(socket);
-  incoming.httpVersionMajor = versionMajor;
-  incoming.httpVersionMinor = versionMinor;
-  incoming.httpVersion = versionMajor === 1 && versionMinor === 1 ?
-    HTTP_VERSION_1_1 :
-    `${versionMajor}.${versionMinor}`;
-  incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders ||
-                                  parser.joinDuplicateHeaders;
-  incoming.url = url;
-  incoming.upgrade = upgrade;
-
-  let n = headers.length;
-
-  // If parser.maxHeaderPairs <= 0 assume that there's no limit.
-  if (parser.maxHeaderPairs > 0)
-    n = MathMin(n, parser.maxHeaderPairs);
-
-  incoming._addHeaderLines(headers, n);
-
-  if (typeof method === 'number') {
-    // server only
-    incoming.method = allMethods[method];
-  } else {
-    // client only
-    incoming.statusCode = statusCode;
-    incoming.statusMessage = statusMessage;
-  }
-
-  return parser.onIncoming(incoming, shouldKeepAlive);
-}
-
-function parserOnBody(b) {
-  const stream = this.incoming;
-
-  // If the stream has already been removed, then drop it.
-  if (stream === null || stream[kSkipPendingData])
-    return;
-
-  // Pretend this was the result of a stream._read call.
-  if (!stream._dumped) {
-    const ret = stream.push(b);
-    if (!ret)
-      readStop(this.socket);
-  }
-}
-
-function parserOnMessageComplete() {
-  const parser = this;
-  const stream = parser.incoming;
-
-  if (stream !== null && !stream[kSkipPendingData]) {
-    stream.complete = true;
-    // Emit any trailing headers.
-    const headers = parser._headers;
-    if (headers.length) {
-      stream._addHeaderLines(headers, headers.length);
-      parser._headers = [];
-      parser._url = '';
-    }
-
-    // For emit end event
-    stream.push(null);
-  }
-
-  // Force to read the next incoming message
-  readStart(parser.socket);
-}
-
-
-const parsers = new FreeList('parsers', 1000, function parsersCb() {
-  const parser = new HTTPParser();
-
-  cleanParser(parser);
-
-  parser[kOnHeaders] = parserOnHeaders;
-  parser[kOnHeadersComplete] = parserOnHeadersComplete;
-  parser[kOnBody] = parserOnBody;
-  parser[kOnMessageComplete] = parserOnMessageComplete;
-
-  return parser;
-});
-
-function closeParserInstance(parser) { parser.close(); }
-
-// Free the parser and also break any links that it
-// might have to any other things.
-// TODO: All parser data should be attached to a
-// single object, so that it can be easily cleaned
-// up by doing `parser.data = {}`, which should
-// be done in FreeList.free.  `parsers.free(parser)`
-// should be all that is needed.
-function freeParser(parser, req, socket) {
-  if (parser) {
-    if (parser._consumed)
-      parser.unconsume();
-    parser.remove();
-    cleanParser(parser);
-    if (parsers.free(parser) === false) {
-      // Make sure the parser's stack has unwound before deleting the
-      // corresponding C++ object through .close().
-      setImmediate(closeParserInstance, parser);
-    } else {
-      // Since the Parser destructor isn't going to run the destroy() callbacks
-      // it needs to be triggered manually.
-      parser.free();
-    }
-  }
-  if (req) {
-    req.parser = null;
-  }
-  if (socket) {
-    socket.parser = null;
-  }
-}
-
-// Character code ranges for valid HTTP tokens
-// Valid chars: ^_`a-zA-Z-0-9!#$%&'*+.|~
-// Based on RFC 7230 Section 3.2.6 token definition
-// See https://tools.ietf.org/html/rfc7230#section-3.2.6
-const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
-const validTokenChars = new Uint8Array([
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
-  0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32-47 (!"#$%&'()*+,-./)
-  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48-63 (0-9:;<=>?)
-  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64-79 (@A-O)
-  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80-95 (P-Z[\]^_)
-  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96-111 (`a-o)
-  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112-127 (p-z{|}~)
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128-143
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144-159
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-175
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176-191
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192-207
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208-223
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224-239
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 240-255
-]);
-
-/**
- * Verifies that the given val is a valid HTTP token
- * per the rules defined in RFC 7230
- * See https://tools.ietf.org/html/rfc7230#section-3.2.6
- * @param {string} val
- * @returns {boolean}
- */
-function checkIsHttpToken(val) {
-  if (val.length >= 10) {
-    return tokenRegExp.test(val);
-  }
-
-  if (val.length === 0) return false;
-
-  // Use lookup table for short strings, regex for longer ones
-  for (let i = 0; i < val.length; i++) {
-    if (!validTokenChars[val.charCodeAt(i)]) {
-      return false;
-    }
-  }
-  return true;
-}
-
-// Strict header value regex per RFC 7230 (original/default behavior):
-// field-value = *( field-content / obs-fold )
-// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
-// field-vchar = VCHAR / obs-text
-// This rejects control characters (0x00-0x1f except HTAB) and DEL (0x7f).
-const strictHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
-
-// Lenient header value regex per Fetch spec (https://fetch.spec.whatwg.org/#header-value):
-// - Must contain no 0x00 (NUL) or HTTP newline bytes (0x0a LF, 0x0d CR)
-// - Must be byte sequences (0x00-0xff), not arbitrary unicode
-// This allows most control characters except NUL, CR, and LF.
-// eslint-disable-next-line no-control-regex
-const lenientHeaderCharRegex = /[\x00\x0a\x0d]|[^\x00-\xff]/;
-
-/**
- * True if val contains an invalid header value character.
- * By default uses strict validation per RFC 7230.
- * When lenient=true, uses relaxed validation per Fetch spec.
- * @param {string} val
- * @param {boolean} [lenient] - Use lenient validation (Fetch spec rules)
- * @returns {boolean}
- */
-function checkInvalidHeaderChar(val, lenient = false) {
-  const regex = lenient ? lenientHeaderCharRegex : strictHeaderCharRegex;
-  return regex.test(val);
-}
-
-function cleanParser(parser) {
-  parser._headers = [];
-  parser._url = '';
-  parser.socket = null;
-  parser.incoming = null;
-  parser.outgoing = null;
-  parser.maxHeaderPairs = MAX_HEADER_PAIRS;
-  parser[kOnMessageBegin] = null;
-  parser[kOnExecute] = null;
-  parser[kOnTimeout] = null;
-  parser._consumed = false;
-  parser.onIncoming = null;
-  parser.joinDuplicateHeaders = null;
-}
-
-function prepareError(err, parser, rawPacket) {
-  err.rawPacket = rawPacket || parser.getCurrentBuffer();
-  if (typeof err.reason === 'string')
-    err.message = `Parse Error: ${err.reason}`;
-}
-
-let warnedLenient = false;
-
-function isLenient() {
-  if (insecureHTTPParser && !warnedLenient) {
-    warnedLenient = true;
-    process.emitWarning('Using insecure HTTP parsing');
-  }
-  return insecureHTTPParser;
-}
+  _checkInvalidHeaderChar,
+  _checkIsHttpToken,
+  chunkExpression,
+  continueExpression,
+  CRLF,
+  freeParser,
+  methods,
+  parsers,
+  kIncomingMessage,
+  HTTPParser,
+  isLenient,
+  calculateLenientFlags,
+  prepareError,
+  kSkipPendingData,
+} = require('internal/http/common');

-function calculateLenientFlags(httpValidation, insecureHTTPParserOption) {
-  if (httpValidation === 'strict') {
-    return HTTPParser.kLenientNone | 0;
-  } else if (httpValidation === 'relaxed') {
-    return HTTPParser.kLenientHeaderValueRelaxed | 0;
-  } else if (httpValidation === 'insecure') {
-    return HTTPParser.kLenientAll | 0;
-  }
-  const lenient = insecureHTTPParserOption === undefined ?
-    isLenient() : insecureHTTPParserOption;
-  return lenient ? HTTPParser.kLenientAll | 0 : HTTPParser.kLenientNone | 0;
-}

 module.exports = {
-  _checkInvalidHeaderChar: checkInvalidHeaderChar,
-  _checkIsHttpToken: checkIsHttpToken,
-  chunkExpression: /(?:^|\W)chunked(?:$|\W)/i,
-  continueExpression: /(?:^|\W)100-continue(?:$|\W)/i,
-  CRLF: '\r\n', // TODO: Deprecate this.
+  _checkInvalidHeaderChar,
+  _checkIsHttpToken,
+  chunkExpression,
+  continueExpression,
+  CRLF, // TODO: Deprecate this.
   freeParser,
   methods,
   parsers,
@@ -343,3 +55,6 @@ module.exports = {
   prepareError,
   kSkipPendingData,
 };
+
+process.emitWarning('The _http_common module is deprecated.',
+                    'DeprecationWarning', 'DEP0199');
diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js
index 0aa7d30580e..5a50d13460e 100644
--- a/lib/_http_incoming.js
+++ b/lib/_http_incoming.js
@@ -22,615 +22,14 @@
 'use strict';

 const {
-  ObjectDefineProperty,
-  ObjectSetPrototypeOf,
-  Symbol,
-} = primordials;
-
-const { Readable, finished } = require('stream');
-const {
-  deprecateInstantiation,
-} = require('internal/util');
-
-const { AbortController } = require('internal/abort_controller');
-
-const kHeaders = Symbol('kHeaders');
-const kHeadersDistinct = Symbol('kHeadersDistinct');
-const kHeadersCount = Symbol('kHeadersCount');
-const kTrailers = Symbol('kTrailers');
-const kTrailersDistinct = Symbol('kTrailersDistinct');
-const kTrailersCount = Symbol('kTrailersCount');
-const kAbortController = Symbol('kAbortController');
-const kAbortSignalSocket = Symbol('kAbortSignalSocket');
-const kAbortSignalListener = Symbol('kAbortSignalListener');
-const kAbortSignalDetached = Symbol('kAbortSignalDetached');
-const kAttachAbortSignal = Symbol('kAttachAbortSignal');
-const kDetachAbortSignal = Symbol('kDetachAbortSignal');
-
-function readStart(socket) {
-  if (socket && !socket._paused && socket.readable)
-    socket.resume();
-}
-
-function readStop(socket) {
-  if (socket)
-    socket.pause();
-}
-
-/* Abstract base class for ServerRequest and ClientResponse. */
-function IncomingMessage(socket) {
-  if (!(this instanceof IncomingMessage)) {
-    return deprecateInstantiation(IncomingMessage, 'DEP0195', socket);
-  }
-
-  Readable.call(this);
-
-  if (socket) {
-    const hwm = socket.readableHighWaterMark;
-    if (this._readableState.highWaterMark !== hwm) {
-      this._readableState.highWaterMark = hwm;
-    }
-  }
-
-  this._readableState.readingMore = true;
-
-  this.socket = socket;
-
-  this.httpVersionMajor = null;
-  this.httpVersionMinor = null;
-  this.httpVersion = null;
-  this.complete = false;
-  this[kHeaders] = null;
-  this[kHeadersCount] = 0;
-  this.rawHeaders = [];
-  this[kTrailers] = null;
-  this[kTrailersCount] = 0;
-  this.rawTrailers = [];
-  this.joinDuplicateHeaders = false;
-  this.aborted = false;
-
-  this.upgrade = null;
-
-  // request (server) only
-  this.url = '';
-  this.method = null;
-
-  // response (client) only
-  this.statusCode = null;
-  this.statusMessage = null;
-  this.client = socket;
-
-  this._consuming = false;
-  // Flag for when we decide that this message cannot possibly be
-  // read by the user, so there's no point continuing to handle it.
-  this._dumped = false;
-  this[kAbortController] = null;
-  this[kAbortSignalSocket] = null;
-  this[kAbortSignalListener] = null;
-  this[kAbortSignalDetached] = false;
-}
-ObjectSetPrototypeOf(IncomingMessage.prototype, Readable.prototype);
-ObjectSetPrototypeOf(IncomingMessage, Readable);
-
-ObjectDefineProperty(IncomingMessage.prototype, 'connection', {
-  __proto__: null,
-  get: function() {
-    return this.socket;
-  },
-  set: function(val) {
-    this.socket = val;
-  },
-});
-
-ObjectDefineProperty(IncomingMessage.prototype, 'headers', {
-  __proto__: null,
-  get: function() {
-    if (!this[kHeaders]) {
-      this[kHeaders] = { __proto__: null };
-
-      const src = this.rawHeaders;
-      const dst = this[kHeaders];
-
-      for (let n = 0; n < this[kHeadersCount]; n += 2) {
-        this._addHeaderLine(src[n + 0], src[n + 1], dst);
-      }
-    }
-    return this[kHeaders];
-  },
-  set: function(val) {
-    this[kHeaders] = val;
-  },
-});
-
-ObjectDefineProperty(IncomingMessage.prototype, 'headersDistinct', {
-  __proto__: null,
-  get: function() {
-    if (!this[kHeadersDistinct]) {
-      this[kHeadersDistinct] = { __proto__: null };
-
-      const src = this.rawHeaders;
-      const dst = this[kHeadersDistinct];
-
-      for (let n = 0; n < this[kHeadersCount]; n += 2) {
-        this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
-      }
-    }
-    return this[kHeadersDistinct];
-  },
-  set: function(val) {
-    this[kHeadersDistinct] = val;
-  },
-});
-
-ObjectDefineProperty(IncomingMessage.prototype, 'trailers', {
-  __proto__: null,
-  get: function() {
-    if (!this[kTrailers]) {
-      this[kTrailers] = { __proto__: null };
-
-      const src = this.rawTrailers;
-      const dst = this[kTrailers];
-
-      for (let n = 0; n < this[kTrailersCount]; n += 2) {
-        this._addHeaderLine(src[n + 0], src[n + 1], dst);
-      }
-    }
-    return this[kTrailers];
-  },
-  set: function(val) {
-    this[kTrailers] = val;
-  },
-});
-
-ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', {
-  __proto__: null,
-  get: function() {
-    if (!this[kTrailersDistinct]) {
-      this[kTrailersDistinct] = { __proto__: null };
-
-      const src = this.rawTrailers;
-      const dst = this[kTrailersDistinct];
-
-      for (let n = 0; n < this[kTrailersCount]; n += 2) {
-        this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
-      }
-    }
-    return this[kTrailersDistinct];
-  },
-  set: function(val) {
-    this[kTrailersDistinct] = val;
-  },
-});
-
-ObjectDefineProperty(IncomingMessage.prototype, 'signal', {
-  __proto__: null,
-  configurable: true,
-  get: function() {
-    if (this[kAbortController] === null) {
-      const ac = new AbortController();
-      this[kAbortController] = ac;
-      if (this.destroyed && (!this.readableEnded || !this.complete)) {
-        ac.abort();
-      } else {
-        this[kAttachAbortSignal]();
-      }
-    }
-    return this[kAbortController].signal;
-  },
-});
-
-IncomingMessage.prototype[kAttachAbortSignal] = function() {
-  if (this[kAbortController].signal.aborted ||
-      this[kAbortSignalDetached] ||
-      this[kAbortSignalListener] !== null) {
-    return;
-  }
-
-  const socket = this.socket;
-  if (!socket) {
-    return;
-  }
-
-  if (socket.destroyed) {
-    abortSignal(this);
-    return;
-  }
-
-  this[kAbortSignalSocket] = socket;
-  this[kAbortSignalListener] = () => {
-    abortSignal(this);
-  };
-  socket.once('close', this[kAbortSignalListener]);
-};
-
-IncomingMessage.prototype[kDetachAbortSignal] = function() {
-  const socket = this[kAbortSignalSocket];
-  const listener = this[kAbortSignalListener];
-  this[kAbortSignalDetached] = true;
-  this[kAbortSignalSocket] = null;
-  this[kAbortSignalListener] = null;
-  if (socket !== null && listener !== null) {
-    socket.removeListener('close', listener);
-  }
-};
-
-IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
-  if (callback)
-    this.on('timeout', callback);
-  this.socket.setTimeout(msecs);
-  return this;
-};
-
-IncomingMessage.prototype._read = function _read() {
-  if (!this._consuming) {
-    this._readableState.readingMore = false;
-    this._consuming = true;
-  }
-
-  // We actually do almost nothing here, because the parserOnBody
-  // function fills up our internal buffer directly.  However, we
-  // do need to unpause the underlying socket so that it flows.
-  if (this.socket.readable)
-    readStart(this.socket);
-};
-
-// It's possible that the socket will be destroyed, and removed from
-// any messages, before ever calling this.  In that case, just skip
-// it, since something else is destroying this connection anyway.
-IncomingMessage.prototype._destroy = function _destroy(err, cb) {
-  if (!this.readableEnded || !this.complete) {
-    this.aborted = true;
-    this.emit('aborted');
-    abortSignal(this);
-  }
-
-  // If aborted and the underlying socket is not already destroyed,
-  // destroy it.
-  // We have to check if the socket is already destroyed because finished
-  // does not call the callback when this method is invoked from `_http_client`
-  // in `test/parallel/test-http-client-spurious-aborted.js`
-  if (this.socket && !this.socket.destroyed && this.aborted) {
-    this.socket.destroy(err);
-    const cleanup = finished(this.socket, (e) => {
-      if (e?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
-        e = null;
-      }
-      cleanup();
-      process.nextTick(onError, this, e || err, cb);
-    });
-  } else if (err == null && !this.aborted) {
-    // The message was received completely and is being destroyed cleanly:
-    // complete the destroy synchronously. 'close' is still emitted on a
-    // later tick by the stream machinery. The deferral below only exists
-    // so that 'error' listeners attached right after destroy(err) still
-    // receive the error, which cannot matter when there is no error.
-    cb();
-  } else {
-    process.nextTick(onError, this, err, cb);
-  }
-};
-
-function abortSignal(self) {
-  self[kDetachAbortSignal]();
-  if (self[kAbortController] !== null) {
-    self[kAbortController].abort();
-  }
-}
-
-IncomingMessage.prototype._addHeaderLines = _addHeaderLines;
-function _addHeaderLines(headers, n) {
-  if (headers?.length) {
-    let dest;
-    if (this.complete) {
-      this.rawTrailers = headers;
-      this[kTrailersCount] = n;
-      dest = this[kTrailers];
-    } else {
-      this.rawHeaders = headers;
-      this[kHeadersCount] = n;
-      dest = this[kHeaders];
-    }
-
-    if (dest) {
-      for (let i = 0; i < n; i += 2) {
-        this._addHeaderLine(headers[i], headers[i + 1], dest);
-      }
-    }
-  }
-}
-
-
-// How repeated instances of a header field are combined in the headers
-// object. Fields that are not known are joined with ', '.
-const kFirstWins = 0;
-const kJoinComma = 1;
-const kJoinSemicolon = 2;
-const kArray = 3;
-
-function knownField(name, merge) {
-  return { name, merge };
-}
-
-// Later values are dropped, unless joinDuplicateHeaders is set.
-const kAge = knownField('age', kFirstWins);
-const kHost = knownField('host', kFirstWins);
-const kFrom = knownField('from', kFirstWins);
-const kETag = knownField('etag', kFirstWins);
-const kServer = knownField('server', kFirstWins);
-const kReferer = knownField('referer', kFirstWins);
-const kExpires = knownField('expires', kFirstWins);
-const kLocation = knownField('location', kFirstWins);
-const kUserAgent = knownField('user-agent', kFirstWins);
-const kRetryAfter = knownField('retry-after', kFirstWins);
-const kContentType = knownField('content-type', kFirstWins);
-const kMaxForwards = knownField('max-forwards', kFirstWins);
-const kAuthorization = knownField('authorization', kFirstWins);
-const kLastModified = knownField('last-modified', kFirstWins);
-const kContentLength = knownField('content-length', kFirstWins);
-const kIfModifiedSince = knownField('if-modified-since', kFirstWins);
-const kProxyAuthorization = knownField('proxy-authorization', kFirstWins);
-const kIfUnmodifiedSince = knownField('if-unmodified-since', kFirstWins);
-
-// Values are joined with ', '.
-const kDate = knownField('date', kJoinComma);
-const kVary = knownField('vary', kJoinComma);
-const kOrigin = knownField('origin', kJoinComma);
-const kExpect = knownField('expect', kJoinComma);
-const kAccept = knownField('accept', kJoinComma);
-const kUpgrade = knownField('upgrade', kJoinComma);
-const kIfMatch = knownField('if-match', kJoinComma);
-const kConnection = knownField('connection', kJoinComma);
-const kCacheControl = knownField('cache-control', kJoinComma);
-const kIfNoneMatch = knownField('if-none-match', kJoinComma);
-const kAcceptEncoding = knownField('accept-encoding', kJoinComma);
-const kAcceptLanguage = knownField('accept-language', kJoinComma);
-const kXForwardedFor = knownField('x-forwarded-for', kJoinComma);
-const kContentEncoding = knownField('content-encoding', kJoinComma);
-const kXForwardedHost = knownField('x-forwarded-host', kJoinComma);
-const kTransferEncoding = knownField('transfer-encoding', kJoinComma);
-const kXForwardedProto = knownField('x-forwarded-proto', kJoinComma);
-
-// Values are joined with '; '.
-const kCookie = knownField('cookie', kJoinSemicolon);
-
-// Values are collected into an array.
-const kSetCookie = knownField('set-cookie', kArray);
-
-// Returns the descriptor of a known field, or the lowercased name of any other
-// field. The 'traditional cased' and lowercase spellings of known fields are
-// matched first to avoid calling toLowerCase() for them.
-// TODO: perhaps http_parser could be returning both raw and lowercased versions
-// of known header names to avoid us having to call toLowerCase() for those
-// headers.
-function matchKnownFields(field, lowercased) {
-  switch (field.length) {
-    case 3:
-      if (field === 'Age' || field === 'age') return kAge;
-      break;
-    case 4:
-      if (field === 'Host' || field === 'host') return kHost;
-      if (field === 'From' || field === 'from') return kFrom;
-      if (field === 'ETag' || field === 'etag') return kETag;
-      if (field === 'Date' || field === 'date') return kDate;
-      if (field === 'Vary' || field === 'vary') return kVary;
-      break;
-    case 6:
-      if (field === 'Server' || field === 'server') return kServer;
-      if (field === 'Cookie' || field === 'cookie') return kCookie;
-      if (field === 'Origin' || field === 'origin') return kOrigin;
-      if (field === 'Expect' || field === 'expect') return kExpect;
-      if (field === 'Accept' || field === 'accept') return kAccept;
-      break;
-    case 7:
-      if (field === 'Referer' || field === 'referer') return kReferer;
-      if (field === 'Expires' || field === 'expires') return kExpires;
-      if (field === 'Upgrade' || field === 'upgrade') return kUpgrade;
-      break;
-    case 8:
-      if (field === 'Location' || field === 'location')
-        return kLocation;
-      if (field === 'If-Match' || field === 'if-match')
-        return kIfMatch;
-      break;
-    case 10:
-      if (field === 'User-Agent' || field === 'user-agent')
-        return kUserAgent;
-      if (field === 'Set-Cookie' || field === 'set-cookie')
-        return kSetCookie;
-      if (field === 'Connection' || field === 'connection')
-        return kConnection;
-      break;
-    case 11:
-      if (field === 'Retry-After' || field === 'retry-after')
-        return kRetryAfter;
-      break;
-    case 12:
-      if (field === 'Content-Type' || field === 'content-type')
-        return kContentType;
-      if (field === 'Max-Forwards' || field === 'max-forwards')
-        return kMaxForwards;
-      break;
-    case 13:
-      if (field === 'Authorization' || field === 'authorization')
-        return kAuthorization;
-      if (field === 'Last-Modified' || field === 'last-modified')
-        return kLastModified;
-      if (field === 'Cache-Control' || field === 'cache-control')
-        return kCacheControl;
-      if (field === 'If-None-Match' || field === 'if-none-match')
-        return kIfNoneMatch;
-      break;
-    case 14:
-      if (field === 'Content-Length' || field === 'content-length')
-        return kContentLength;
-      break;
-    case 15:
-      if (field === 'Accept-Encoding' || field === 'accept-encoding')
-        return kAcceptEncoding;
-      if (field === 'Accept-Language' || field === 'accept-language')
-        return kAcceptLanguage;
-      if (field === 'X-Forwarded-For' || field === 'x-forwarded-for')
-        return kXForwardedFor;
-      break;
-    case 16:
-      if (field === 'Content-Encoding' || field === 'content-encoding')
-        return kContentEncoding;
-      if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host')
-        return kXForwardedHost;
-      break;
-    case 17:
-      if (field === 'If-Modified-Since' || field === 'if-modified-since')
-        return kIfModifiedSince;
-      if (field === 'Transfer-Encoding' || field === 'transfer-encoding')
-        return kTransferEncoding;
-      if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto')
-        return kXForwardedProto;
-      break;
-    case 19:
-      if (field === 'Proxy-Authorization' || field === 'proxy-authorization')
-        return kProxyAuthorization;
-      if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since')
-        return kIfUnmodifiedSince;
-      break;
-  }
-  if (lowercased) {
-    return field;
-  }
-  return matchKnownFields(field.toLowerCase(), true);
-}
-// Add the given (field, value) pair to the message
-//
-// Per RFC2616, section 4.2 it is acceptable to join multiple instances of the
-// same header with a ', ' if the header in question supports specification of
-// multiple values this way. The one exception to this is the Cookie header,
-// which has multiple values joined with a '; ' instead. If a header's values
-// cannot be joined in either of these ways, we declare the first instance the
-// winner and drop the second. Fields that are not known are always joined.
-IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
-function _addHeaderLine(field, value, dest) {
-  const known = matchKnownFields(field);
-  let merge = kJoinComma;
-  if (typeof known === 'string') {
-    field = known;
-  } else {
-    field = known.name;
-    merge = known.merge;
-  }
-  if (merge === kJoinComma || merge === kJoinSemicolon) {
-    // Make a delimited list
-    if (typeof dest[field] === 'string') {
-      dest[field] += (merge === kJoinComma ? ', ' : '; ') + value;
-    } else {
-      dest[field] = value;
-    }
-  } else if (merge === kArray) {
-    // Array header -- only Set-Cookie at the moment
-    if (dest['set-cookie'] !== undefined) {
-      dest['set-cookie'].push(value);
-    } else {
-      dest['set-cookie'] = [value];
-    }
-  } else if (this.joinDuplicateHeaders) {
-    // RFC 9110 https://www.rfc-editor.org/rfc/rfc9110#section-5.2
-    // https://github.com/nodejs/node/issues/45699
-    // allow authorization multiple fields
-    // Make a delimited list
-    if (dest[field] === undefined) {
-      dest[field] = value;
-    } else {
-      dest[field] += ', ' + value;
-    }
-  } else if (dest[field] === undefined) {
-    // Drop duplicates
-    dest[field] = value;
-  }
-}
-
-IncomingMessage.prototype._addHeaderLineDistinct = _addHeaderLineDistinct;
-function _addHeaderLineDistinct(field, value, dest) {
-  field = field.toLowerCase();
-  if (!dest[field]) {
-    dest[field] = [value];
-  } else {
-    dest[field].push(value);
-  }
-}
-
-IncomingMessage.prototype._dumpAndCloseReadable = function _dumpAndCloseReadable() {
-  this._dumped = true;
-  this._readableState.ended = true;
-  this._readableState.endEmitted = true;
-  this._readableState.destroyed = true;
-  this._readableState.closed = true;
-  this._readableState.closeEmitted = true;
-};
-
-
-// Call this instead of resume() if we want to just
-// dump all the data to /dev/null
-IncomingMessage.prototype._dump = function _dump() {
-  if (!this._dumped) {
-    this._dumped = true;
-    // If there is buffered data, it may trigger 'data' events.
-    // Remove 'data' event listeners explicitly.
-    this.removeAllListeners('data');
-    if (this.complete && !this.destroyed && this.readableLength === 0) {
-      // The message was fully received and never read: there is nothing to
-      // pull off the wire. Go straight to the 'end' emission instead of
-      // paying for the resume() and flow() scheduling machinery.
-      this.read(0);
-    } else {
-      this.resume();
-    }
-  }
-};
-
-// Case-insensitive ASCII compare against an already-lowercased name.
-// Avoids allocating a lowercased copy of every header name.
-function asciiEqualIgnoreCase(a, lower) {
-  const len = lower.length;
-  if (a.length !== len)
-    return false;
-  if (a === lower)
-    return true;
-  for (let i = 0; i < len; i++) {
-    let c = a.charCodeAt(i);
-    if (c >= 65 && c <= 90)
-      c += 32;
-    if (c !== lower.charCodeAt(i))
-      return false;
-  }
-  return true;
-}
-
-function getRawHeader(msg, lowerName, joinDuplicates) {
-  const rawHeaders = msg.rawHeaders;
-  const count = msg[kHeadersCount];
-  let result;
-  for (let i = 0; i < count; i += 2) {
-    if (!asciiEqualIgnoreCase(rawHeaders[i], lowerName))
-      continue;
-    const value = rawHeaders[i + 1];
-    if (result === undefined) {
-      result = value;
-      if (!joinDuplicates)
-        return result;
-    } else {
-      result += ', ' + value;
-    }
-  }
-  return result;
-}
-
-function onError(self, error, cb) {
-  // This is to keep backward compatible behavior.
-  // An error is emitted only if there are listeners attached to the event.
-  if (self.listenerCount('error') === 0) {
-    cb();
-  } else {
-    cb(error);
-  }
-}
+  IncomingMessage,
+  kDetachAbortSignal,
+  kHeadersCount,
+  asciiEqualIgnoreCase,
+  getRawHeader,
+  readStart,
+  readStop,
+} = require('internal/http/incoming');

 module.exports = {
   IncomingMessage,
@@ -641,3 +40,6 @@ module.exports = {
   readStart,
   readStop,
 };
+
+process.emitWarning('The _http_incoming module is deprecated. Use `node:http` instead.',
+                    'DeprecationWarning', 'DEP0199');
diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js
index f6ce0e5a475..360efe39946 100644
--- a/lib/_http_outgoing.js
+++ b/lib/_http_outgoing.js
@@ -22,1460 +22,15 @@
 'use strict';

 const {
-  ArrayIsArray,
-  MathFloor,
-  ObjectDefineProperty,
-  ObjectHasOwn,
-  ObjectKeys,
-  ObjectSetPrototypeOf,
-  ObjectValues,
-  SafeSet,
-  Symbol,
-} = primordials;
-
-const { getDefaultHighWaterMark } = require('internal/streams/state');
-const assert = require('internal/assert');
-const EE = require('events');
-const Stream = require('stream');
-const { kOutHeaders, utcDateHeader, kNeedDrain } = require('internal/http');
-const { Buffer } = require('buffer');
-const {
-  _checkIsHttpToken: checkIsHttpToken,
-  _checkInvalidHeaderChar: checkInvalidHeaderChar,
-  chunkExpression: RE_TE_CHUNKED,
-  isLenient,
-} = require('_http_common');
-const {
-  defaultTriggerAsyncIdScope,
-  symbols: { async_id_symbol },
-} = require('internal/async_hooks');
-const {
-  codes: {
-    ERR_HTTP_BODY_NOT_ALLOWED,
-    ERR_HTTP_CONTENT_LENGTH_MISMATCH,
-    ERR_HTTP_HEADERS_SENT,
-    ERR_HTTP_INVALID_HEADER_VALUE,
-    ERR_HTTP_TRAILER_INVALID,
-    ERR_INVALID_ARG_TYPE,
-    ERR_INVALID_ARG_VALUE,
-    ERR_INVALID_CHAR,
-    ERR_INVALID_HTTP_TOKEN,
-    ERR_METHOD_NOT_IMPLEMENTED,
-    ERR_STREAM_ALREADY_FINISHED,
-    ERR_STREAM_CANNOT_PIPE,
-    ERR_STREAM_DESTROYED,
-    ERR_STREAM_NULL_VALUES,
-    ERR_STREAM_WRITE_AFTER_END,
-    ERR_UNKNOWN_ENCODING,
-  },
-  hideStackFrames,
-} = require('internal/errors');
-const {
-  validateObject,
-  validateOneOf,
-  validateString,
-} = require('internal/validators');
-const {
-  assignFunctionName,
-  deprecateInstantiation,
-} = require('internal/util');
-const { isUint8Array } = require('internal/util/types');
-
-let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
-  debug = fn;
-});
-
-const kCorked = Symbol('corked');
-
-// Container for the headers set through `setHeader()`, keyed by the
-// lower-cased header name. It has no enumerable or inherited properties, like
-// a `{ __proto__: null }` literal, but instances are fast-mode objects: V8
-// creates null-prototype literals in dictionary mode, which costs a hash table
-// allocation per response and slower property access for every header.
-class OutgoingHeaders {}
-ObjectSetPrototypeOf(OutgoingHeaders.prototype, null);
-delete OutgoingHeaders.prototype.constructor;
-const kSocket = Symbol('kSocket');
-const kChunkedBuffer = Symbol('kChunkedBuffer');
-const kChunkedLength = Symbol('kChunkedLength');
-const kUniqueHeaders = Symbol('kUniqueHeaders');
-const kBytesWritten = Symbol('kBytesWritten');
-const kErrored = Symbol('errored');
-const kLenientCache = Symbol('kLenientCache');
-
-let keepAliveTimeoutCache = -1;
-let keepAliveMaxCache = -1;
-let keepAliveHeaderCache = '';
-const kWritableFinished = Symbol('kWritableFinished');
-const kEndCallbacks = Symbol('kEndCallbacks');
-const kFlushError = Symbol('kFlushError');
-const kHighWaterMark = Symbol('kHighWaterMark');
-const kRejectNonStandardBodyWrites = Symbol('kRejectNonStandardBodyWrites');
-
-const nop = () => {};
-
-const RE_CONN_CLOSE = /(?:^|\W)close(?:$|\W)/i;
-
-// isCookieField performs a case-insensitive comparison of a provided string
-// against the word "cookie." As of V8 6.6 this is faster than handrolling or
-// using a case-insensitive RegExp.
-function isCookieField(s) {
-  return s.length === 6 && s.toLowerCase() === 'cookie';
-}
-
-function isContentDispositionField(s) {
-  return s.length === 19 && s.toLowerCase() === 'content-disposition';
-}
-
-function OutgoingMessage(options) {
-  if (!(this instanceof OutgoingMessage)) {
-    return deprecateInstantiation(OutgoingMessage, 'DEP0195', options);
-  }
-
-  // Preset the listener table with the events that are commonly listened
-  // for, mirroring streams. The presence of the properties keeps `_events`
-  // in fast mode: the first `on()` call would otherwise have to allocate a
-  // dictionary for the `{ __proto__: null }` object created by EventEmitter.
-  this._events ??= {
-    close: undefined,
-    error: undefined,
-    finish: undefined,
-    prefinish: undefined,
-    drain: undefined,
-  };
-
-  Stream.call(this);
-
-  // Queue that holds all currently pending data, until the response will be
-  // assigned to the socket (until it will its turn in the HTTP pipeline).
-  this.outputData = [];
-
-  // `outputSize` is an approximate measure of how much data is queued on this
-  // response. `_onPendingData` will be invoked to update similar global
-  // per-connection counter. That counter will be used to pause/unpause the
-  // TCP socket and HTTP Parser and thus handle the backpressure.
-  this.outputSize = 0;
-
-  this.writable = true;
-  this.destroyed = false;
-
-  this._last = false;
-  this.chunkedEncoding = false;
-  this.shouldKeepAlive = true;
-  this.maxRequestsOnConnectionReached = false;
-  this._defaultKeepAlive = true;
-  this.useChunkedEncodingByDefault = true;
-  this.sendDate = false;
-  this._removedConnection = false;
-  this._removedContLen = false;
-  this._removedTE = false;
-
-  this.strictContentLength = false;
-  this[kBytesWritten] = 0;
-  this._contentLength = null;
-  this._hasBody = true;
-  this._trailer = '';
-  this[kNeedDrain] = false;
-
-  this.finished = false;
-  this._headerSent = false;
-  this[kCorked] = 0;
-  this[kChunkedBuffer] = [];
-  this[kChunkedLength] = 0;
-  this._closed = false;
-
-  this[kSocket] = null;
-  this._header = null;
-  this[kOutHeaders] = null;
-
-  this._keepAliveTimeout = 0;
-
-  this._onPendingData = nop;
-
-  this[kErrored] = null;
-  this[kWritableFinished] = false;
-  this[kEndCallbacks] = null;
-  this[kFlushError] = null;
-  this[kHighWaterMark] = options?.highWaterMark ?? getDefaultHighWaterMark();
-  this[kRejectNonStandardBodyWrites] = options?.rejectNonStandardBodyWrites ?? false;
-  this[kLenientCache] = null;
-}
-ObjectSetPrototypeOf(OutgoingMessage.prototype, Stream.prototype);
-ObjectSetPrototypeOf(OutgoingMessage, Stream);
-
-// Check if lenient header validation should be used.
-// For ClientRequest: checks this.httpValidation or this.insecureHTTPParser
-// For ServerResponse: checks the server's httpValidation or insecureHTTPParser
-// Falls back to global --insecure-http-parser flag.
-OutgoingMessage.prototype._isLenientHeaderValidation = function() {
-  // The underlying options cannot change during the lifetime of a message:
-  // compute the lookup chain only once per message.
-  this[kLenientCache] ??= isLenientHeaderValidation(this);
-  return this[kLenientCache];
-};
-
-function isLenientHeaderValidation(msg) {
-  // New httpValidation option takes priority (ClientRequest case)
-  if (msg.httpValidation !== undefined) {
-    return msg.httpValidation !== 'strict';
-  }
-  // ServerResponse: check server's httpValidation option
-  const serverHttpValidation = msg.req?.socket?.server?.httpValidation;
-  if (serverHttpValidation !== undefined) {
-    return serverHttpValidation !== 'strict';
-  }
-  // Legacy insecureHTTPParser - ClientRequest has it directly
-  if (typeof msg.insecureHTTPParser === 'boolean') {
-    return msg.insecureHTTPParser;
-  }
-  // ServerResponse can access via req.socket.server
-  const serverOption = msg.req?.socket?.server?.insecureHTTPParser;
-  if (typeof serverOption === 'boolean') {
-    return serverOption;
-  }
-  // Fall back to global option
-  return isLenient();
-}
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'errored', {
-  __proto__: null,
-  get() {
-    return this[kErrored];
-  },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'closed', {
-  __proto__: null,
-  get() {
-    return this._closed;
-  },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'writableFinished', {
-  __proto__: null,
-  get() {
-    return this[kWritableFinished];
-  },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'writableObjectMode', {
-  __proto__: null,
-  get() {
-    return false;
-  },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'writableLength', {
-  __proto__: null,
-  get() {
-    let bufferedLength = this[kChunkedLength];
-    if (bufferedLength !== 0) {
-      bufferedLength += bufferedLength.toString(16).length + 4 +
-        (!this._headerSent && this._header !== null ? this._header.length : 0);
-    }
-    return this.outputSize + bufferedLength + (this[kSocket] ? this[kSocket].writableLength : 0);
-  },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'writableHighWaterMark', {
-  __proto__: null,
-  get() {
-    return this[kSocket] ? this[kSocket].writableHighWaterMark : this[kHighWaterMark];
-  },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'writableCorked', {
-  __proto__: null,
-  get() {
-    return this[kCorked];
-  },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'connection', {
-  __proto__: null,
-  get: function() {
-    return this[kSocket];
-  },
-  set: function(val) {
-    this.socket = val;
-  },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'socket', {
-  __proto__: null,
-  get: function() {
-    return this[kSocket];
-  },
-  set: function(val) {
-    for (let n = 0; n < this[kCorked]; n++) {
-      val?.cork();
-      this[kSocket]?.uncork();
-    }
-    this[kSocket] = val;
-  },
-});
-
-OutgoingMessage.prototype._renderHeaders = function _renderHeaders() {
-  if (this._header) {
-    throw new ERR_HTTP_HEADERS_SENT('render');
-  }
-
-  const headersMap = this[kOutHeaders];
-  const headers = {};
-
-  if (headersMap !== null) {
-    const keys = ObjectKeys(headersMap);
-    // Retain for(;;) loop for performance reasons
-    // Refs: https://github.com/nodejs/node/pull/30958
-    for (let i = 0, l = keys.length; i < l; i++) {
-      const key = keys[i];
-      headers[headersMap[key][0]] = headersMap[key][1];
-    }
-  }
-  return headers;
-};
-
-OutgoingMessage.prototype.cork = function cork() {
-  this[kCorked]++;
-  if (this[kSocket]) {
-    this[kSocket].cork();
-  }
-};
-
-OutgoingMessage.prototype.uncork = function uncork() {
-  this[kCorked]--;
-  const socket = this[kSocket];
-  try {
-    if (this[kCorked] || this[kChunkedBuffer].length === 0) {
-      return;
-    }
-
-    const len = this[kChunkedLength];
-    const buf = this[kChunkedBuffer];
-    if (this.destroyed || socket?.destroyed) {
-      const error = this[kErrored] || socket?._writableState?.errored || new ERR_STREAM_DESTROYED('write');
-      for (let n = 2; n < buf.length; n += 3) {
-        if (buf[n] !== nop) {
-          process.nextTick(buf[n], error);
-        }
-      }
-      buf.length = 0;
-      this[kChunkedLength] = 0;
-      return;
-    }
-    assert(this.chunkedEncoding);
-
-    const callbacks = [];
-    this._send(len.toString(16), 'latin1', null);
-    this._send(crlf_buf, null, null);
-    for (let n = 0; n < buf.length; n += 3) {
-      this._send(buf[n + 0], buf[n + 1], null);
-      callbacks.push(buf[n + 2]);
-    }
-    this._send(crlf_buf, null, (err) => {
-      for (const callback of callbacks) {
-        callback(err);
-      }
-    });
-
-    this[kChunkedBuffer].length = 0;
-    this[kChunkedLength] = 0;
-  } finally {
-    socket?.uncork();
-  }
-
-  // If we had a pending drain and flushed all data, emit the drain event.
-  if (this[kNeedDrain] && this.writableLength === 0) {
-    this[kNeedDrain] = false;
-    this.emit('drain');
-  }
-};
-
-OutgoingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
-
-  if (callback) {
-    this.on('timeout', callback);
-  }
-
-  if (!this[kSocket]) {
-    this.once('socket', function socketSetTimeoutOnConnect(socket) {
-      socket.setTimeout(msecs);
-    });
-  } else {
-    this[kSocket].setTimeout(msecs);
-  }
-  return this;
-};
-
-
-// It's possible that the socket will be destroyed, and removed from
-// any messages, before ever calling this.  In that case, just skip
-// it, since something else is destroying this connection anyway.
-OutgoingMessage.prototype.destroy = function destroy(error) {
-  if (this.destroyed) {
-    return this;
-  }
-  this.destroyed = true;
-
-  this[kErrored] = error;
-  if (this[kChunkedBuffer].length !== 0) {
-    this[kCorked] = 1;
-    this.uncork();
-  }
-
-  if (this[kSocket]) {
-    this[kSocket].destroy(error);
-  } else {
-    process.nextTick(emitDestroyNT, this);
-  }
-
-  return this;
-};
-
-function emitDestroyNT(self) {
-  if (!self._closed) {
-    self._closed = true;
-    self.emit('close');
-  }
-}
-
-
-// This abstract either writing directly to the socket or buffering it.
-OutgoingMessage.prototype._send = function _send(data, encoding, callback, byteLength) {
-  // This is a shameful hack to get the headers and first body chunk onto
-  // the same packet. Future versions of Node are going to take care of
-  // this at a lower level and in a more general way.
-  if (!this._headerSent && this._header !== null) {
-    // `this._header` can be null if OutgoingMessage is used without a proper Socket
-    // See: /test/parallel/test-http-outgoing-message-inheritance.js
-    if (typeof data === 'string' &&
-        (encoding === 'utf8' || encoding === 'latin1' || !encoding)) {
-      data = this._header + data;
-    } else {
-      const header = this._header;
-      this.outputData.unshift({
-        data: header,
-        encoding: 'latin1',
-        callback: null,
-      });
-      this.outputSize += header.length;
-      this._onPendingData(header.length);
-    }
-    this._headerSent = true;
-  }
-  return this._writeRaw(data, encoding, callback, byteLength);
-};
-
-OutgoingMessage.prototype._writeRaw = _writeRaw;
-function _writeRaw(data, encoding, callback, size) {
-  const conn = this[kSocket];
-  if (conn?.destroyed) {
-    // The socket was destroyed. If we're still trying to write to it,
-    // then we haven't gotten the 'close' event yet.
-    return false;
-  }
-
-  if (typeof encoding === 'function') {
-    callback = encoding;
-    encoding = null;
-  }
-
-  if (conn && conn._httpMessage === this && conn.writable) {
-    // There might be pending data in the this.output buffer.
-    if (this.outputData.length) {
-      this._flushOutput(conn);
-    }
-    // Directly write to socket.
-    return conn.write(data, encoding, callback);
-  }
-  // Buffer, as long as we're not destroyed.
-  this.outputData.push({ data, encoding, callback });
-  this.outputSize += data.length;
-  this._onPendingData(data.length);
-  return this.outputSize < this[kHighWaterMark];
-}
-
-
-OutgoingMessage.prototype._storeHeader = _storeHeader;
-function _storeHeader(firstLine, headers) {
-  // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
-  // in the case of response it is: 'HTTP/1.1 200 OK\r\n'
-  const state = {
-    connection: false,
-    contLen: false,
-    te: false,
-    date: false,
-    expect: false,
-    trailer: false,
-    header: firstLine,
-  };
-  const lenient = this._isLenientHeaderValidation();
-
-  if (headers) {
-    if (headers === this[kOutHeaders]) {
-      for (const key in headers) {
-        const entry = headers[key];
-        processHeader(this, state, entry[0], entry[1], false, lenient);
-      }
-    } else if (ArrayIsArray(headers)) {
-      if (headers.length && ArrayIsArray(headers[0])) {
-        for (let i = 0; i < headers.length; i++) {
-          const entry = headers[i];
-          processHeader(this, state, entry[0], entry[1], true, lenient);
-        }
-      } else {
-        if (headers.length % 2 !== 0) {
-          throw new ERR_INVALID_ARG_VALUE('headers', headers);
-        }
-
-        for (let n = 0; n < headers.length; n += 2) {
-          processHeader(this, state, headers[n + 0], headers[n + 1], true, lenient);
-        }
-      }
-    } else {
-      for (const key in headers) {
-        if (ObjectHasOwn(headers, key)) {
-          processHeader(this, state, key, headers[key], true, lenient);
-        }
-      }
-    }
-  }
-
-  let { header } = state;
-
-  // Date header
-  if (this.sendDate && !state.date) {
-    header += utcDateHeader();
-  }
-
-  // Force the connection to close when the response is a 204 No Content or
-  // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked"
-  // header.
-  //
-  // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but
-  // node.js used to send out a zero chunk anyway to accommodate clients
-  // that don't have special handling for those responses.
-  //
-  // It was pointed out that this might confuse reverse proxies to the point
-  // of creating security liabilities, so suppress the zero chunk and force
-  // the connection to close.
-  if (this.chunkedEncoding && (this.statusCode === 204 ||
-                               this.statusCode === 304)) {
-    debug(this.statusCode + ' response should not use chunked encoding,' +
-          ' closing connection.');
-    this.chunkedEncoding = false;
-    this.shouldKeepAlive = false;
-  }
-
-  // keep-alive logic
-  if (this._removedConnection) {
-    // shouldKeepAlive is generally true for HTTP/1.1. In that common case,
-    // even if the connection header isn't sent, we still persist by default.
-    this._last = !this.shouldKeepAlive;
-  } else if (!state.connection) {
-    const shouldSendKeepAlive = this.shouldKeepAlive &&
-        (state.contLen || this.useChunkedEncodingByDefault || this.agent);
-    if (shouldSendKeepAlive && this.maxRequestsOnConnectionReached) {
-      header += 'Connection: close\r\n';
-    } else if (shouldSendKeepAlive) {
-      if (this._keepAliveTimeout && this._defaultKeepAlive) {
-        // The keep-alive header lines are identical for every response of a
-        // given server: cache the last rendered value.
-        const timeout = this._keepAliveTimeout;
-        const max = ~~this._maxRequestsPerSocket;
-        if (timeout !== keepAliveTimeoutCache || max !== keepAliveMaxCache) {
-          keepAliveTimeoutCache = timeout;
-          keepAliveMaxCache = max;
-          keepAliveHeaderCache = 'Connection: keep-alive\r\n' +
-            `Keep-Alive: timeout=${MathFloor(timeout / 1000)}` +
-            (max > 0 ? `, max=${max}` : '') + '\r\n';
-        }
-        header += keepAliveHeaderCache;
-      } else {
-        header += 'Connection: keep-alive\r\n';
-      }
-    } else {
-      this._last = true;
-      header += 'Connection: close\r\n';
-    }
-  }
-
-  if (!state.contLen && !state.te) {
-    if (!this._hasBody) {
-      // Make sure we don't end the 0\r\n\r\n at the end of the message.
-      this.chunkedEncoding = false;
-    } else if (!this.useChunkedEncodingByDefault) {
-      this._last = true;
-    } else if (!state.trailer &&
-               !this._removedContLen &&
-               typeof this._contentLength === 'number') {
-      header += 'Content-Length: ' + this._contentLength + '\r\n';
-    } else if (!this._removedTE) {
-      header += 'Transfer-Encoding: chunked\r\n';
-      this.chunkedEncoding = true;
-    } else {
-      // We should only be able to get here if both Content-Length and
-      // Transfer-Encoding are removed by the user.
-      // See: test/parallel/test-http-remove-header-stays-removed.js
-      debug('Both Content-Length and Transfer-Encoding are removed');
-
-      // We can't keep alive in this case, because with no header info the body
-      // is defined as all data until the connection is closed.
-      this._last = true;
-    }
-  }
-
-  // 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 (this.chunkedEncoding !== true && state.trailer) {
-    throw new ERR_HTTP_TRAILER_INVALID();
-  }
-
-  this._header = header + '\r\n';
-  this._headerSent = false;
-
-  // Wait until the first body chunk, or close(), is sent to flush,
-  // UNLESS we're sending Expect: 100-continue.
-  if (state.expect) this._send('');
-}
-
-function processHeader(self, state, key, value, validate, lenient) {
-  if (validate)
-    validateHeaderName(key);
-
-  // If key is content-disposition and there is content-length
-  // encode the value in latin1
-  // https://www.rfc-editor.org/rfc/rfc6266#section-4.3
-  // Refs: https://github.com/nodejs/node/pull/46528
-  if (isContentDispositionField(key) && self._contentLength) {
-    // The value could be an array here
-    if (ArrayIsArray(value)) {
-      for (let i = 0; i < value.length; i++) {
-        value[i] = Buffer.from(value[i], 'latin1');
-      }
-    } else {
-      value = Buffer.from(value, 'latin1');
-    }
-  }
-
-  if (ArrayIsArray(value)) {
-    if (
-      (value.length < 2 || !isCookieField(key)) &&
-      (!self[kUniqueHeaders] || !self[kUniqueHeaders].has(key.toLowerCase()))
-    ) {
-      // Retain for(;;) loop for performance reasons
-      // Refs: https://github.com/nodejs/node/pull/30958
-      for (let i = 0; i < value.length; i++)
-        storeHeader(self, state, key, value[i], validate, lenient);
-      return;
-    }
-    value = value.join('; ');
-  }
-  storeHeader(self, state, key, value, validate, lenient);
-}
-
-function storeHeader(self, state, key, value, validate, lenient) {
-  if (validate)
-    validateHeaderValue(key, value, lenient);
-  state.header += key + ': ' + value + '\r\n';
-  matchHeader(self, state, key, value);
-}
-
-function lowerOutgoingHeaderName(field) {
-  switch (field) {
-    case 'Connection':
-    case 'connection':
-      return 'connection';
-    case 'Content-Length':
-    case 'content-length':
-      return 'content-length';
-    case 'Transfer-Encoding':
-    case 'transfer-encoding':
-      return 'transfer-encoding';
-    case 'Date':
-    case 'date':
-      return 'date';
-    case 'Expect':
-    case 'expect':
-      return 'expect';
-    case 'Trailer':
-    case 'trailer':
-      return 'trailer';
-    case 'Keep-Alive':
-    case 'keep-alive':
-      return 'keep-alive';
-    default:
-      return field.toLowerCase();
-  }
-}
-
-function matchHeader(self, state, field, value) {
-  if (field.length < 4 || field.length > 17)
-    return;
-  field = lowerOutgoingHeaderName(field);
-  switch (field) {
-    case 'connection':
-      state.connection = true;
-      self._removedConnection = false;
-      if (RE_CONN_CLOSE.test(value))
-        self._last = true;
-      else
-        self.shouldKeepAlive = true;
-      break;
-    case 'transfer-encoding':
-      state.te = true;
-      self._removedTE = false;
-      if (RE_TE_CHUNKED.test(value))
-        self.chunkedEncoding = true;
-      break;
-    case 'content-length':
-      state.contLen = true;
-      self._contentLength = +value;
-      self._removedContLen = false;
-      break;
-    case 'date':
-    case 'expect':
-    case 'trailer':
-      state[field] = true;
-      break;
-    case 'keep-alive':
-      self._defaultKeepAlive = false;
-      break;
-  }
-}
-
-const validateHeaderName = assignFunctionName('validateHeaderName', hideStackFrames((name, label) => {
-  if (!isValidHeaderName(name)) {
-    throw new ERR_INVALID_HTTP_TOKEN.HideStackFramesError(label || 'Header name', name);
-  }
-}));
-
-/**
- * Non-throwing counterpart of `validateHeaderName()`.
- * @param {any} name
- * @returns {boolean}
- */
-function isValidHeaderName(name) {
-  return typeof name === 'string' && checkIsHttpToken(name);
-}
-
-const kHttpValidationValues = ['strict', 'relaxed'];
-
-/**
- * Non-throwing counterpart of `validateHeaderValue()`.
- * @param {any} value
- * @param {{ httpValidation?: 'strict' | 'relaxed' }} [options]
- * @returns {boolean}
- */
-function isValidHeaderValue(value, options) {
-  let lenient = false;
-  if (options !== undefined) {
-    validateObject(options, 'options');
-    const { httpValidation } = options;
-    if (httpValidation === 'relaxed') {
-      lenient = true;
-    } else if (httpValidation !== undefined && httpValidation !== 'strict') {
-      validateOneOf(httpValidation, 'options.httpValidation', kHttpValidationValues);
-    }
-  }
-  if (value === undefined || typeof value === 'symbol') {
-    return false;
-  }
-  return !checkInvalidHeaderChar(value, lenient);
-}
-
-const validateHeaderValue = assignFunctionName('validateHeaderValue', hideStackFrames((name, value, lenient) => {
-  if (value === undefined) {
-    throw new ERR_HTTP_INVALID_HEADER_VALUE.HideStackFramesError(value, name);
-  }
-  if (checkInvalidHeaderChar(value, lenient)) {
-    debug('Header "%s" contains invalid characters', name);
-    throw new ERR_INVALID_CHAR.HideStackFramesError('header content', name);
-  }
-}));
-
-function parseUniqueHeadersOption(headers) {
-  if (!ArrayIsArray(headers)) {
-    return null;
-  }
-
-  const unique = new SafeSet();
-  const l = headers.length;
-  for (let i = 0; i < l; i++) {
-    unique.add(headers[i].toLowerCase());
-  }
-
-  return unique;
-}
-
-OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
-  if (this._header) {
-    throw new ERR_HTTP_HEADERS_SENT('set');
-  }
-  validateHeaderName(name);
-  if (value === undefined) {
-    throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
-  }
-  if (checkInvalidHeaderChar(value, this._isLenientHeaderValidation())) {
-    debug('Header "%s" contains invalid characters', name);
-    throw new ERR_INVALID_CHAR('header content', name);
-  }
-
-  let headers = this[kOutHeaders];
-  if (headers === null)
-    this[kOutHeaders] = headers = new OutgoingHeaders();
-
-  headers[name.toLowerCase()] = [name, value];
-  return this;
-};
-
-OutgoingMessage.prototype.setHeaders = function setHeaders(headers) {
-  if (this._header) {
-    throw new ERR_HTTP_HEADERS_SENT('set');
-  }
-
-
-  if (
-    !headers ||
-    ArrayIsArray(headers) ||
-    typeof headers.keys !== 'function' ||
-    typeof headers.get !== 'function'
-  ) {
-    throw new ERR_INVALID_ARG_TYPE('headers', ['Headers', 'Map'], headers);
-  }
-
-  // Headers object joins multiple cookies with a comma when using
-  // the getter to retrieve the value,
-  // unless iterating over the headers directly.
-  // We also cannot safely split by comma.
-  // To avoid setHeader overwriting the previous value we push
-  // set-cookie values in array and set them all at once.
-  let cookies = null;
-
-  for (const { 0: key, 1: value } of headers) {
-    if (key === 'set-cookie') {
-      if (ArrayIsArray(value)) {
-        cookies ??= [];
-        cookies.push(...value);
-      } else {
-        cookies ??= [];
-        cookies.push(value);
-      }
-      continue;
-    }
-    this.setHeader(key, value);
-  }
-  if (cookies != null) {
-    this.setHeader('set-cookie', cookies);
-  }
-
-  return this;
-};
-
-OutgoingMessage.prototype.appendHeader = function appendHeader(name, value) {
-  if (this._header) {
-    throw new ERR_HTTP_HEADERS_SENT('append');
-  }
-  validateHeaderName(name);
-  if (value === undefined) {
-    throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
-  }
-  if (checkInvalidHeaderChar(value, this._isLenientHeaderValidation())) {
-    debug('Header "%s" contains invalid characters', name);
-    throw new ERR_INVALID_CHAR('header content', name);
-  }
-
-  const field = name.toLowerCase();
-  const headers = this[kOutHeaders];
-  if (headers === null || !headers[field]) {
-    return this.setHeader(name, value);
-  }
-
-  // Prepare the field for appending, if required
-  if (!ArrayIsArray(headers[field][1])) {
-    headers[field][1] = [headers[field][1]];
-  }
-
-  const existingValues = headers[field][1];
-  if (ArrayIsArray(value)) {
-    for (let i = 0, length = value.length; i < length; i++) {
-      existingValues.push(value[i]);
-    }
-  } else {
-    existingValues.push(value);
-  }
-
-  return this;
-};
-
-
-OutgoingMessage.prototype.getHeader = function getHeader(name) {
-  validateString(name, 'name');
-
-  const headers = this[kOutHeaders];
-  if (headers === null)
-    return;
-
-  const entry = headers[name.toLowerCase()];
-  return entry?.[1];
-};
-
-
-// Returns an array of the names of the current outgoing headers.
-OutgoingMessage.prototype.getHeaderNames = function getHeaderNames() {
-  return this[kOutHeaders] !== null ? ObjectKeys(this[kOutHeaders]) : [];
-};
-
-
-// Returns an array of the names of the current outgoing raw headers.
-OutgoingMessage.prototype.getRawHeaderNames = function getRawHeaderNames() {
-  const headersMap = this[kOutHeaders];
-  if (headersMap === null) return [];
-
-  const headers = ObjectValues(headersMap);
-  // Retain for(;;) loop for performance reasons
-  // Refs: https://github.com/nodejs/node/pull/30958
-  for (let i = 0; i < headers.length; i++) {
-    headers[i] = headers[i][0];
-  }
-
-  return headers;
-};
-
-
-// Returns a shallow copy of the current outgoing headers.
-OutgoingMessage.prototype.getHeaders = function getHeaders() {
-  const headers = this[kOutHeaders];
-  const ret = { __proto__: null };
-  if (headers) {
-    const keys = ObjectKeys(headers);
-    // Retain for(;;) loop for performance reasons
-    // Refs: https://github.com/nodejs/node/pull/30958
-    for (let i = 0; i < keys.length; ++i) {
-      const key = keys[i];
-      const val = headers[key][1];
-      ret[key] = val;
-    }
-  }
-  return ret;
-};
-
-
-OutgoingMessage.prototype.hasHeader = function hasHeader(name) {
-  validateString(name, 'name');
-  return this[kOutHeaders] !== null &&
-    !!this[kOutHeaders][name.toLowerCase()];
-};
-
-
-OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
-  validateString(name, 'name');
-
-  if (this._header) {
-    throw new ERR_HTTP_HEADERS_SENT('remove');
-  }
-
-  const key = name.toLowerCase();
-
-  switch (key) {
-    case 'connection':
-      this._removedConnection = true;
-      break;
-    case 'content-length':
-      this._removedContLen = true;
-      break;
-    case 'transfer-encoding':
-      this._removedTE = true;
-      break;
-    case 'date':
-      this.sendDate = false;
-      break;
-  }
-
-  if (this[kOutHeaders] !== null) {
-    delete this[kOutHeaders][key];
-  }
-};
-
-
-OutgoingMessage.prototype._implicitHeader = function _implicitHeader() {
-  throw new ERR_METHOD_NOT_IMPLEMENTED('_implicitHeader()');
-};
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'headersSent', {
-  __proto__: null,
-  configurable: true,
-  enumerable: true,
-  get: function() { return !!this._header; },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'writableEnded', {
-  __proto__: null,
-  get: function() { return this.finished; },
-});
-
-ObjectDefineProperty(OutgoingMessage.prototype, 'writableNeedDrain', {
-  __proto__: null,
-  get: function() {
-    return !this.destroyed && !this.finished && this[kNeedDrain];
-  },
-});
-
-const crlf_buf = Buffer.from('\r\n');
-OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
-  if (typeof encoding === 'function') {
-    callback = encoding;
-    encoding = null;
-  }
-
-  const ret = write_(this, chunk, encoding, callback, false);
-  if (!ret)
-    this[kNeedDrain] = true;
-  return ret;
-};
-
-function onError(msg, err, callback) {
-  if (msg.destroyed) {
-    return;
-  }
-
-  const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined;
-  defaultTriggerAsyncIdScope(triggerAsyncId,
-                             process.nextTick,
-                             emitErrorNt,
-                             msg,
-                             err,
-                             callback);
-}
-
-function emitErrorNt(msg, err, callback) {
-  callback(err);
-  if (typeof msg.emit === 'function' && !msg.destroyed) {
-    msg.emit('error', err);
-  }
-}
-
-function strictContentLength(msg) {
-  return (
-    msg.strictContentLength &&
-    msg._contentLength != null &&
-    msg._hasBody &&
-    !msg._removedContLen &&
-    !msg.chunkedEncoding &&
-    !msg.hasHeader('transfer-encoding')
-  );
-}
-
-function write_(msg, chunk, encoding, callback, fromEnd) {
-  if (typeof callback !== 'function')
-    callback = nop;
-
-  if (chunk === null) {
-    throw new ERR_STREAM_NULL_VALUES();
-  } else if (typeof chunk !== 'string' && !isUint8Array(chunk)) {
-    throw new ERR_INVALID_ARG_TYPE(
-      'chunk', ['string', 'Buffer', 'Uint8Array'], chunk);
-  }
-
-  let err;
-  if (msg.finished) {
-    err = new ERR_STREAM_WRITE_AFTER_END();
-  } else if (msg.destroyed) {
-    err = new ERR_STREAM_DESTROYED('write');
-  }
-
-  if (err) {
-    if (!msg.destroyed) {
-      onError(msg, err, callback);
-    } else {
-      process.nextTick(callback, err);
-    }
-    return false;
-  }
-
-  let len;
-
-  if (msg.strictContentLength) {
-    len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
-
-    if (
-      strictContentLength(msg) &&
-      (fromEnd ? msg[kBytesWritten] + len !== msg._contentLength : msg[kBytesWritten] + len > msg._contentLength)
-    ) {
-      throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(len + msg[kBytesWritten], msg._contentLength);
-    }
-
-    msg[kBytesWritten] += len;
-  }
-
-  if (!msg._header) {
-    if (fromEnd) {
-      len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
-      msg._contentLength = len;
-    }
-    msg._implicitHeader();
-  }
-
-  if (!msg._hasBody) {
-    if (msg[kRejectNonStandardBodyWrites]) {
-      throw new ERR_HTTP_BODY_NOT_ALLOWED();
-    } else {
-      debug('This type of response MUST NOT have a body. ' +
-        'Ignoring write() calls.');
-      process.nextTick(callback);
-      return true;
-    }
-  }
-
-  if (msg.chunkedEncoding && typeof chunk === 'string' && encoding && !Buffer.isEncoding(encoding)) {
-    throw new ERR_UNKNOWN_ENCODING(encoding);
-  }
-
-  if (!fromEnd && msg.socket && !msg.socket.writableCorked) {
-    msg.cork();
-    process.nextTick(connectionCorkNT, msg);
-  }
-
-  let ret;
-  if (msg.chunkedEncoding && chunk.length !== 0) {
-    len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
-    if (msg[kCorked]) {
-      if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
-        chunk = Stream._uint8ArrayToBuffer(chunk);
-      }
-      msg[kChunkedBuffer].push(chunk, encoding, callback);
-      msg[kChunkedLength] += len;
-      ret = msg.writableLength < msg.writableHighWaterMark;
-    } else {
-      msg._send(len.toString(16), 'latin1', null);
-      msg._send(crlf_buf, null, null);
-      msg._send(chunk, encoding, null, len);
-      ret = msg._send(crlf_buf, null, callback);
-    }
-  } else if (msg.chunkedEncoding && msg[kCorked] && msg[kChunkedBuffer].length !== 0) {
-    msg[kChunkedBuffer].push(chunk, encoding, callback);
-    ret = msg.writableLength < msg.writableHighWaterMark;
-  } else {
-    ret = msg._send(chunk, encoding, callback, len);
-  }
-
-  debug('write ret = ' + ret);
-  return ret;
-}
-
-
-// If this last write can be delivered immediately as the final chunk, this
-// prepares to do so, and then returns true. If not, it returns false and
-// a separate _send call and tick will be required to finish up.
-function maybePrepareFinalChunk(msg, chunk, encoding) {
-  if (typeof chunk !== 'string' && !isUint8Array(chunk))
-    return false;
-
-  if (msg.destroyed || msg.strictContentLength)
-    return false;
-
-  if (!msg._header) {
-    msg._contentLength = typeof chunk === 'string' ?
-      Buffer.byteLength(chunk, encoding) : chunk.byteLength;
-    msg._implicitHeader();
-  }
-
-  return !!msg._header && msg._hasBody && !msg.chunkedEncoding;
-}
-
-function connectionCorkNT(msg) {
-  if (msg[kCorked]) {
-    msg.uncork();
-  }
-}
-
-OutgoingMessage.prototype.addTrailers = function addTrailers(headers) {
-  if (this.finished) {
-    throw new ERR_HTTP_HEADERS_SENT('set trailing');
-  }
-
-  this._trailer = '';
-  const keys = ObjectKeys(headers);
-  const isArray = ArrayIsArray(headers);
-  // Retain for(;;) loop for performance reasons
-  // Refs: https://github.com/nodejs/node/pull/30958
-  for (let i = 0, l = keys.length; i < l; i++) {
-    let field, value;
-    const key = keys[i];
-    if (isArray) {
-      field = headers[key][0];
-      value = headers[key][1];
-    } else {
-      field = key;
-      value = headers[key];
-    }
-    validateHeaderName(field, 'Trailer name');
-
-    // Check if the field must be sent several times
-    const isArrayValue = ArrayIsArray(value);
-    const lenient = this._isLenientHeaderValidation();
-    if (
-      isArrayValue && value.length > 1 &&
-      (!this[kUniqueHeaders] || !this[kUniqueHeaders].has(field.toLowerCase()))
-    ) {
-      for (let j = 0, l = value.length; j < l; j++) {
-        if (checkInvalidHeaderChar(value[j], lenient)) {
-          debug('Trailer "%s"[%d] contains invalid characters', field, j);
-          throw new ERR_INVALID_CHAR('trailer content', field);
-        }
-        this._trailer += field + ': ' + value[j] + '\r\n';
-      }
-    } else {
-      if (isArrayValue) {
-        value = value.join('; ');
-      }
-
-      if (checkInvalidHeaderChar(value, lenient)) {
-        debug('Trailer "%s" contains invalid characters', field);
-        throw new ERR_INVALID_CHAR('trailer content', field);
-      }
-      this._trailer += field + ': ' + value + '\r\n';
-    }
-  }
-};
-
-// Deliver end() callbacks, mirroring Writable: null on successful finish,
-// otherwise the error that prevented all data from being flushed.
-function flushEndCallbacks(msg, err) {
-  const callbacks = msg[kEndCallbacks];
-  if (callbacks === null)
-    return;
-  msg[kEndCallbacks] = null;
-  for (let i = 0; i < callbacks.length; i++)
-    callbacks[i](err);
-}
-
-function getEndCallbackError(msg) {
-  return msg[kErrored] ??
-    msg[kSocket]?.errored ??
-    new ERR_STREAM_DESTROYED('end');
-}
-
-function queueEndCallback(msg, callback) {
-  if (msg[kWritableFinished]) {
-    callback(new ERR_STREAM_ALREADY_FINISHED('end'));
-    return;
-  }
-  if (msg[kFlushError] !== null) {
-    process.nextTick(callback, msg[kFlushError]);
-    return;
-  }
-  msg[kEndCallbacks] ??= [];
-  msg[kEndCallbacks].push(callback);
-}
-
-function onFinish(outmsg, err) {
-  if (err ||
-      outmsg[kErrored] ||
-      outmsg[kSocket]?.errored ||
-      outmsg[kSocket]?._hadError) {
-    outmsg[kFlushError] = err ?? getEndCallbackError(outmsg);
-    flushEndCallbacks(outmsg, outmsg[kFlushError]);
-    return;
-  }
-
-  outmsg[kWritableFinished] = true;
-  flushEndCallbacks(outmsg, null);
-  outmsg.emit('finish');
-}
-
-OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
-  if (typeof chunk === 'function') {
-    callback = chunk;
-    chunk = null;
-    encoding = null;
-  } else if (typeof encoding === 'function') {
-    callback = encoding;
-    encoding = null;
-  }
-
-  let finishCallback = null;
-
-  if (chunk) {
-    if (this.finished) {
-      onError(this,
-              new ERR_STREAM_WRITE_AFTER_END(),
-              typeof callback !== 'function' ? nop : callback);
-      return this;
-    }
-
-    if (this[kSocket]) {
-      this[kSocket].cork();
-    }
-
-    if (maybePrepareFinalChunk(this, chunk, encoding)) {
-      // If just one final write is required, with nothing to follow, we
-      // attach finish to the write to avoid a separate send() & tick step
-      // later on - this is purely a performance optimization.
-      if (typeof callback === 'function') {
-        queueEndCallback(this, callback);
-        callback = undefined;
-      }
-      finishCallback = onFinish.bind(undefined, this);
-    }
-
-    write_(this, chunk, encoding, finishCallback, true);
-  } else if (this.finished) {
-    if (typeof callback === 'function') {
-      queueEndCallback(this, callback);
-    }
-    return this;
-  } else if (!this._header) {
-    if (this[kSocket]) {
-      this[kSocket].cork();
-    }
-
-    this._contentLength = 0;
-    this._implicitHeader();
-  }
-
-  if (typeof callback === 'function')
-    queueEndCallback(this, callback);
-
-  if (strictContentLength(this) && this[kBytesWritten] !== this._contentLength) {
-    throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], this._contentLength);
-  }
-
-  // Flush buffered chunks before terminating the response.
-  if (this[kChunkedBuffer].length !== 0) {
-    this[kSocket]?.cork();
-    this[kCorked] = 1;
-    this.uncork();
-  }
-
-  if (finishCallback === null) {
-    // If we didn't early finish, send the last data and schedule 'finish' now:
-    finishCallback = onFinish.bind(undefined, this);
-
-    if (this._hasBody && this.chunkedEncoding) {
-      this._send('0\r\n' + this._trailer + '\r\n', 'latin1', finishCallback);
-    } else if (!this._headerSent || this.writableLength || chunk) {
-      this._send('', 'latin1', finishCallback);
-    } else {
-      process.nextTick(finishCallback);
-    }
-  }
-
-  if (this[kSocket]) {
-    // Fully uncork connection on end().
-    this[kSocket]._writableState.corked = 1;
-    this[kSocket].uncork();
-  }
-  this[kCorked] = 1;
-  this.uncork();
-
-  this.finished = true;
-
-  // There is the first message on the outgoing queue, and we've sent
-  // everything to the socket.
-  debug('outgoing message end.');
-  if (this.outputData.length === 0 &&
-      this[kSocket] &&
-      this[kSocket]._httpMessage === this) {
-    this._finish();
-  }
-
-  return this;
-};
-
-
-// This function is called once all user data are flushed to the socket.
-// Note that it has a chance that the socket is not drained.
-OutgoingMessage.prototype._finish = function _finish() {
-  assert(this[kSocket]);
-  this.emit('prefinish');
-};
-
-
-// This logic is probably a bit confusing. Let me explain a bit:
-//
-// In both HTTP servers and clients it is possible to queue up several
-// outgoing messages. This is easiest to imagine in the case of a client.
-// Take the following situation:
-//
-//    req1 = client.request('GET', '/');
-//    req2 = client.request('POST', '/');
-//
-// When the user does
-//
-//   req2.write('hello world\n');
-//
-// it's possible that the first request has not been completely flushed to
-// the socket yet. Thus the outgoing messages need to be prepared to queue
-// up data internally before sending it on further to the socket's queue.
-//
-// This function, _flush(), is called by both the Server and Client
-// to attempt to flush any pending messages out to the socket.
-OutgoingMessage.prototype._flush = function _flush() {
-  const socket = this[kSocket];
-
-  if (socket?.writable) {
-    // There might be remaining data in this.output; write it out
-    const ret = this._flushOutput(socket);
-
-    if (this.finished) {
-      // This is a queue to the server or client to bring in the next this.
-      this._finish();
-    } else if (this[kNeedDrain] && ret !== false) {
-      // Socket accepted all data without backpressure - it won't emit
-      // drain, so we emit it since the OM buffer is now clear.
-      this[kNeedDrain] = false;
-      this.emit('drain');
-    }
-  }
-};
-
-OutgoingMessage.prototype._flushOutput = function _flushOutput(socket) {
-  const outputLength = this.outputData.length;
-  if (outputLength <= 0)
-    return undefined;
-
-  const outputData = this.outputData;
-  socket.cork();
-  let ret;
-  // Retain for(;;) loop for performance reasons
-  // Refs: https://github.com/nodejs/node/pull/30958
-  for (let i = 0; i < outputLength; i++) {
-    const { data, encoding, callback } = outputData[i];
-    // Avoid any potential ref to Buffer in new generation from old generation
-    outputData[i].data = null;
-    ret = socket.write(data, encoding, callback);
-  }
-  socket.uncork();
-
-  this.outputData = [];
-  this._onPendingData(-this.outputSize);
-  this.outputSize = 0;
-
-  return ret;
-};
-
-
-OutgoingMessage.prototype.flushHeaders = function flushHeaders() {
-  if (!this._header) {
-    this._implicitHeader();
-  }
-
-  // Force-flush the headers.
-  this._send('');
-};
-
-OutgoingMessage.prototype.pipe = function pipe() {
-  // OutgoingMessage should be write-only. Piping from it is disabled.
-  this.emit('error', new ERR_STREAM_CANNOT_PIPE());
-};
-
-OutgoingMessage.prototype[EE.captureRejectionSymbol] =
-function(err, event) {
-  this.destroy(err);
-};
+  kHighWaterMark,
+  kUniqueHeaders,
+  parseUniqueHeadersOption,
+  isValidHeaderName,
+  isValidHeaderValue,
+  validateHeaderName,
+  validateHeaderValue,
+  OutgoingMessage,
+} = require('internal/http/outgoing');

 module.exports = {
   kHighWaterMark,
@@ -1487,3 +42,6 @@ module.exports = {
   validateHeaderValue,
   OutgoingMessage,
 };
+
+process.emitWarning('The _http_outgoing module is deprecated. Use `node:http` instead.',
+                    'DeprecationWarning', 'DEP0199');
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 43af4fff0a4..830250b5302 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -22,1519 +22,17 @@
 'use strict';

 const {
-  ArrayIsArray,
-  Error,
-  MathMin,
-  NumberIsFinite,
-  ObjectKeys,
-  ObjectSetPrototypeOf,
-  ReflectApply,
-  Symbol,
-  SymbolAsyncDispose,
-  SymbolFor,
-} = primordials;
-
-const { Duplex } = require('stream');
-const net = require('net');
-const EE = require('events');
-const assert = require('internal/assert');
-const {
-  parsers,
-  freeParser,
-  continueExpression,
-  chunkExpression,
-  kIncomingMessage,
-  kSocket,
-  HTTPParser,
-  calculateLenientFlags,
-  _checkInvalidHeaderChar: checkInvalidHeaderChar,
-  prepareError,
-} = require('_http_common');
-const { ConnectionsList } = internalBinding('http_parser');
-const {
-  kUniqueHeaders,
-  parseUniqueHeadersOption,
-  OutgoingMessage,
-  validateHeaderName,
-  validateHeaderValue,
-} = require('_http_outgoing');
-const {
-  kOutHeaders,
-  kNeedDrain,
-  isTraceHTTPEnabled,
-  traceBegin,
-  traceEnd,
-  getNextTraceEventId,
-} = require('internal/http');
-const {
-  defaultTriggerAsyncIdScope,
-  getOrSetAsyncId,
-} = require('internal/async_hooks');
-const {
-  IncomingMessage,
-  kDetachAbortSignal,
-  kHeadersCount,
-  asciiEqualIgnoreCase,
-  getRawHeader,
-} = require('_http_incoming');
-const {
-  ConnResetException,
-  codes: {
-    ERR_HTTP_HEADERS_SENT,
-    ERR_HTTP_INVALID_STATUS_CODE,
-    ERR_HTTP_REQUEST_TIMEOUT,
-    ERR_HTTP_SOCKET_ASSIGNED,
-    ERR_HTTP_SOCKET_ENCODING,
-    ERR_INVALID_ARG_VALUE,
-    ERR_INVALID_CHAR,
-    ERR_OUT_OF_RANGE,
-  },
-} = require('internal/errors');
-const {
-  assignFunctionName,
-  deprecateInstantiation,
-  kEmptyObject,
-  promisify,
-} = require('internal/util');
-const {
-  validateInteger,
-  validateBoolean,
-  validateOneOf,
-  validateLinkHeaderValue,
-  validateObject,
-  validateFunction,
-} = 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 onResponseCreatedChannel = dc.channel('http.server.response.created');
-const onResponseFinishChannel = dc.channel('http.server.response.finish');
-
-const kServerResponse = Symbol('ServerResponse');
-const kServerResponseStatistics = Symbol('ServerResponseStatistics');
-const kUpgradeStream = Symbol('UpgradeStream');
-
-const kOptimizeEmptyRequests = Symbol('OptimizeEmptyRequestsOption');
-const kConnectionState = Symbol('ConnectionState');
-const kResponseOptions = Symbol('ResponseOptions');
-
-const statusLineCache = [];
-
-const {
-  hasObserver,
-  startPerf,
-  stopPerf,
-} = require('internal/perf/observe');
-
-const STATUS_CODES = {
-  100: 'Continue',                   // RFC 7231 6.2.1
-  101: 'Switching Protocols',        // RFC 7231 6.2.2
-  102: 'Processing',                 // RFC 2518 10.1 (obsoleted by RFC 4918)
-  103: 'Early Hints',                // RFC 8297 2
-  200: 'OK',                         // RFC 7231 6.3.1
-  201: 'Created',                    // RFC 7231 6.3.2
-  202: 'Accepted',                   // RFC 7231 6.3.3
-  203: 'Non-Authoritative Information', // RFC 7231 6.3.4
-  204: 'No Content',                 // RFC 7231 6.3.5
-  205: 'Reset Content',              // RFC 7231 6.3.6
-  206: 'Partial Content',            // RFC 7233 4.1
-  207: 'Multi-Status',               // RFC 4918 11.1
-  208: 'Already Reported',           // RFC 5842 7.1
-  226: 'IM Used',                    // RFC 3229 10.4.1
-  300: 'Multiple Choices',           // RFC 7231 6.4.1
-  301: 'Moved Permanently',          // RFC 7231 6.4.2
-  302: 'Found',                      // RFC 7231 6.4.3
-  303: 'See Other',                  // RFC 7231 6.4.4
-  304: 'Not Modified',               // RFC 7232 4.1
-  305: 'Use Proxy',                  // RFC 7231 6.4.5
-  307: 'Temporary Redirect',         // RFC 7231 6.4.7
-  308: 'Permanent Redirect',         // RFC 7238 3
-  400: 'Bad Request',                // RFC 7231 6.5.1
-  401: 'Unauthorized',               // RFC 7235 3.1
-  402: 'Payment Required',           // RFC 7231 6.5.2
-  403: 'Forbidden',                  // RFC 7231 6.5.3
-  404: 'Not Found',                  // RFC 7231 6.5.4
-  405: 'Method Not Allowed',         // RFC 7231 6.5.5
-  406: 'Not Acceptable',             // RFC 7231 6.5.6
-  407: 'Proxy Authentication Required', // RFC 7235 3.2
-  408: 'Request Timeout',            // RFC 7231 6.5.7
-  409: 'Conflict',                   // RFC 7231 6.5.8
-  410: 'Gone',                       // RFC 7231 6.5.9
-  411: 'Length Required',            // RFC 7231 6.5.10
-  412: 'Precondition Failed',        // RFC 7232 4.2
-  413: 'Payload Too Large',          // RFC 7231 6.5.11
-  414: 'URI Too Long',               // RFC 7231 6.5.12
-  415: 'Unsupported Media Type',     // RFC 7231 6.5.13
-  416: 'Range Not Satisfiable',      // RFC 7233 4.4
-  417: 'Expectation Failed',         // RFC 7231 6.5.14
-  418: 'I\'m a Teapot',              // RFC 7168 2.3.3
-  421: 'Misdirected Request',        // RFC 7540 9.1.2
-  422: 'Unprocessable Entity',       // RFC 4918 11.2
-  423: 'Locked',                     // RFC 4918 11.3
-  424: 'Failed Dependency',          // RFC 4918 11.4
-  425: 'Too Early',                  // RFC 8470 5.2
-  426: 'Upgrade Required',           // RFC 2817 and RFC 7231 6.5.15
-  428: 'Precondition Required',      // RFC 6585 3
-  429: 'Too Many Requests',          // RFC 6585 4
-  431: 'Request Header Fields Too Large', // RFC 6585 5
-  451: 'Unavailable For Legal Reasons', // RFC 7725 3
-  500: 'Internal Server Error',      // RFC 7231 6.6.1
-  501: 'Not Implemented',            // RFC 7231 6.6.2
-  502: 'Bad Gateway',                // RFC 7231 6.6.3
-  503: 'Service Unavailable',        // RFC 7231 6.6.4
-  504: 'Gateway Timeout',            // RFC 7231 6.6.5
-  505: 'HTTP Version Not Supported', // RFC 7231 6.6.6
-  506: 'Variant Also Negotiates',    // RFC 2295 8.1
-  507: 'Insufficient Storage',       // RFC 4918 11.5
-  508: 'Loop Detected',              // RFC 5842 7.2
-  509: 'Bandwidth Limit Exceeded',
-  510: 'Not Extended',               // RFC 2774 7
-  511: 'Network Authentication Required', // RFC 6585 6
-};
-
-const kOnExecute = HTTPParser.kOnExecute | 0;
-const kOnTimeout = HTTPParser.kOnTimeout | 0;
-
-const kConnections = Symbol('http.server.connections');
-const kConnectionsCheckingInterval = Symbol('http.server.connectionsCheckingInterval');
-
-const HTTP_SERVER_TRACE_EVENT_NAME = 'http.server.request';
-
-class HTTPServerAsyncResource {
-  constructor(type, socket) {
-    this.type = type;
-    this.socket = socket;
-  }
-}
-
-function ServerResponse(req, options) {
-  if (!(this instanceof ServerResponse)) {
-    return deprecateInstantiation(ServerResponse, 'DEP0195', 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(getRawHeader(req, 'te', true));
-    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);
-  }
-  if (onResponseCreatedChannel.hasSubscribers) {
-    onResponseCreatedChannel.publish({
-      request: req,
-      response: this,
-    });
-  }
-}
-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() {
-  // 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. :-)
-  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.writeInformation = function writeInformation(
-  statusCode, headers, cb) {
-  if (this._header) {
-    throw new ERR_HTTP_HEADERS_SENT('write');
-  }
-
-  validateInteger(statusCode, 'statusCode', 100, 199);
-  if (statusCode === 101) {
-    throw new ERR_HTTP_INVALID_STATUS_CODE(statusCode);
-  }
-
-  const statusMessage = STATUS_CODES[statusCode] || 'unknown';
-  let head = `HTTP/1.1 ${statusCode} ${statusMessage}\r\n`;
-
-  const lenient = this._isLenientHeaderValidation();
-  if (headers !== undefined && headers !== null) {
-    if (ArrayIsArray(headers)) {
-      if (headers.length && ArrayIsArray(headers[0])) {
-        for (let i = 0; i < headers.length; i++) {
-          const entry = headers[i];
-          head += processInformationHeader(entry[0], entry[1], lenient);
-        }
-      } else {
-        if (headers.length % 2 !== 0) {
-          throw new ERR_INVALID_ARG_VALUE('headers', headers);
-        }
-        for (let i = 0; i < headers.length; i += 2) {
-          head += processInformationHeader(headers[i], headers[i + 1], lenient);
-        }
-      }
-    } else {
-      validateObject(headers, 'headers');
-      const keys = ObjectKeys(headers);
-      for (let i = 0; i < keys.length; i++) {
-        const key = keys[i];
-        head += processInformationHeader(key, headers[key], lenient);
-      }
-    }
-  }
-
-  head += '\r\n';
-
-  return this._writeRaw(head, 'ascii', cb);
-};
-
-function processInformationHeader(name, value, lenient) {
-  validateHeaderName(name);
-  validateHeaderValue(name, value, lenient);
-  return `${name}: ${value}\r\n`;
-}
-
-ServerResponse.prototype.writeContinue = function writeContinue(cb) {
-  this.writeInformation(100, null, cb);
-  this._sent100 = true;
-};
-
-ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
-  this.writeInformation(102, null, cb);
-};
-
-ServerResponse.prototype.writeEarlyHints = function writeEarlyHints(hints, cb) {
-  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');
-  }
-
-  const headers = { __proto__: null, Link: link };
-  const keys = ObjectKeys(hints);
-  for (let i = 0; i < keys.length; i++) {
-    const key = keys[i];
-    if (key !== 'link') {
-      headers[key] = hints[key];
-    }
-  }
-
-  this.writeInformation(103, headers, 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') {
-    // writeHead(statusCode, reasonPhrase[, headers])
-    this.statusMessage = reason;
-  } else {
-    // writeHead(statusCode[, headers])
-    this.statusMessage ||= STATUS_CODES[statusCode] || 'unknown';
-    obj ??= reason;
-  }
-  this.statusCode = statusCode;
-
-  let headers;
-  if (this[kOutHeaders]) {
-    // Slow-case: when progressive API and header fields are passed.
-    let k;
-    if (ArrayIsArray(obj)) {
-      if (obj.length % 2 !== 0) {
-        throw new ERR_INVALID_ARG_VALUE('headers', obj);
-      }
-
-      // 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 < obj.length; n += 2) {
-        k = obj[n + 0];
-        this.removeHeader(k);
-      }
-
-      for (let n = 0; n < obj.length; n += 2) {
-        k = obj[n + 0];
-        if (k) this.appendHeader(k, obj[n + 1]);
-      }
-    } else if (obj) {
-      const keys = ObjectKeys(obj);
-      // Retain for(;;) loop for performance reasons
-      // Refs: https://github.com/nodejs/node/pull/30958
-      for (let i = 0; i < keys.length; i++) {
-        k = keys[i];
-        if (k) this.setHeader(k, obj[k]);
-      }
-    }
-    // Only progressive api is used
-    headers = this[kOutHeaders];
-  } else {
-    // Only writeHead() called
-    headers = obj;
-  }
-
-  let statusLine;
-  if (this.statusMessage === STATUS_CODES[statusCode]) {
-    // Default reason phrases contain no invalid characters and are shared
-    // across responses: cache the whole status line.
-    statusLine = statusLineCache[statusCode] ??=
-      `HTTP/1.1 ${statusCode} ${this.statusMessage}\r\n`;
-  } else {
-    if (checkInvalidHeaderChar(this.statusMessage))
-      throw new ERR_INVALID_CHAR('statusMessage');
-
-    statusLine = `HTTP/1.1 ${statusCode} ${this.statusMessage}\r\n`;
-  }
-
-  if (statusCode === 204 || statusCode === 304 ||
-      (statusCode >= 100 && statusCode <= 199)) {
-    // RFC 2616, 10.2.5:
-    // The 204 response MUST NOT include a message-body, and thus is always
-    // terminated by the first empty line after the header fields.
-    // RFC 2616, 10.3.5:
-    // The 304 response MUST NOT contain a message-body, and thus is always
-    // terminated by the first empty line after the header fields.
-    // RFC 2616, 10.1 Informational 1xx:
-    // This class of status code indicates a provisional response,
-    // consisting only of the Status-Line and optional headers, and is
-    // terminated by an empty line.
-    this._hasBody = false;
-  }
-
-  // Don't keep alive connections where the client expects 100 Continue
-  // but we sent a final status; they may put extra bytes on the wire.
-  if (this._expect_continue && !this._sent100) {
-    this.shouldKeepAlive = false;
-  }
-
-  this._storeHeader(statusLine, headers);
-
-  return this;
-}
-
-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 optimizeEmptyRequests = options.optimizeEmptyRequests;
-  if (optimizeEmptyRequests !== undefined)
-    validateBoolean(optimizeEmptyRequests, 'options.optimizeEmptyRequests');
-  this[kOptimizeEmptyRequests] = optimizeEmptyRequests || false;
-
-  const insecureHTTPParser = options.insecureHTTPParser;
-  if (insecureHTTPParser !== undefined)
-    validateBoolean(insecureHTTPParser, 'options.insecureHTTPParser');
-  this.insecureHTTPParser = insecureHTTPParser;
-
-  const httpValidation = options.httpValidation;
-  if (httpValidation !== undefined) {
-    validateOneOf(httpValidation, 'options.httpValidation',
-                  ['strict', 'relaxed', 'insecure']);
-    if (insecureHTTPParser !== undefined) {
-      throw new ERR_INVALID_ARG_VALUE(
-        'options.httpValidation',
-        httpValidation,
-        'cannot be used together with options.insecureHTTPParser',
-      );
-    }
-  }
-  this.httpValidation = httpValidation;
-
-  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 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 = 65_000; // 65 seconds;
-  }
-
-  const keepAliveTimeoutBuffer = options.keepAliveTimeoutBuffer;
-  if (keepAliveTimeoutBuffer !== undefined) {
-    validateInteger(keepAliveTimeoutBuffer, 'keepAliveTimeoutBuffer', 0);
-    this.keepAliveTimeoutBuffer = keepAliveTimeoutBuffer;
-  } else {
-    this.keepAliveTimeoutBuffer = 1000;
-  }
-
-  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;
-  }
-
-  const shouldUpgradeCallback = options.shouldUpgradeCallback;
-  if (shouldUpgradeCallback !== undefined) {
-    validateFunction(shouldUpgradeCallback, 'options.shouldUpgradeCallback');
-    this.shouldUpgradeCallback = shouldUpgradeCallback;
-  } else {
-    this.shouldUpgradeCallback = function() {
-      return this.listenerCount('upgrade') > 0;
-    };
-  }
-}
-
-function setupConnectionsTracking() {
-  // Start connection handling
-  this[kConnections] ||= new ConnectionsList();
-
-  if (this[kConnectionsCheckingInterval]) {
-    clearInterval(this[kConnectionsCheckingInterval]);
-  }
-  // This checker is started without checking whether any headersTimeout or requestTimeout is non zero
-  // otherwise it would not be started if such timeouts are modified after createServer.
-  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 deprecateInstantiation(Server, 'DEP0195', 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);
-  }
-
-  // Similar option to this. Too lazy to write my own docs.
-  // http://www.squid-cache.org/Doc/config/half_closed_clients/
-  // https://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F
-  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 close() {
-  httpServerPreClose(this);
-  ReflectApply(net.Server.prototype.close, this, arguments);
-  return this;
-};
-
-Server.prototype[SymbolAsyncDispose] = assignFunctionName(SymbolAsyncDispose, async function() {
-  await promisify(this.close).call(this);
-});
-
-Server.prototype.closeAllConnections = function closeAllConnections() {
-  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 closeIdleConnections() {
-  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] =
-assignFunctionName(EE.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:
-      ReflectApply(
-        net.Server.prototype[SymbolFor('nodejs.rejection')],
-        this, arguments);
-  }
-});
-
-function checkConnections() {
-  const headersTimeout =
-    NumberIsFinite(this.headersTimeout) && this.headersTimeout >= 0 ?
-      this.headersTimeout : 0;
-  const requestTimeout =
-    NumberIsFinite(this.requestTimeout) && this.requestTimeout >= 0 ?
-      this.requestTimeout : 0;
-
-  if (headersTimeout === 0 && requestTimeout === 0) {
-    return;
-  }
-
-  const expired = this[kConnections].expired(headersTimeout, 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');
-
-  // Ensure that the server property of the socket is correctly set.
-  // See https://github.com/nodejs/node/issues/13435
-  socket.server = server;
-
-  // If the user has added a listener to the server,
-  // request, or response, then it's their responsibility.
-  // otherwise, destroy on timeout by default
-  if (server.timeout && typeof socket.setTimeout === 'function')
-    socket.setTimeout(server.timeout);
-  socket.on('timeout', socketOnTimeout);
-
-  const parser = parsers.alloc();
-
-  const lenientFlags = calculateLenientFlags(server.httpValidation, server.insecureHTTPParser);
-
-  // Propagate headers limit from server instance to parser
-  if (typeof server.maxHeadersCount === 'number') {
-    parser.maxHeaderPairs = server.maxHeadersCount << 1;
-  }
-
-  // TODO(addaleax): This doesn't play well with the
-  // `async_hooks.currentResource()` proposal, see
-  // https://github.com/nodejs/node/pull/21313
-  parser.initialize(
-    HTTPParser.REQUEST,
-    new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket),
-    server.maxHeaderSize || 0,
-    lenientFlags,
-    server[kConnections],
-    parser.maxHeaderPairs,
-  );
-  parser.socket = socket;
-  socket.parser = parser;
-
-  const state = {
-    onData: null,
-    onEnd: null,
-    onClose: null,
-    onDrain: null,
-    outgoing: [],
-    incoming: [],
-    // `outgoingData` is an approximate amount of bytes queued through all
-    // inactive responses. If more data than the high watermark is queued - we
-    // need to pause TCP socket/HTTP parser, and wait until the data will be
-    // sent to the client.
-    outgoingData: 0,
-    requestsCount: 0,
-    keepAliveTimeoutSet: false,
-    onPendingData: null,
-    server,
-    socket,
-  };
-  state.onPendingData = updateOutgoingData.bind(undefined, socket, state);
-  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);
-
-  // We are consuming socket, so it won't get any actual data
-  socket.on('resume', onSocketResume);
-  socket.on('pause', onSocketPause);
-
-  // Overrides to unconsume on `data`, `readable` listeners
-  socket.on = generateSocketListenerWrapper('on');
-  socket.addListener = generateSocketListenerWrapper('addListener');
-  socket.prependListener = generateSocketListenerWrapper('prependListener');
-  socket.setEncoding = socketSetEncoding;
-
-  // We only consume the socket if it has never been consumed before.
-  if (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 we previously paused, then start reading again.
-  if (socket._paused && !needPause) {
-    socket._paused = false;
-    if (socket.parser)
-      socket.parser.resume();
-    socket.resume();
-  }
-
-  const msg = socket._httpMessage;
-  // Only emit 'drain' once the message has no data pending anywhere, so that
-  // msg.writableLength === 0 when the event fires. socketOnDrain is called
-  // synchronously from updateOutgoingData during _flushOutput, at which point
-  // the bytes we just handed to the socket (or the stale outputSize) mean
-  // the message is not actually drained yet - we wait for the socket's
-  // own 'drain' event instead.
-  if (msg && !msg.finished && msg[kNeedDrain] && msg.writableLength === 0) {
-    msg[kNeedDrain] = false;
-    msg.emit('drain');
-  }
-}
-
-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) {
-  debug('server socket close');
-  freeParser(socket.parser, null, socket);
-  abortIncoming(state.incoming);
-  abortOutgoing(state.outgoing);
-}
-
-function abortIncoming(incoming) {
-  while (incoming.length) {
-    const req = incoming.shift();
-    req.destroy(new ConnResetException('aborted'));
-  }
-}
-
-function abortOutgoing(outgoing) {
-  while (outgoing.length) {
-    const req = outgoing.shift();
-    req.destroy(new ConnResetException('aborted'));
-  }
-}
-
-function socketOnEnd(server, socket, parser, state) {
-  const ret = parser.finish();
-
-  if (ret instanceof Error) {
-    debug('parse error');
-    // socketOnError has additional logic and will call socket.destroy(err).
-    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 has additional logic and will call socket.destroy(err).
-  socketOnError.call(socket, new ERR_HTTP_REQUEST_TIMEOUT());
-}
-
-function onParserExecute(server, socket, parser, state, ret) {
-  // When underlying `net.Socket` instance is consumed - no
-  // `data` events are emitted, and thus `socket.setTimeout` fires the
-  // callback even if the data is constantly flowing into the socket.
-  // See, https://github.com/nodejs/node/commit/ec2822adaad76b126b5cccdeaa1addf2376c9aa6
-  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) {
-  // Ignore further errors
-  this.removeListener('error', socketOnError);
-
-  if (this.listenerCount('error', noop) === 0) {
-    this.on('error', noop);
-  }
-
-  if (!this.server.emit('clientError', e, this)) {
-    // Caution must be taken to avoid corrupting the remote peer.
-    // Reply an error segment if there is no in-flight `ServerResponse`,
-    // or no data of the in-flight one has been written yet to this socket.
-    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);
-  }
-}
-
-class UpgradeStream extends Duplex {
-  constructor(socket, req) {
-    super({
-      allowHalfOpen: socket.allowHalfOpen,
-    });
-
-    this[kSocket] = socket;
-    this[kIncomingMessage] = req;
-
-    // Proxy error, end & closure events immediately.
-    socket.on('error', (err) => this.destroy(err));
-
-    socket.on('close', () => this.destroy());
-    this.on('close', () => socket.destroy());
-
-    socket.on('end', () => {
-      this.push(null);
-
-      // Match the socket behaviour, where 'end' will fire despite no 'data'
-      // listeners if a socket with no pending data ends:
-      if (this.readableLength === 0) {
-        this.resume();
-      }
-    });
-
-    // Other events (most notably, reading) all only
-    // activate after requestBodyCompleted is called.
-  }
-
-  requestBodyCompleted(upgradeHead) {
-    this[kIncomingMessage] = null;
-
-    // When the request body is completed, we begin streaming all the
-    // post-body data for the upgraded protocol:
-    if (upgradeHead?.length > 0) {
-      if (!this.push(upgradeHead)) {
-        this[kSocket].pause();
-      }
-    }
-
-    this[kSocket].on('data', (data) => {
-      if (!this.push(data)) {
-        this[kSocket].pause();
-      }
-    });
-  }
-
-  _read(size) {
-    // Reading the upgrade stream starts the request stream flowing. It's
-    // important that this happens, even if there are no listeners, or it
-    // would be impossible to read this without explicitly reading all the
-    // request body first, which is backward incompatible & awkward.
-    this[kIncomingMessage]?.resume();
-
-    this[kSocket].resume();
-  }
-
-  _final(callback) {
-    this[kSocket].end(callback);
-  }
-
-  _write(chunk, encoding, callback) {
-    this[kSocket].write(chunk, encoding, callback);
-  }
-
-  _destroy(err, callback) {
-    this[kSocket].destroy(err);
-    callback(err);
-  }
-}
-
-function onParserExecuteCommon(server, socket, parser, state, ret, d) {
-  if (ret instanceof Error) {
-    prepareError(ret, parser, d);
-    debug('parse error', ret);
-    socketOnError.call(socket, ret);
-  } else if (parser.incoming?.upgrade) {
-    // Upgrade or CONNECT
-    const req = parser.incoming;
-    debug('SERVER upgrade or connect', req.method);
-    const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
-
-    let upgradeStream;
-    if (req.complete) {
-      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;
-
-      // If the request is complete (no body, or all body read upfront) then
-      // we just emit the socket directly as the upgrade stream.
-      upgradeStream = socket;
-    } else {
-      // If the body hasn't been fully parsed yet, we emit immediately but
-      // we add a wrapper around the socket to not expose incoming data
-      // until the request body has finished.
-
-      if (socket[kUpgradeStream]) {
-        // We've already emitted the incomplete upgrade - nothing do to
-        // until actual body parsing completion.
-        return;
-      }
-
-      d ||= Buffer.alloc(0);
-
-      upgradeStream = new UpgradeStream(socket, req);
-      socket[kUpgradeStream] = upgradeStream;
-    }
-
-    if (server.listenerCount(eventName) > 0) {
-      debug('SERVER have listener for %s', eventName);
-
-      const bodyHead = d.slice(ret, d.length);
-
-      if (req.complete && socket[kUpgradeStream]) {
-        // Previously emitted, now completed - just activate the stream
-        socket[kUpgradeStream].requestBodyCompleted(bodyHead);
-      } else {
-        socket.readableFlowing = null;
-        server.emit(eventName, req, upgradeStream, bodyHead);
-      }
-    } else {
-      // Got upgrade or CONNECT method, but have no handler.
-      socket.destroy();
-    }
-  } else if (parser.incoming && parser.incoming.method === 'PRI') {
-    debug('SERVER got PRI request');
-    socket.destroy();
-  }
-
-  if (socket._paused && socket.parser) {
-    // onIncoming paused the socket, we should pause the parser as well
-    debug('pause parser');
-    socket.parser.pause();
-  }
-}
-
-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 resOnFinish(req, res, socket, state, server) {
-  if (onResponseFinishChannel.hasSubscribers) {
-    onResponseFinishChannel.publish({
-      request: req,
-      response: res,
-      socket,
-      server,
-    });
-  }
-
-  // Usually the first incoming element should be our request.  it may
-  // be that in the case abortIncoming() was called that the incoming
-  // array will be empty.
-  assert(state.incoming.length === 0 || state.incoming[0] === req);
-
-  req[kDetachAbortSignal]();
-  state.incoming.shift();
-
-  // If the user never called req.read(), and didn't pipe() or
-  // .resume() or .on('data'), then we call req._dump() so that the
-  // bytes will be pulled off the wire.
-  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) {
-    const keepAliveTimeout = NumberIsFinite(server.keepAliveTimeout) && server.keepAliveTimeout >= 0 ?
-      server.keepAliveTimeout : 0;
-    const keepAliveTimeoutBuffer = NumberIsFinite(server.keepAliveTimeoutBuffer) && server.keepAliveTimeoutBuffer >= 0 ?
-      server.keepAliveTimeoutBuffer : 1e3;
-
-    if (keepAliveTimeout && typeof socket.setTimeout === 'function') {
-      // Extend the internal timeout by the configured buffer to reduce
-      // the likelihood of ECONNRESET errors.
-      // This allows fine-tuning beyond the advertised keepAliveTimeout.
-      socket.setTimeout(keepAliveTimeout + keepAliveTimeoutBuffer);
-      state.keepAliveTimeoutSet = true;
-    }
-  } else {
-    // Start sending the next message
-    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');
-  }
-}
-
-// Check for the presence of a request header by scanning rawHeaders instead
-// of materializing the req.headers object. Only the first req[kHeadersCount]
-// entries are considered, matching what the req.headers getter exposes.
-function hasRequestHeader(req, length, lowerName) {
-  const rawHeaders = req.rawHeaders;
-  const count = req[kHeadersCount];
-  for (let i = 0; i < count; i += 2) {
-    const key = rawHeaders[i];
-    if (key.length === length && asciiEqualIgnoreCase(key, lowerName))
-      return true;
-  }
-  return false;
-}
-
-function hasBodyHeaders(req) {
-  const rawHeaders = req.rawHeaders;
-  const count = req[kHeadersCount];
-  for (let i = 0; i < count; i += 2) {
-    const key = rawHeaders[i];
-    const length = key.length;
-    if (length === 14) {
-      if (asciiEqualIgnoreCase(key, 'content-length'))
-        return true;
-    } else if (length === 17) {
-      if (asciiEqualIgnoreCase(key, 'transfer-encoding'))
-        return true;
-    }
-  }
-  return false;
-}
-
-// The options object passed to the ServerResponse constructor is identical
-// for every request of a given server: cache it. Custom response classes get
-// a fresh object since they may retain or mutate it.
-function getResponseOptions(server, socket) {
-  const highWaterMark = socket.writableHighWaterMark;
-  const rejectNonStandardBodyWrites = server.rejectNonStandardBodyWrites;
-  if (server[kServerResponse] !== ServerResponse) {
-    return { highWaterMark, rejectNonStandardBodyWrites };
-  }
-  let options = server[kResponseOptions];
-  if (options === undefined ||
-      options.highWaterMark !== highWaterMark ||
-      options.rejectNonStandardBodyWrites !== rejectNonStandardBodyWrites) {
-    options = server[kResponseOptions] =
-      { highWaterMark, rejectNonStandardBodyWrites };
-  }
-  return options;
-}
-
-// Shared 'finish' listener: everything resOnFinish needs is reachable from
-// the response, avoiding a bound function per request.
-function onResponseFinish() {
-  const state = this[kConnectionState];
-  resOnFinish(this.req, this, state.socket, state, state.server);
-}
-
-// The following callback is issued after the headers have been read on a
-// new message. In this callback we setup the response object and pass it
-// to the user.
-function parserOnIncoming(server, socket, state, req, keepAlive) {
-  resetSocketTimeout(server, socket, state);
-
-  if (req.upgrade) {
-    req.upgrade = req.method === 'CONNECT' ||
-                  !!server.shouldUpgradeCallback(req);
-    if (req.upgrade) {
-      return 0;
-    }
-  }
-
-  state.incoming.push(req);
-
-  // If the writable end isn't consuming, then stop reading
-  // so that we don't become overwhelmed by a flood of
-  // pipelined requests that may never be resolved.
-  if (!socket._paused) {
-    const ws = socket._writableState;
-    if (ws.needDrain || state.outgoingData >= socket.writableHighWaterMark) {
-      socket._paused = true;
-      // We also need to pause the parser, but don't do that until after
-      // the call to execute, because we may still be processing the last
-      // chunk.
-      socket.pause();
-    }
-  }
-
-  const res = new server[kServerResponse](req, getResponseOptions(server, socket));
-  res._keepAliveTimeout = server.keepAliveTimeout;
-  res._maxRequestsPerSocket = server.maxRequestsPerSocket;
-  res._onPendingData = state.onPendingData;
-  res[kConnectionState] = state;
-
-  res.shouldKeepAlive = keepAlive;
-  res[kUniqueHeaders] = server[kUniqueHeaders];
-
-  if (onRequestStartChannel.hasSubscribers) {
-    onRequestStartChannel.publish({
-      request: req,
-      response: res,
-      socket,
-      server,
-    });
-  }
-
-  // Check if we should optimize empty requests (those without Content-Length or Transfer-Encoding headers)
-  const shouldOptimize = server[kOptimizeEmptyRequests] === true && !hasBodyHeaders(req);
-
-  if (shouldOptimize) {
-    // Fast processing where emitting 'data', 'end' and 'close' events is
-    // skipped and data is dumped.
-    // This avoids a lot of unnecessary overhead otherwise introduced by
-    // stream.Readable life cycle rules. The downside is that this will
-    // break some servers that read bodies for methods that don't have body headers.
-    req._dumpAndCloseReadable();
-    req._read();
-  }
-
-  if (socket._httpMessage) {
-    // There are already pending outgoing res, append.
-    state.outgoing.push(res);
-  } else {
-    res.assignSocket(socket);
-  }
-
-  // When we're finished writing the response, check if this is the last
-  // response, if so destroy the socket.
-  res.on('finish', onResponseFinish);
-
-  let handled = false;
-
-
-  if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) {
-
-    // From RFC 7230 5.4 https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
-    // A server MUST respond with a 400 (Bad Request) status code to any
-    // HTTP/1.1 request message that lacks a Host header field
-    if (server.requireHostHeader && !hasRequestHeader(req, 4, 'host')) {
-      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 {
-      const expect = getRawHeader(req, 'expect', true);
-      if (expect !== undefined) {
-        handled = true;
-
-        if (continueExpression.test(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;  // No special treatment.
-}
-
-function resetSocketTimeout(server, socket, state) {
-  if (!state.keepAliveTimeoutSet)
-    return;
-
-  socket.setTimeout(server.timeout || 0);
-  state.keepAliveTimeoutSet = false;
-}
-
-function onSocketResume() {
-  // It may seem that the socket is resumed, but this is an enemy's trick to
-  // deceive us! `resume` is emitted asynchronously, and may be called from
-  // `incoming.readStart()`. Stop the socket again here, just to preserve the
-  // state.
-  //
-  // We don't care about stream semantics for the consumed socket anyway.
-  if (this._paused) {
-    this.pause();
-    return;
-  }
-
-  if (this._handle && !this._handle.reading) {
-    this._handle.reading = true;
-    this._handle.readStart();
-  }
-}
-
-function onSocketPause() {
-  if (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);
+  STATUS_CODES,
+  Server,
+  ServerResponse,
+  setupConnectionsTracking,
+  storeHTTPOptions,
+  _connectionListener,
+  kServerResponse,
+  httpServerPreClose,
+  kConnectionsCheckingInterval,
+} = require('internal/http/server');

-    return res;
-  };
-}

 module.exports = {
   STATUS_CODES,
@@ -1542,8 +40,11 @@ module.exports = {
   ServerResponse,
   setupConnectionsTracking,
   storeHTTPOptions,
-  _connectionListener: connectionListener,
+  _connectionListener,
   kServerResponse,
   httpServerPreClose,
   kConnectionsCheckingInterval,
 };
+
+process.emitWarning('The _http_server module is deprecated. Use `node:http` instead.',
+                    'DeprecationWarning', 'DEP0199');
diff --git a/lib/http.js b/lib/http.js
index 726e3e30933..bc99ba61e37 100644
--- a/lib/http.js
+++ b/lib/http.js
@@ -27,10 +27,10 @@ const {
 } = primordials;

 const { validateInteger, validateObject } = require('internal/validators');
-const httpAgent = require('_http_agent');
-const { ClientRequest } = require('_http_client');
-const { methods, parsers } = require('_http_common');
-const { IncomingMessage } = require('_http_incoming');
+const httpAgent = require('internal/http/agent');
+const { ClientRequest } = require('internal/http/client');
+const { methods, parsers } = require('internal/http/common');
+const { IncomingMessage } = require('internal/http/incoming');
 const {
   ERR_INVALID_ARG_TYPE,
   ERR_INVALID_ARG_VALUE,
@@ -48,13 +48,13 @@ const {
   validateHeaderName,
   validateHeaderValue,
   OutgoingMessage,
-} = require('_http_outgoing');
+} = require('internal/http/outgoing');
 const {
   _connectionListener,
   STATUS_CODES,
   Server,
   ServerResponse,
-} = require('_http_server');
+} = require('internal/http/server');
 const {
   parseProxyUrl,
   getGlobalAgent,
diff --git a/lib/https.js b/lib/https.js
index dc9df0da184..454e923cc34 100644
--- a/lib/https.js
+++ b/lib/https.js
@@ -57,15 +57,15 @@ const {
   kWaitForProxyTunnel,
   getGlobalAgent,
 } = require('internal/http');
-const { Agent: HttpAgent } = require('_http_agent');
+const { Agent: HttpAgent } = require('internal/http/agent');
 const {
   httpServerPreClose,
   Server: HttpServer,
   setupConnectionsTracking,
   storeHTTPOptions,
   _connectionListener,
-} = require('_http_server');
-const { ClientRequest } = require('_http_client');
+} = require('internal/http/server');
+const { ClientRequest } = require('internal/http/client');
 let debug = require('internal/util/debuglog').debuglog('https', (fn) => {
   debug = fn;
 });
diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js
index 06daf2183db..363825061dd 100644
--- a/lib/internal/child_process.js
+++ b/lib/internal/child_process.js
@@ -194,9 +194,9 @@ const handleConversion = {
         socket.setTimeout(0);

         if (freeParser === undefined)
-          freeParser = require('_http_common').freeParser;
+          freeParser = require('internal/http/common').freeParser;
         if (HTTPParser === undefined)
-          HTTPParser = require('_http_common').HTTPParser;
+          HTTPParser = require('internal/http/common').HTTPParser;

         // In case of an HTTP connection socket, release the associated
         // resources
diff --git a/lib/internal/http/agent.js b/lib/internal/http/agent.js
new file mode 100644
index 00000000000..3871c5b56a2
--- /dev/null
+++ b/lib/internal/http/agent.js
@@ -0,0 +1,715 @@
+// 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.
+
+'use strict';
+
+const {
+  NumberIsFinite,
+  NumberParseInt,
+  ObjectKeys,
+  ObjectSetPrototypeOf,
+  ObjectValues,
+  Symbol,
+} = primordials;
+
+const net = require('net');
+const EventEmitter = require('events');
+let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
+  debug = fn;
+});
+const {
+  parseProxyConfigFromEnv,
+  kProxyConfig,
+  checkShouldUseProxy,
+  kWaitForProxyTunnel,
+  getGlobalAgent,
+} = require('internal/http');
+const { AsyncResource } = require('async_hooks');
+const {
+  async_id_symbol,
+  owner_symbol,
+} = require('internal/async_hooks').symbols;
+const {
+  deprecateInstantiation,
+  getLazy,
+  kEmptyObject,
+  once,
+} = require('internal/util');
+const {
+  onStreamRead,
+} = require('internal/stream_base_commons');
+const {
+  kReadBytesOrError,
+  streamBaseState,
+} = internalBinding('stream_wrap');
+const {
+  validateNumber,
+  validateOneOf,
+  validateString,
+} = require('internal/validators');
+const assert = require('internal/assert');
+const { getOptionValue } = require('internal/options');
+
+const kOnKeylog = Symbol('onkeylog');
+const kRequestOptions = Symbol('requestOptions');
+const kRequestAsyncResource = Symbol('requestAsyncResource');
+const kFreeSocketDataGuard = Symbol('freeSocketDataGuard');
+
+// New Agent code.
+
+// The largest departure from the previous implementation is that
+// an Agent instance holds connections for a variable number of host:ports.
+// Surprisingly, this is still API compatible as far as third parties are
+// concerned. The only code that really notices the difference is the
+// request object.
+
+// Another departure is that all code related to HTTP parsing is in
+// ClientRequest.onSocket(). The Agent is now *strictly*
+// concerned with managing a connection pool.
+
+class ReusedHandle {
+  constructor(type, handle) {
+    this.type = type;
+    this.handle = handle;
+  }
+}
+
+function freeSocketErrorListener(err) {
+  const socket = this;
+  debug('SOCKET ERROR on FREE socket:', err.message, err.stack);
+  socket.destroy();
+  socket.emit('agentRemove');
+}
+
+// Guard against unsolicited data arriving while a socket is idle in the
+// freeSockets pool.  When the HTTPParser is detached the data would sit
+// in the TCP buffer and be silently consumed as the response for the
+// *next* request that reuses the socket (response-queue poisoning).
+// See: https://hackerone.com/reports/3582376
+function freeSocketOnReadGuard() {
+  const nread = streamBaseState[kReadBytesOrError];
+  if (nread === 0) return;
+
+  debug('READ on FREE socket - destroying poisoned socket');
+  this[owner_symbol].destroy();
+}
+
+function installFreeSocketDataGuard(socket) {
+  if (socket.readableLength > 0) {
+    debug('BUFFERED DATA on FREE socket - destroying poisoned socket');
+    socket.destroy();
+    return;
+  }
+
+  if (socket.connecting) {
+    socket[kFreeSocketDataGuard] = function onConnect() {
+      socket[kFreeSocketDataGuard] = null;
+      installFreeSocketDataGuard(socket);
+    };
+    socket.once('connect', socket[kFreeSocketDataGuard]);
+    return;
+  }
+
+  const handle = socket._handle;
+  if (handle) {
+    handle.onread = freeSocketOnReadGuard;
+    if (!handle.reading) {
+      handle.reading = true;
+      const err = handle.readStart();
+      if (err) socket.destroy();
+    }
+  }
+}
+
+function removeFreeSocketDataGuard(socket) {
+  if (socket[kFreeSocketDataGuard]) {
+    socket.removeListener('connect', socket[kFreeSocketDataGuard]);
+    socket[kFreeSocketDataGuard] = null;
+  }
+
+  const handle = socket._handle;
+  if (handle?.onread === freeSocketOnReadGuard) {
+    handle.onread = onStreamRead;
+  }
+}
+
+function Agent(options) {
+  if (!(this instanceof Agent)) {
+    return deprecateInstantiation(Agent, 'DEP0195', options);
+  }
+
+  EventEmitter.call(this);
+
+  this.options = { __proto__: null, ...options };
+
+  this.defaultPort = this.options.defaultPort || 80;
+  this.protocol = this.options.protocol || 'http:';
+
+  if (this.options.noDelay === undefined)
+    this.options.noDelay = true;
+
+  // Don't confuse net and make it think that we're connecting to a pipe
+  this.options.path = null;
+  this.requests = { __proto__: null };
+  this.sockets = { __proto__: null };
+  this.freeSockets = { __proto__: null };
+  this.keepAliveMsecs = this.options.keepAliveMsecs || 1000;
+  this.keepAlive = this.options.keepAlive || false;
+  this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets;
+  this.maxFreeSockets = this.options.maxFreeSockets || 256;
+  this.scheduling = this.options.scheduling || 'lifo';
+  this.maxTotalSockets = this.options.maxTotalSockets;
+  this.totalSocketCount = 0;
+
+  this.agentKeepAliveTimeoutBuffer =
+    typeof this.options.agentKeepAliveTimeoutBuffer === 'number' &&
+    this.options.agentKeepAliveTimeoutBuffer >= 0 &&
+    NumberIsFinite(this.options.agentKeepAliveTimeoutBuffer) ?
+      this.options.agentKeepAliveTimeoutBuffer :
+      1000;
+
+  const proxyEnv = this.options.proxyEnv;
+  if (typeof proxyEnv === 'object' && proxyEnv !== null) {
+    this[kProxyConfig] = parseProxyConfigFromEnv(proxyEnv, this.protocol, this.keepAlive);
+    debug(`new ${this.protocol} agent with proxy config`, this[kProxyConfig]);
+  }
+
+  validateOneOf(this.scheduling, 'scheduling', ['fifo', 'lifo']);
+
+  if (this.maxTotalSockets !== undefined) {
+    validateNumber(this.maxTotalSockets, 'maxTotalSockets', 1);
+  } else {
+    this.maxTotalSockets = Infinity;
+  }
+
+  this.on('free', (socket, options) => {
+    const name = this.getName(options);
+    debug('agent.on(free)', name);
+
+    // TODO(ronag): socket.destroy(err) might have been called
+    // before coming here and have an 'error' scheduled. In the
+    // case of socket.destroy() below this 'error' has no handler
+    // and could cause unhandled exception.
+
+    if (!socket.writable) {
+      socket.destroy();
+      return;
+    }
+
+    const requests = this.requests[name];
+    if (requests?.length) {
+      const req = requests.shift();
+      const reqAsyncRes = req[kRequestAsyncResource];
+      if (reqAsyncRes) {
+        // Run request within the original async context.
+        reqAsyncRes.runInAsyncScope(() => {
+          asyncResetHandle(socket);
+          setRequestSocket(this, req, socket);
+        });
+        req[kRequestAsyncResource] = null;
+      } else {
+        setRequestSocket(this, req, socket);
+      }
+      if (requests.length === 0) {
+        delete this.requests[name];
+      }
+      return;
+    }
+
+    // If there are no pending requests, then put it in
+    // the freeSockets pool, but only if we're allowed to do so.
+    const req = socket._httpMessage;
+    if (!req || !req.shouldKeepAlive || !this.keepAlive) {
+      socket.destroy();
+      return;
+    }
+
+    const freeSockets = this.freeSockets[name] || [];
+    const freeLen = freeSockets.length;
+    let count = freeLen;
+    if (this.sockets[name])
+      count += this.sockets[name].length;
+
+    if (this.totalSocketCount > this.maxTotalSockets ||
+        count > this.maxSockets ||
+        freeLen >= this.maxFreeSockets ||
+        !this.keepSocketAlive(socket)) {
+      socket.destroy();
+      return;
+    }
+
+    this.freeSockets[name] = freeSockets;
+    socket[async_id_symbol] = -1;
+    socket._httpMessage = null;
+    this.removeSocket(socket, options);
+
+    socket.once('error', freeSocketErrorListener);
+    installFreeSocketDataGuard(socket);
+    freeSockets.push(socket);
+  });
+
+  // Don't emit keylog events unless there is a listener for them.
+  this.on('newListener', maybeEnableKeylog);
+}
+ObjectSetPrototypeOf(Agent.prototype, EventEmitter.prototype);
+ObjectSetPrototypeOf(Agent, EventEmitter);
+
+function maybeEnableKeylog(eventName) {
+  if (eventName === 'keylog') {
+    this.removeListener('newListener', maybeEnableKeylog);
+    // Future sockets will listen on keylog at creation.
+    const agent = this;
+    this[kOnKeylog] = function onkeylog(keylog) {
+      agent.emit('keylog', keylog, this);
+    };
+    // Existing sockets will start listening on keylog now. Both maps hold
+    // arrays of sockets keyed by name, so each bucket has to be walked.
+    const sets = [this.freeSockets, this.sockets];
+    for (let s = 0; s < sets.length; s++) {
+      const buckets = ObjectValues(sets[s]);
+      for (let b = 0; b < buckets.length; b++) {
+        const sockets = buckets[b];
+        for (let n = 0; n < sockets.length; n++) {
+          sockets[n].on('keylog', this[kOnKeylog]);
+        }
+      }
+    }
+  }
+}
+
+const lazyTLS = getLazy(() => require('tls'));
+
+Agent.defaultMaxSockets = Infinity;
+
+// See ProxyConfig in internal/http.js for how the connection should be handled
+// when the agent is configured to use a proxy server.
+Agent.prototype.createConnection = function createConnection(...args) {
+  const normalized = net._normalizeArgs(args);
+  const options = normalized[0];
+  const cb = normalized[1];
+
+  // Check if this specific request should bypass the proxy
+  const shouldUseProxy = checkShouldUseProxy(this[kProxyConfig], options);
+  debug(`http createConnection should use proxy for ${options.host}:${options.port}:`, shouldUseProxy);
+  if (!shouldUseProxy) {  // Forward to net.createConnection if no proxying is needed.
+    return net.createConnection(...args);
+  }
+
+  // Create a copy of the shared proxy connection options and connect
+  // to the proxy server instead of the endpoint. For Agent.prototype.createConnection
+  // which is used by the http agent, this is enough
+  const connectOptions = {
+    ...this[kProxyConfig].proxyConnectionOptions,
+  };
+  const proxyProtocol = this[kProxyConfig].protocol;
+  if (proxyProtocol === 'http:') {
+    return net.connect(connectOptions, cb);
+  } else if (proxyProtocol === 'https:') {
+    return lazyTLS().connect(connectOptions, cb);
+  }
+  // This should be unreachable because proxy config should be null for other protocols.
+  assert.fail(`Unexpected proxy protocol ${proxyProtocol}`);
+
+};
+
+// Get the key for a given set of request options
+Agent.prototype.getName = function getName(options = kEmptyObject) {
+  let name = options.host || 'localhost';
+
+  name += ':';
+  if (options.port)
+    name += options.port;
+
+  name += ':';
+  if (options.localAddress)
+    name += options.localAddress;
+
+  // Pacify parallel/test-http-agent-getname by only appending
+  // the ':' when options.family is set.
+  if (options.family === 4 || options.family === 6)
+    name += `:${options.family}`;
+
+  if (options.socketPath)
+    name += `:${options.socketPath}`;
+
+  return name;
+};
+
+function handleSocketAfterProxy(err, req) {
+  if (err.code === 'ERR_PROXY_TUNNEL') {
+    if (err.proxyTunnelTimeout) {
+      req.emit('timeout');  // Propagate the timeout from the tunnel to the request.
+    }
+    req.emit('error', err);
+  }
+}
+
+Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
+                                                 localAddress/* legacy */) {
+  // Legacy API: addRequest(req, host, port, localAddress)
+  if (typeof options === 'string') {
+    options = {
+      __proto__: null,
+      host: options,
+      port,
+      localAddress,
+    };
+  }
+
+  // Here the agent options will override per-request options.
+  options = { __proto__: null, ...options, ...this.options };
+  if (options.socketPath)
+    options.path = options.socketPath;
+
+  normalizeServerName(options, req);
+
+  const name = this.getName(options);
+  this.sockets[name] ||= [];
+
+  const freeSockets = this.freeSockets[name];
+  let socket;
+  if (freeSockets) {
+    while (freeSockets.length && freeSockets[0].destroyed) {
+      freeSockets.shift();
+    }
+    socket = this.scheduling === 'fifo' ?
+      freeSockets.shift() :
+      freeSockets.pop();
+    if (!freeSockets.length)
+      delete this.freeSockets[name];
+  }
+
+  const freeLen = freeSockets ? freeSockets.length : 0;
+  const sockLen = freeLen + this.sockets[name].length;
+
+  // Reusing a socket from the pool.
+  // If the caller specified a highWaterMark that differs from the pooled
+  // socket's writableHighWaterMark, sync the socket's HWM so that
+  // backpressure semantics match what the caller requested.
+  if (socket && options.highWaterMark != null &&
+      socket.writableHighWaterMark !== options.highWaterMark) {
+    debug('sync reused socket HWM (socket=%d, request=%d)',
+          socket.writableHighWaterMark, options.highWaterMark);
+    socket._writableState.highWaterMark = options.highWaterMark;
+  }
+
+  if (socket) {
+    asyncResetHandle(socket);
+    this.reuseSocket(socket, req);
+    setRequestSocket(this, req, socket);
+    this.sockets[name].push(socket);
+  } else if (sockLen < this.maxSockets &&
+             this.totalSocketCount < this.maxTotalSockets) {
+    // If we are under maxSockets create a new one.
+    this.createSocket(req, options, (err, socket) => {
+      if (err) {
+        handleSocketAfterProxy(err, req);
+        debug('call onSocket', sockLen, freeLen);
+        req.onSocket(socket, err);
+        return;
+      }
+
+      setRequestSocket(this, req, socket);
+    });
+  } else {
+    debug('wait for socket');
+    // We are over limit so we'll add it to the queue.
+    this.requests[name] ||= [];
+
+    // Used to create sockets for pending requests from different origin
+    req[kRequestOptions] = options;
+    // Used to capture the original async context.
+    req[kRequestAsyncResource] = new AsyncResource('QueuedRequest');
+
+    this.requests[name].push(req);
+  }
+};
+
+Agent.prototype.createSocket = function createSocket(req, options, cb) {
+  // Here the agent options will override per-request options.
+  options = { __proto__: null, ...options, ...this.options };
+  if (options.socketPath)
+    options.path = options.socketPath;
+
+  normalizeServerName(options, req);
+
+  // Make sure per-request timeout is respected.
+  const timeout = req.timeout || this.options.timeout || undefined;
+  if (timeout) {
+    options.timeout = timeout;
+  }
+
+  const name = this.getName(options);
+  options._agentKey = name;
+
+  debug('createConnection', name);
+  options.encoding = null;
+
+  const oncreate = once((err, s) => {
+    if (err)
+      return cb(err);
+    this.sockets[name] ||= [];
+    this.sockets[name].push(s);
+    this.totalSocketCount++;
+    debug('sockets', name, this.sockets[name].length, this.totalSocketCount);
+    installListeners(this, s, options);
+    cb(null, s);
+  });
+  // When keepAlive is true, pass the related options to createConnection
+  if (this.keepAlive) {
+    options.keepAlive = this.keepAlive;
+    options.keepAliveInitialDelay = this.keepAliveMsecs;
+  }
+
+  const newSocket = this.createConnection(options, oncreate);
+  // In the case where we are proxying through a tunnel for HTTPS, only add
+  // the socket to the pool and install/invoke the listeners after
+  // the tunnel is successfully established, so that actual operations
+  // on the socket all go through the tunnel. Errors emitted during
+  // tunnel establishment will be handled in the createConnection method
+  // in lib/https.js.
+  if (newSocket && !newSocket[kWaitForProxyTunnel])
+    oncreate(null, newSocket);
+};
+
+function normalizeServerName(options, req) {
+  if (!options.servername && options.servername !== '')
+    options.servername = calculateServerName(options, req);
+}
+
+function calculateServerName(options, req) {
+  let servername = options.host;
+  const hostHeader = req.getHeader('host');
+  if (hostHeader) {
+    validateString(hostHeader, 'options.headers.host');
+
+    // abc => abc
+    // abc:123 => abc
+    // [::1] => ::1
+    // [::1]:123 => ::1
+    if (hostHeader[0] === '[') {
+      const index = hostHeader.indexOf(']');
+      if (index === -1) {
+        // Leading '[', but no ']'. Need to do something...
+        servername = hostHeader;
+      } else {
+        servername = hostHeader.substring(1, index);
+      }
+    } else {
+      servername = hostHeader.split(':', 1)[0];
+    }
+  }
+  // Don't implicitly set invalid (IP) servernames.
+  if (net.isIP(servername))
+    servername = '';
+  return servername;
+}
+
+function installListeners(agent, s, options) {
+  function onFree() {
+    debug('CLIENT socket onFree');
+    agent.emit('free', s, options);
+  }
+  s.on('free', onFree);
+
+  function onClose(err) {
+    debug('CLIENT socket onClose');
+    // This is the only place where sockets get removed from the Agent.
+    // If you want to remove a socket from the pool, just close it.
+    // All socket errors end in a close event anyway.
+    agent.totalSocketCount--;
+    agent.removeSocket(s, options);
+  }
+  s.on('close', onClose);
+
+  function onTimeout() {
+    debug('CLIENT socket onTimeout');
+
+    // Destroy if in free list.
+    // TODO(ronag): Always destroy, even if not in free list.
+    const sockets = agent.freeSockets;
+    if (ObjectKeys(sockets).some((name) => sockets[name].includes(s))) {
+      return s.destroy();
+    }
+  }
+  s.on('timeout', onTimeout);
+
+  function onRemove() {
+    // We need this function for cases like HTTP 'upgrade'
+    // (defined by WebSockets) where we need to remove a socket from the
+    // pool because it'll be locked up indefinitely
+    debug('CLIENT socket onRemove');
+    agent.totalSocketCount--;
+    agent.removeSocket(s, options);
+    s.removeListener('close', onClose);
+    s.removeListener('free', onFree);
+    s.removeListener('timeout', onTimeout);
+    s.removeListener('agentRemove', onRemove);
+  }
+  s.on('agentRemove', onRemove);
+
+  if (agent[kOnKeylog]) {
+    s.on('keylog', agent[kOnKeylog]);
+  }
+}
+
+Agent.prototype.removeSocket = function removeSocket(s, options) {
+  const name = this.getName(options);
+  debug('removeSocket', name, 'writable:', s.writable);
+  const sets = [this.sockets];
+
+  // If the socket was destroyed, remove it from the free buffers too.
+  if (!s.writable)
+    sets.push(this.freeSockets);
+
+  for (let sk = 0; sk < sets.length; sk++) {
+    const sockets = sets[sk];
+
+    if (sockets[name]) {
+      const index = sockets[name].indexOf(s);
+      if (index !== -1) {
+        sockets[name].splice(index, 1);
+        // Don't leak
+        if (sockets[name].length === 0)
+          delete sockets[name];
+      }
+    }
+  }
+
+  let req;
+  if (this.requests[name]?.length) {
+    debug('removeSocket, have a request, make a socket');
+    req = this.requests[name][0];
+  } else {
+    // TODO(rickyes): this logic will not be FIFO across origins.
+    // There might be older requests in a different origin, but
+    // if the origin which releases the socket has pending requests
+    // that will be prioritized.
+    const keys = ObjectKeys(this.requests);
+    for (let i = 0; i < keys.length; i++) {
+      const prop = keys[i];
+      // Check whether this specific origin is already at maxSockets
+      if (this.sockets[prop]?.length) break;
+      debug('removeSocket, have a request with different origin,' +
+        ' make a socket');
+      req = this.requests[prop][0];
+      options = req[kRequestOptions];
+      break;
+    }
+  }
+
+  if (req && options) {
+    req[kRequestOptions] = undefined;
+    // If we have pending requests and a socket gets closed make a new one
+    this.createSocket(req, options, (err, socket) => {
+      if (err) {
+        handleSocketAfterProxy(err, req);
+        req.onSocket(null, err);
+        return;
+      }
+
+      socket.emit('free');
+    });
+  }
+
+};
+
+Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
+  socket.setKeepAlive(true, this.keepAliveMsecs);
+  socket.unref();
+
+  let agentTimeout = this.options.timeout || 0;
+  let canKeepSocketAlive = true;
+
+  if (socket._httpMessage?.res) {
+    const keepAliveHint = socket._httpMessage.res.headers['keep-alive'];
+
+    if (keepAliveHint) {
+      const hint = /^timeout=(\d+)/.exec(keepAliveHint)?.[1];
+
+      if (hint) {
+        // Let the timer expire before the announced timeout to reduce
+        // the likelihood of ECONNRESET errors
+        let serverHintTimeout = (NumberParseInt(hint) * 1000) - this.agentKeepAliveTimeoutBuffer;
+        serverHintTimeout = serverHintTimeout > 0 ? serverHintTimeout : 0;
+        if (serverHintTimeout === 0) {
+          // Cannot safely reuse the socket because the server timeout is
+          // too short
+          canKeepSocketAlive = false;
+        } else if (serverHintTimeout < agentTimeout) {
+          agentTimeout = serverHintTimeout;
+        }
+      }
+    }
+  }
+
+  if (socket.timeout !== agentTimeout) {
+    socket.setTimeout(agentTimeout);
+  }
+
+  return canKeepSocketAlive;
+};
+
+Agent.prototype.reuseSocket = function reuseSocket(socket, req) {
+  debug('have free socket');
+  socket.removeListener('error', freeSocketErrorListener);
+  removeFreeSocketDataGuard(socket);
+  req.reusedSocket = true;
+  socket.ref();
+};
+
+Agent.prototype.destroy = function destroy() {
+  const sets = [this.freeSockets, this.sockets];
+  for (let s = 0; s < sets.length; s++) {
+    const set = sets[s];
+    const keys = ObjectKeys(set);
+    for (let v = 0; v < keys.length; v++) {
+      const setName = set[keys[v]];
+      for (let n = 0; n < setName.length; n++) {
+        setName[n].destroy();
+      }
+    }
+  }
+};
+
+function setRequestSocket(agent, req, socket) {
+  req.onSocket(socket);
+  const agentTimeout = agent.options.timeout || 0;
+  if (req.timeout === undefined || req.timeout === agentTimeout) {
+    return;
+  }
+  socket.setTimeout(req.timeout);
+}
+
+function asyncResetHandle(socket) {
+  // Guard against an uninitialized or user supplied Socket.
+  const handle = socket._handle;
+  if (handle && typeof handle.asyncReset === 'function') {
+    // Assign the handle a new asyncId and run any destroy()/init() hooks.
+    handle.asyncReset(new ReusedHandle(handle.getProviderType(), handle));
+    socket[async_id_symbol] = handle.getAsyncId();
+  }
+}
+
+module.exports = {
+  Agent,
+  globalAgent: getGlobalAgent(getOptionValue('--use-env-proxy') ? process.env : undefined, Agent),
+};
diff --git a/lib/internal/http/client.js b/lib/internal/http/client.js
new file mode 100644
index 00000000000..1952e7b69af
--- /dev/null
+++ b/lib/internal/http/client.js
@@ -0,0 +1,1303 @@
+// 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.
+
+'use strict';
+
+const {
+  ArrayIsArray,
+  Boolean,
+  Error,
+  NumberIsFinite,
+  ObjectAssign,
+  ObjectDefineProperty,
+  ObjectHasOwn,
+  ObjectKeys,
+  ObjectSetPrototypeOf,
+  ReflectApply,
+  String,
+  Symbol,
+} = primordials;
+
+const net = require('net');
+const assert = require('internal/assert');
+const {
+  deprecateInstantiation,
+  kEmptyObject,
+  once,
+} = require('internal/util');
+const {
+  _checkIsHttpToken: checkIsHttpToken,
+  freeParser,
+  parsers,
+  HTTPParser,
+  calculateLenientFlags,
+  prepareError,
+  kSkipPendingData,
+} = require('internal/http/common');
+const { kDetachAbortSignal } = require('internal/http/incoming');
+const {
+  kHighWaterMark,
+  kUniqueHeaders,
+  parseUniqueHeadersOption,
+  OutgoingMessage,
+} = require('internal/http/outgoing');
+const Agent = require('internal/http/agent');
+const { Buffer } = require('buffer');
+const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
+const { URL, urlToHttpOptions, isURL } = require('internal/url');
+const {
+  kOutHeaders,
+  kNeedDrain,
+  isTraceHTTPEnabled,
+  traceBegin,
+  traceEnd,
+  getNextTraceEventId,
+  kProxyConfig,
+  checkShouldUseProxy,
+} = require('internal/http');
+const {
+  ConnResetException,
+  codes: {
+    ERR_HTTP_HEADERS_SENT,
+    ERR_INVALID_ARG_TYPE,
+    ERR_INVALID_ARG_VALUE,
+    ERR_INVALID_HTTP_TOKEN,
+    ERR_INVALID_PROTOCOL,
+    ERR_UNESCAPED_CHARACTERS,
+  },
+} = require('internal/errors');
+const {
+  validateInteger,
+  validateBoolean,
+  validateOneOf,
+  validatePort,
+  validateString,
+} = require('internal/validators');
+const { getTimerDuration } = require('internal/timers');
+const {
+  hasObserver,
+  startPerf,
+  stopPerf,
+} = require('internal/perf/observe');
+
+const kClientRequestStatistics = Symbol('ClientRequestStatistics');
+
+const dc = require('diagnostics_channel');
+const onClientRequestCreatedChannel = dc.channel('http.client.request.created');
+const onClientRequestStartChannel = dc.channel('http.client.request.start');
+const onClientRequestErrorChannel = dc.channel('http.client.request.error');
+const onClientResponseFinishChannel = dc.channel('http.client.response.finish');
+
+function emitErrorEvent(request, error) {
+  if (onClientRequestErrorChannel.hasSubscribers) {
+    onClientRequestErrorChannel.publish({
+      request,
+      error,
+    });
+  }
+  request.emit('error', error);
+}
+
+const { addAbortSignal, finished } = require('stream');
+
+let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
+  debug = fn;
+});
+
+const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;
+const kError = Symbol('kError');
+const kPath = Symbol('kPath');
+const kAuthority = Symbol('kAuthority');
+const kProxyRewrittenToAbsolute = Symbol('kProxyRewrittenToAbsolute');
+
+const HTTP_CLIENT_TRACE_EVENT_NAME = 'http.client.request';
+
+function validateHost(host, name) {
+  if (host !== null && host !== undefined && typeof host !== 'string') {
+    throw new ERR_INVALID_ARG_TYPE(`options.${name}`,
+                                   ['string', 'undefined', 'null'],
+                                   host);
+  }
+  return host;
+}
+
+class HTTPClientAsyncResource {
+  constructor(type, req) {
+    this.type = type;
+    this.req = req;
+  }
+}
+
+// The only documented shape is [k, v, k, v, ...]. Here we also accept [[k, v], [k, v], ...].
+// for backward compatibility, and reject others. Also reject if there are duplicate Host entries.
+// Returns the Host header value, or undefined if absent.
+function getHostFromHeaderArray(headers) {
+  let host;
+  const isPairs = headers.length > 0 && ArrayIsArray(headers[0]);
+  if (isPairs) {
+    for (let i = 0; i < headers.length; i++) {
+      const entry = headers[i];
+      if (!ArrayIsArray(entry)) {
+        throw new ERR_INVALID_ARG_VALUE(`options.headers[${i}]`, typeof entry,
+                                        'must be an array when headers is passed as an array of pairs');
+      }
+      if (`${entry[0]}`.toLowerCase() === 'host') {
+        if (host !== undefined) {
+          throw new ERR_INVALID_ARG_VALUE('options.headers', '(redacted)',
+                                          'must not contain duplicate Host headers');
+        }
+        host = `${entry[1]}`;
+      }
+    }
+  } else {
+    for (let i = 0; i + 1 < headers.length; i += 2) {
+      if (`${headers[i]}`.toLowerCase() === 'host') {
+        if (host !== undefined) {
+          throw new ERR_INVALID_ARG_VALUE('options.headers', '(redacted)',
+                                          'must not contain duplicate Host headers');
+        }
+        host = `${headers[i + 1]}`;
+      }
+    }
+  }
+  return host;
+}
+
+function authoritiesMatch(canonicalHost, hostFromHeader) {
+  let parsed;
+  try {
+    parsed = new URL(`http://${hostFromHeader}`);
+  } catch {
+    return false;
+  }
+  if (parsed.username || parsed.password ||
+      parsed.pathname !== '/' || parsed.search || parsed.hash) {
+    return false;
+  }
+  return parsed.host === canonicalHost;
+}
+
+// https://datatracker.ietf.org/doc/html/rfc9112#section-3.2
+// When the request target is in absolute-form, ensure it is consistent with
+// the request authority: same scheme, no userinfo, and an authority
+// component agree with options.host[:port].
+function validateRequestAuthority(pathOption, proxyAuthority, userHostHeader, headerArray) {
+  validatePort(proxyAuthority.port, 'options.port', true);
+  pathOption = `${pathOption}`;
+  const requestBase = new URL(`http://${proxyAuthority.host}`);
+  requestBase.port = proxyAuthority.port;
+
+  const result = { requestBase };
+  if (headerArray !== undefined) {
+    const host = getHostFromHeaderArray(headerArray);
+    // Since we don't mutate the header array to normalize the Host value, unlike
+    // in the case of other shapes of headers provided, we check that it is identical
+    // to the authority from the requestBase.
+    if (host !== undefined && host !== requestBase.host) {
+      throw new ERR_INVALID_ARG_VALUE(
+        'Host in options.headers', host,
+        `must match the request authority (${requestBase.host})`);
+    }
+  } else if (userHostHeader !== undefined) {
+    if (!authoritiesMatch(requestBase.host, userHostHeader)) {
+      throw new ERR_INVALID_ARG_VALUE(
+        'Host in options.headers', userHostHeader,
+        `must match the request authority (${requestBase.host})`);
+    }
+  }
+
+  // Per RFC 9112 Section 3.2, if request target is in absolute-form its authority
+  // must agree with the request authority.
+  let requestURL;
+  let isAbsoluteForm = false;
+  try {
+    requestURL = new URL(pathOption);
+    isAbsoluteForm = true;
+  } catch {
+    if (pathOption.charCodeAt(0) !== 0x2F) {
+      throw new ERR_INVALID_ARG_VALUE(
+        'options.path', pathOption, 'must be in absolute-form or start with /');
+    }
+    requestURL = new URL(requestBase.origin + pathOption);
+  }
+  result.requestURL = requestURL;
+  if (!isAbsoluteForm) {
+    return result;
+  }
+
+  if (requestURL.username || requestURL.password) {
+    requestURL.username = '';
+    requestURL.password = '';
+    throw new ERR_INVALID_ARG_VALUE(
+      'options.path', requestURL.href, 'must not contain userinfo, use options.auth instead');
+  }
+
+  if (requestURL.protocol !== 'http:') {
+    throw new ERR_INVALID_ARG_VALUE(
+      'options.path', requestURL.protocol, 'must use http: scheme when specified as an absolute URL');
+  }
+
+  if (requestBase.host !== requestURL.host) {
+    throw new ERR_INVALID_ARG_VALUE(
+      'options.path', requestURL, `must match the request authority (${requestBase.host})`);
+  }
+
+  return result;
+}
+
+// When proxying a HTTP request, the following needs to be done:
+// https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.2
+// 1. Rewrite the request path to absolute-form.
+// 2. Add proxy-connection and proxy-authorization headers appropriately.
+//
+// This function checks whether the request should be rewritten for proxying
+// and modifies the headers as well as req.path if necessary.
+// The handling of the proxy server connection is done in createConnection.
+// It also validates that the Host header and absolute-form path authority match the
+// request authority specified by reqOptions.
+function rewriteForProxiedHttp(req, reqOptions, proxyAuthority, userHostHeader, headerArray) {
+  if (req._header) {
+    debug('request._header is already sent, skipping rewriteForProxiedHttp', reqOptions);
+    return false;
+  }
+  const agent = req.agent;
+  if (!agent || !agent[kProxyConfig]) {
+    return false;
+  }
+  if ((reqOptions.protocol || agent.protocol) !== 'http:') {
+    return false;
+  }
+  // TODO(joyeecheung): cache this on the req or on the agent.
+  const shouldUseProxy = checkShouldUseProxy(agent[kProxyConfig], reqOptions);
+  debug(`rewriteForProxiedHttp should use proxy for ${reqOptions.host}:${reqOptions.port}:`, shouldUseProxy);
+  if (!shouldUseProxy) {
+    return false;
+  }
+
+  // Per RFC 9112 Section 3.2.2, we don't need to rewrite CONNECT or OPTIONS * requests.
+  let requestURL;
+  if (req.method !== 'CONNECT' && !(req.method === 'OPTIONS' && req.path === '*')) {
+    // Validate Host header values agree with the request authority before mutating req,
+    // so a rejected request doesn't leave proxy-* headers stuck on the outgoing header store.
+    // XXX(joyeecheung): This validates whether the request conforms to the RFC, but here
+    // we only do it for proxied requests for backward compatibility. For non-proxied requests,
+    // ensuring that the request is well formed has been entirely left to the user.
+    const result = validateRequestAuthority(req.path, proxyAuthority, userHostHeader, headerArray);
+    if (headerArray === undefined) {
+      const currentHost = req.getHeader('host');
+      if (currentHost !== undefined && currentHost !== result.requestBase.host) {
+        req.setHeader('Host', result.requestBase.host);
+      }
+    }
+    requestURL = result.requestURL;
+  }
+
+  // Add proxy headers.
+  const { auth, href } = agent[kProxyConfig];
+  if (auth) {
+    req.setHeader('proxy-authorization', auth);
+  }
+  if (req.shouldKeepAlive) {
+    req.setHeader('proxy-connection', 'keep-alive');
+  } else {
+    req.setHeader('proxy-connection', 'close');
+  }
+
+  if (requestURL !== undefined) {
+    // Convert the path to absolute-form. The authority is built from options.
+    req.path = requestURL.href;
+    req[kProxyRewrittenToAbsolute] = true;
+  }
+  debug(`updated request for HTTP proxy ${href} with ${req.path} `, req[kOutHeaders]);
+  return true;
+};
+
+function ClientRequest(input, options, cb) {
+  if (!(this instanceof ClientRequest)) {
+    return deprecateInstantiation(ClientRequest, 'DEP0195', input, options, cb);
+  }
+
+  OutgoingMessage.call(this);
+
+  let pathIsFromURL = false;
+  if (typeof input === 'string') {
+    const urlStr = input;
+    input = urlToHttpOptions(new URL(urlStr));
+    pathIsFromURL = true;
+  } else if (isURL(input)) {
+    // url.URL instance
+    input = urlToHttpOptions(input);
+    pathIsFromURL = true;
+  } else {
+    cb = options;
+    options = input;
+    input = null;
+  }
+
+  if (typeof options === 'function') {
+    cb = options;
+    options = input || kEmptyObject;
+  } else {
+    const hasPathOverride = pathIsFromURL &&
+      options != null &&
+      ObjectHasOwn(options, 'path');
+    if (hasPathOverride) {
+      pathIsFromURL = false;
+    }
+
+    options = ObjectAssign({ __proto__: null }, input, options);
+  }
+
+  // Propagate the user's highWaterMark to OutgoingMessage so that
+  // _writeRaw() uses the correct threshold for writes buffered before
+  // the socket connects (Path B).  Without this, the OutgoingMessage
+  // defaults to 64 KB regardless of what the caller requested.
+  if (options.highWaterMark != null) {
+    this[kHighWaterMark] = options.highWaterMark;
+  }
+
+  let agent = options.agent;
+  const defaultAgent = options._defaultAgent || Agent.globalAgent;
+  if (agent === false) {
+    agent = new defaultAgent.constructor();
+  } else if (agent === null || agent === undefined) {
+    if (typeof options.createConnection !== 'function') {
+      agent = defaultAgent;
+    }
+    // Explicitly pass through this statement as agent will not be used
+    // when createConnection is provided.
+  } else if (typeof agent.addRequest !== 'function') {
+    throw new ERR_INVALID_ARG_TYPE('options.agent',
+                                   ['Agent-like Object', 'undefined', 'false'],
+                                   agent);
+  }
+  this.agent = agent;
+
+  const protocol = options.protocol || defaultAgent.protocol;
+  let expectedProtocol = defaultAgent.protocol;
+  if (this.agent?.protocol)
+    expectedProtocol = this.agent.protocol;
+
+  if (options.path) {
+    const path = String(options.path);
+    if (INVALID_PATH_REGEX.test(path)) {
+      debug('Path contains unescaped characters: "%s"', path);
+      throw new ERR_UNESCAPED_CHARACTERS('Request path');
+    }
+  }
+
+  if (protocol !== expectedProtocol) {
+    throw new ERR_INVALID_PROTOCOL(protocol, expectedProtocol);
+  }
+
+  const defaultPort = options.defaultPort ||
+                    (this.agent?.defaultPort);
+
+  const optsWithoutSignal = { __proto__: null, ...options };
+
+  const port = optsWithoutSignal.port = options.port || defaultPort || 80;
+  const host = optsWithoutSignal.host = validateHost(options.hostname, 'hostname') ||
+                                        validateHost(options.host, 'host') || 'localhost';
+
+  const setHost = options.setHost !== undefined ?
+    Boolean(options.setHost) :
+    options.setDefaultHeaders !== false;
+
+  this._removedConnection = options.setDefaultHeaders === false;
+  this._removedContLen = options.setDefaultHeaders === false;
+  this._removedTE = options.setDefaultHeaders === false;
+
+  this.socketPath = options.socketPath;
+
+  if (options.timeout !== undefined)
+    this.timeout = getTimerDuration(options.timeout, 'timeout');
+
+  const signal = options.signal;
+  if (signal) {
+    addAbortSignal(signal, this);
+    delete optsWithoutSignal.signal;
+  }
+  let method = options.method;
+  if (method != null) {
+    validateString(method, 'options.method');
+  }
+
+  if (method) {
+    if (!checkIsHttpToken(method)) {
+      throw new ERR_INVALID_HTTP_TOKEN('Method', method);
+    }
+    method = this.method = method.toUpperCase();
+  } else {
+    method = this.method = 'GET';
+  }
+
+  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 httpValidation = options.httpValidation;
+  if (httpValidation !== undefined) {
+    validateOneOf(httpValidation, 'options.httpValidation',
+                  ['strict', 'relaxed', 'insecure']);
+    if (insecureHTTPParser !== undefined) {
+      throw new ERR_INVALID_ARG_VALUE(
+        'options.httpValidation',
+        httpValidation,
+        'cannot be used together with options.insecureHTTPParser',
+      );
+    }
+  }
+
+  this.httpValidation = httpValidation;
+
+  if (options.joinDuplicateHeaders !== undefined) {
+    validateBoolean(options.joinDuplicateHeaders, 'options.joinDuplicateHeaders');
+  }
+
+  this.joinDuplicateHeaders = options.joinDuplicateHeaders;
+
+  let path = options.path || '/';
+  // Strip the leading slash added when the CONNECT target comes from a URL.
+  if (method === 'CONNECT' && pathIsFromURL && path[0] === '/') {
+    path = path.slice(1) || '/';
+  }
+
+  this[kPath] = path;
+  if (cb) {
+    this.once('response', cb);
+  }
+
+  if (method === 'GET' ||
+      method === 'HEAD' ||
+      method === 'DELETE' ||
+      method === 'OPTIONS' ||
+      method === 'TRACE' ||
+      method === 'CONNECT') {
+    this.useChunkedEncodingByDefault = false;
+  } else {
+    this.useChunkedEncodingByDefault = true;
+  }
+
+  this._ended = false;
+  this.res = null;
+  this.aborted = false;
+  this.timeoutCb = null;
+  this.upgradeOrConnect = false;
+  this.parser = null;
+  this.maxHeadersCount = null;
+  this.reusedSocket = false;
+  this.host = host;
+  this.protocol = protocol;
+  this[kProxyRewrittenToAbsolute] = false;
+
+  if (this.agent) {
+    // If there is an agent we should default to Connection:keep-alive,
+    // but only if the Agent will actually reuse the connection!
+    // If it's not a keepAlive agent, and the maxSockets==Infinity, then
+    // there's never a case where this socket will actually be reused
+    if (!this.agent.keepAlive && !NumberIsFinite(this.agent.maxSockets)) {
+      this._last = true;
+      this.shouldKeepAlive = false;
+    } else {
+      this._last = false;
+      this.shouldKeepAlive = true;
+    }
+  }
+
+  let hostHeaderFromOptions = host;
+  // For the Host header, ensure that IPv6 addresses are enclosed
+  // in square brackets, as defined by URI formatting
+  // https://tools.ietf.org/html/rfc3986#section-3.2.2
+  const posColon = hostHeaderFromOptions.indexOf(':');
+  if (posColon !== -1 &&
+      hostHeaderFromOptions.includes(':', posColon + 1) &&
+      hostHeaderFromOptions.charCodeAt(0) !== 91/* '[' */) {
+    hostHeaderFromOptions = `[${hostHeaderFromOptions}]`;
+  }
+  const proxyAuthority = { host: hostHeaderFromOptions, port };
+
+  if (port && +port !== defaultPort) {
+    hostHeaderFromOptions += ':' + port;
+  }
+  // Preserve the request authority (with the port when non-default) so that
+  // the perf_hooks entry can report a faithful URL.
+  this[kAuthority] = hostHeaderFromOptions;
+  const headersArray = ArrayIsArray(options.headers);
+  if (!headersArray) {
+    if (options.headers) {
+      const keys = ObjectKeys(options.headers);
+      // Retain for(;;) loop for performance reasons
+      // Refs: https://github.com/nodejs/node/pull/30958
+      for (let i = 0; i < keys.length; i++) {
+        const key = keys[i];
+        this.setHeader(key, options.headers[key]);
+      }
+    }
+
+    // Save the Host header before the implicit auto-set below, so the
+    // proxy validator can tell user-explicit values from Node-generated ones.
+    const userHostHeader = this.getHeader('host');
+
+    if (host && !this.getHeader('host') && setHost) {
+      const hostHeader = method === 'CONNECT' && options.path ?
+        String(this.path) : hostHeaderFromOptions;
+      this.setHeader('Host', hostHeader);
+    }
+
+    if (options.auth && !this.getHeader('Authorization')) {
+      this.setHeader('Authorization', 'Basic ' +
+                     Buffer.from(options.auth).toString('base64'));
+    }
+
+    if (this.getHeader('expect')) {
+      if (this._header) {
+        throw new ERR_HTTP_HEADERS_SENT('render');
+      }
+
+      rewriteForProxiedHttp(this, optsWithoutSignal, proxyAuthority, userHostHeader);
+      this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
+                        this[kOutHeaders]);
+    } else {
+      rewriteForProxiedHttp(this, optsWithoutSignal, proxyAuthority, userHostHeader);
+    }
+  } else {
+    rewriteForProxiedHttp(this, optsWithoutSignal, proxyAuthority, undefined, options.headers);
+    this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
+                      options.headers);
+  }
+
+  this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders);
+
+  // initiate connection
+  if (this.agent) {
+    this.agent.addRequest(this, optsWithoutSignal);
+  } else {
+    // No agent, default to Connection:close.
+    this._last = true;
+    this.shouldKeepAlive = false;
+    let opts = optsWithoutSignal;
+    if (opts.path || opts.socketPath) {
+      opts = { ...optsWithoutSignal };
+      if (opts.socketPath) {
+        opts.path = opts.socketPath;
+      } else {
+        opts.path &&= undefined;
+      }
+    }
+    if (typeof opts.createConnection === 'function') {
+      const oncreate = once((err, socket) => {
+        if (err) {
+          process.nextTick(() => emitErrorEvent(this, err));
+        } else {
+          this.onSocket(socket);
+        }
+      });
+
+      try {
+        const newSocket = opts.createConnection(opts, oncreate);
+        if (newSocket) {
+          oncreate(null, newSocket);
+        }
+      } catch (err) {
+        oncreate(err);
+      }
+    } else {
+      debug('CLIENT use net.createConnection', opts);
+      this.onSocket(net.createConnection(opts));
+    }
+  }
+  if (onClientRequestCreatedChannel.hasSubscribers) {
+    onClientRequestCreatedChannel.publish({
+      request: this,
+    });
+  }
+}
+ObjectSetPrototypeOf(ClientRequest.prototype, OutgoingMessage.prototype);
+ObjectSetPrototypeOf(ClientRequest, OutgoingMessage);
+
+ObjectDefineProperty(ClientRequest.prototype, 'path', {
+  __proto__: null,
+  get() {
+    return this[kPath];
+  },
+  set(value) {
+    const path = String(value);
+    if (INVALID_PATH_REGEX.test(path)) {
+      throw new ERR_UNESCAPED_CHARACTERS('Request path');
+    }
+    this[kPath] = path;
+  },
+  configurable: true,
+  enumerable: true,
+});
+
+ClientRequest.prototype._finish = function _finish() {
+  OutgoingMessage.prototype._finish.call(this);
+  if (hasObserver('http')) {
+    startPerf(this, kClientRequestStatistics, {
+      type: 'http',
+      name: 'HttpClient',
+      detail: {
+        req: {
+          method: this.method,
+          // If the path has been rewritten to absolute-form for proxying,
+          // it is already a full URL.
+          url: this[kProxyRewrittenToAbsolute] ?
+            this.path :
+            `${this.protocol}//${this[kAuthority]}${this.path}`,
+          headers: typeof this.getHeaders === 'function' ? this.getHeaders() : {},
+        },
+      },
+    });
+  }
+  if (onClientRequestStartChannel.hasSubscribers) {
+    onClientRequestStartChannel.publish({
+      request: this,
+    });
+  }
+  if (isTraceHTTPEnabled()) {
+    this._traceEventId = getNextTraceEventId();
+    traceBegin(HTTP_CLIENT_TRACE_EVENT_NAME, this._traceEventId);
+  }
+};
+
+ClientRequest.prototype._implicitHeader = function _implicitHeader() {
+  if (this._header) {
+    throw new ERR_HTTP_HEADERS_SENT('render');
+  }
+  this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
+                    this[kOutHeaders]);
+};
+
+ClientRequest.prototype.abort = function abort() {
+  if (this.aborted) {
+    return;
+  }
+  this.aborted = true;
+  process.nextTick(emitAbortNT, this);
+  this.destroy();
+};
+
+ClientRequest.prototype.destroy = function destroy(err) {
+  if (this.destroyed) {
+    return this;
+  }
+  this.destroyed = true;
+
+  // If we're aborting, we don't care about any more response data.
+  if (this.res) {
+    this.res._dump();
+  }
+
+  // Nothing left to cancel: the request was fully sent and the response was
+  // fully received. Destroying the socket here would emit an error on a
+  // socket that is already being released to the agent, at which point
+  // socketErrorListener has been removed and nothing would handle it.
+  if (this.writableFinished && this.res?.complete) {
+    return this;
+  }
+
+  this[kError] = err;
+  this.socket?.destroy(err);
+
+  return this;
+};
+
+function emitAbortNT(req) {
+  req.emit('abort');
+}
+
+function ondrain() {
+  const msg = this._httpMessage;
+  if (msg && !msg.finished && msg[kNeedDrain]) {
+    msg[kNeedDrain] = false;
+    msg.emit('drain');
+  }
+}
+
+function socketCloseListener() {
+  const socket = this;
+  const req = socket._httpMessage;
+  debug('HTTP socket close');
+
+  // NOTE: It's important to get parser here, because it could be freed by
+  // the `socketOnData`.
+  const parser = socket.parser;
+  const res = req.res;
+
+  req.destroyed = true;
+  if (res) {
+    // Socket closed before we emitted 'end' below.
+    if (!res.complete) {
+      res.destroy(new ConnResetException('aborted',
+                                         req[kError] ? { cause: req[kError] } : undefined));
+    }
+    req._closed = true;
+    req.emit('close');
+    if (!res.aborted && res.readable) {
+      res.push(null);
+    }
+  } else {
+    if (!req.socket._hadError) {
+      // This socket error fired before we started to
+      // receive a response. The error needs to
+      // fire on the request.
+      req.socket._hadError = true;
+      emitErrorEvent(req, new ConnResetException('socket hang up'));
+    }
+    req._closed = true;
+    req.emit('close');
+  }
+
+  // Too bad.  That output wasn't getting written.
+  // This is pretty terrible that it doesn't raise an error.
+  // Fixed better in v0.10
+  if (req.outputData)
+    req.outputData.length = 0;
+
+  if (parser) {
+    parser.finish();
+    freeParser(parser, req, socket);
+  }
+}
+
+function socketErrorListener(err) {
+  const socket = this;
+  const req = socket._httpMessage;
+  debug('SOCKET ERROR:', err.message, err.stack);
+
+  if (req) {
+    // For Safety. Some additional errors might fire later on
+    // and we need to make sure we don't double-fire the error event.
+    socket._hadError = true;
+    emitErrorEvent(req, err);
+    // Preserve the socket error for an incomplete response, including TLS
+    // errors that are emitted without setting socket.errored.
+    req[kError] ||= err;
+  }
+
+  const parser = socket.parser;
+  if (parser) {
+    parser.finish();
+    freeParser(parser, req, socket);
+  }
+
+  // Ensure that no further data will come out of the socket
+  socket.removeListener('data', socketOnData);
+  socket.removeListener('end', socketOnEnd);
+  socket.destroy();
+}
+
+function socketOnEnd() {
+  const socket = this;
+  const req = this._httpMessage;
+  const parser = this.parser;
+
+  if (!req.res && !req.socket._hadError) {
+    // If we don't have a response then we know that the socket
+    // ended prematurely and we need to emit an error on the request.
+    req.socket._hadError = true;
+    emitErrorEvent(req, new ConnResetException('socket hang up'));
+  }
+  if (parser) {
+    parser.finish();
+    freeParser(parser, req, socket);
+  }
+  socket.destroy();
+}
+
+function socketOnData(d) {
+  const socket = this;
+  const req = this._httpMessage;
+  const parser = this.parser;
+
+  assert(parser && parser.socket === socket);
+
+  const ret = parser.execute(d);
+  if (ret instanceof Error) {
+    prepareError(ret, parser, d);
+    debug('parse error', ret);
+    freeParser(parser, req, socket);
+    socket.removeListener('data', socketOnData);
+    socket.removeListener('end', socketOnEnd);
+    socket.destroy();
+    req.socket._hadError = true;
+    emitErrorEvent(req, ret);
+  } else if (parser.incoming?.upgrade) {
+    // Upgrade (if status code 101) or CONNECT
+    const bytesParsed = ret;
+    const res = parser.incoming;
+    req.res = res;
+
+    socket.removeListener('data', socketOnData);
+    socket.removeListener('end', socketOnEnd);
+    socket.removeListener('drain', ondrain);
+
+    if (req.timeoutCb) socket.removeListener('timeout', req.timeoutCb);
+    socket.removeListener('timeout', responseOnTimeout);
+
+    parser.finish();
+    freeParser(parser, req, socket);
+
+    const bodyHead = d.slice(bytesParsed, d.length);
+
+    const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
+    if (req.listenerCount(eventName) > 0) {
+      req.upgradeOrConnect = true;
+
+      // detach the socket
+      socket.emit('agentRemove');
+      socket.removeListener('close', socketCloseListener);
+      socket.removeListener('error', socketErrorListener);
+
+      socket._httpMessage = null;
+      socket.readableFlowing = null;
+
+      req.emit(eventName, res, socket, bodyHead);
+      req.destroyed = true;
+      req._closed = true;
+      req.emit('close');
+    } else {
+      // Requested Upgrade or used CONNECT method, but have no handler.
+      socket.destroy();
+    }
+  } else if (parser.incoming?.complete &&
+             // When the status code is informational (100, 102-199),
+             // the server will send a final response after this client
+             // sends a request body, so we must not free the parser.
+             // 101 (Switching Protocols) and all other status codes
+             // should be processed normally.
+             !statusIsInformational(parser.incoming.statusCode)) {
+    socket.removeListener('data', socketOnData);
+    socket.removeListener('end', socketOnEnd);
+    socket.removeListener('drain', ondrain);
+    freeParser(parser, req, socket);
+  }
+}
+
+function statusIsInformational(status) {
+  // 100 (Continue)    RFC7231 Section 6.2.1
+  // 102 (Processing)  RFC2518
+  // 103 (Early Hints) RFC8297
+  // 104-199 (Unassigned)
+  return (status < 200 && status >= 100 && status !== 101);
+}
+
+// client
+function parserOnIncomingClient(res, shouldKeepAlive) {
+  const socket = this.socket;
+  const req = socket._httpMessage;
+
+  debug('AGENT incoming response!');
+
+  if (req.res) {
+    // We already have a response object, this means the server
+    // sent a double response.
+    socket.destroy();
+    if (socket.parser) {
+      // https://github.com/nodejs/node/issues/60025
+      // Now, parser.incoming is pointed to the new IncomingMessage,
+      // we need to rewrite it to the first one and skip all the pending IncomingMessage
+      socket.parser.incoming = req.res;
+      socket.parser.incoming[kSkipPendingData] = true;
+    }
+    return 0;
+  }
+  req.res = res;
+
+  // Skip body and treat as Upgrade.
+  if (res.upgrade)
+    return 2;
+
+  // Responses to CONNECT request is handled as Upgrade.
+  const method = req.method;
+  if (method === 'CONNECT') {
+    res.upgrade = true;
+    return 2;  // Skip body and treat as Upgrade.
+  }
+
+  if (statusIsInformational(res.statusCode)) {
+    // Restart the parser, as this is a 1xx informational message.
+    req.res = null; // Clear res so that we don't hit double-responses.
+    // Maintain compatibility by sending 100-specific events
+    if (res.statusCode === 100) {
+      req.emit('continue');
+    }
+    // Send information events to all 1xx responses except 101 Upgrade.
+    req.emit('information', {
+      statusCode: res.statusCode,
+      statusMessage: res.statusMessage,
+      httpVersion: res.httpVersion,
+      httpVersionMajor: res.httpVersionMajor,
+      httpVersionMinor: res.httpVersionMinor,
+      headers: res.headers,
+      rawHeaders: res.rawHeaders,
+    });
+
+    return 1;  // Skip body but don't treat as Upgrade.
+  }
+
+  if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
+    // Server MUST respond with Connection:keep-alive for us to enable it.
+    // If we've been upgraded (via WebSockets) we also shouldn't try to
+    // keep the connection open.
+    req.shouldKeepAlive = false;
+  }
+
+  if (req[kClientRequestStatistics] && hasObserver('http')) {
+    stopPerf(req, kClientRequestStatistics, {
+      detail: {
+        res: {
+          statusCode: res.statusCode,
+          statusMessage: res.statusMessage,
+          headers: res.headers,
+        },
+      },
+    });
+  }
+  if (onClientResponseFinishChannel.hasSubscribers) {
+    onClientResponseFinishChannel.publish({
+      request: req,
+      response: res,
+    });
+  }
+  if (isTraceHTTPEnabled() && typeof req._traceEventId === 'number') {
+    traceEnd(HTTP_CLIENT_TRACE_EVENT_NAME, req._traceEventId, {
+      path: req.path,
+      statusCode: res.statusCode,
+    });
+  }
+  req.res = res;
+  res.req = req;
+
+  // Add our listener first, so that we guarantee socket cleanup
+  res.on('end', responseOnEnd);
+  req.on('finish', requestOnFinish);
+  socket.on('timeout', responseOnTimeout);
+
+  // If the user did not listen for the 'response' event, then they
+  // can't possibly read the data, so we ._dump() it into the void
+  // so that the socket doesn't hang there in a paused state.
+  if (req.aborted || !req.emit('response', res))
+    res._dump();
+
+  if (method === 'HEAD')
+    return 1;  // Skip body but don't treat as Upgrade.
+
+  if (res.statusCode === 304) {
+    res.complete = true;
+    return 1; // Skip body as there won't be any
+  }
+
+  return 0;  // No special treatment.
+}
+
+// client
+function responseKeepAlive(req) {
+  const socket = req.socket;
+
+  debug('AGENT socket keep-alive');
+  if (req.timeoutCb) {
+    socket.setTimeout(0, req.timeoutCb);
+    req.timeoutCb = null;
+  }
+  socket.removeListener('close', socketCloseListener);
+  socket.removeListener('error', socketErrorListener);
+  socket.removeListener('data', socketOnData);
+  socket.removeListener('end', socketOnEnd);
+
+  // TODO(ronag): Between here and emitFreeNT the socket
+  // has no 'error' handler.
+
+  // There are cases where _handle === null. Avoid those. Passing undefined to
+  // nextTick() will call getDefaultTriggerAsyncId() to retrieve the id.
+  const asyncId = socket._handle ? socket._handle.getAsyncId() : undefined;
+  // Mark this socket as available, AFTER user-added end
+  // handlers have a chance to run.
+  defaultTriggerAsyncIdScope(asyncId, process.nextTick, emitFreeNT, req);
+
+  req.destroyed = true;
+  if (req.res) {
+    // Detach socket from IncomingMessage to avoid destroying the freed
+    // socket in IncomingMessage.destroy().
+    req.res.socket = null;
+  }
+}
+
+function responseOnEnd() {
+  const req = this.req;
+  const socket = req.socket;
+
+  this[kDetachAbortSignal]();
+
+  if (socket) {
+    if (req.timeoutCb) socket.removeListener('timeout', emitRequestTimeout);
+    socket.removeListener('timeout', responseOnTimeout);
+  }
+
+  req._ended = true;
+
+  if (!req.shouldKeepAlive) {
+    if (socket.writable) {
+      debug('AGENT socket.destroySoon()');
+      if (typeof socket.destroySoon === 'function')
+        socket.destroySoon();
+      else
+        socket.end();
+    }
+    assert(!socket.writable);
+  } else if (req.writableFinished && !this.aborted) {
+    assert(req.finished);
+    // We can assume `req.finished` means all data has been written since:
+    // - `'responseOnEnd'` means we have been assigned a socket.
+    // - when we have a socket we write directly to it without buffering.
+    // - `req.finished` means `end()` has been called and no further data.
+    //   can be written
+    // In addition, `req.writableFinished` means all data written has been
+    // accepted by the kernel. (i.e. the `req.socket` is drained).Without
+    // this constraint, we may assign a non drained socket to a request.
+    responseKeepAlive(req);
+  }
+}
+
+function responseOnTimeout() {
+  const req = this._httpMessage;
+  if (!req) return;
+  const res = req.res;
+  if (!res) return;
+  res.emit('timeout');
+}
+
+// This function is necessary in the case where we receive the entire response
+// from the server before we finish sending out the request.
+function requestOnFinish() {
+  const req = this;
+
+  // If the response ends before this request finishes writing, `responseOnEnd()`
+  // already released the socket. When `finish` fires later, that socket may
+  // belong to a different request, so only call `responseKeepAlive()` when the
+  // original request is still alive (`!req.destroyed`).
+  if (req.shouldKeepAlive && req._ended && !req.destroyed)
+    responseKeepAlive(req);
+}
+
+function emitFreeNT(req) {
+  req._closed = true;
+  req.emit('close');
+  if (req.socket) {
+    req.socket.emit('free');
+  }
+}
+
+function tickOnSocket(req, socket) {
+  const parser = parsers.alloc();
+  req.socket = socket;
+  const lenientFlags = calculateLenientFlags(req.httpValidation, req.insecureHTTPParser);
+  // Propagate headers limit from request object to parser
+  if (typeof req.maxHeadersCount === 'number') {
+    parser.maxHeaderPairs = req.maxHeadersCount << 1;
+  }
+  parser.initialize(HTTPParser.RESPONSE,
+                    new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req),
+                    req.maxHeaderSize || 0,
+                    lenientFlags,
+                    undefined,
+                    parser.maxHeaderPairs);
+  parser.socket = socket;
+  parser.outgoing = req;
+  req.parser = parser;
+
+  socket.parser = parser;
+  socket._httpMessage = req;
+
+  parser.joinDuplicateHeaders = req.joinDuplicateHeaders;
+
+  parser.onIncoming = parserOnIncomingClient;
+  socket.on('data', socketOnData);
+  socket.on('end', socketOnEnd);
+  socket.on('close', socketCloseListener);
+  socket.on('drain', ondrain);
+
+  if (
+    req.timeout !== undefined ||
+    (req.agent?.options?.timeout)
+  ) {
+    listenSocketTimeout(req);
+  }
+  req.emit('socket', socket);
+}
+
+function emitRequestTimeout() {
+  const req = this._httpMessage;
+  if (req) {
+    req.emit('timeout');
+  }
+}
+
+function listenSocketTimeout(req) {
+  if (req.timeoutCb) {
+    return;
+  }
+  // Set timeoutCb so it will get cleaned up on request end.
+  req.timeoutCb = emitRequestTimeout;
+  // Delegate socket timeout event.
+  if (req.socket) {
+    req.socket.once('timeout', emitRequestTimeout);
+  } else {
+    req.on('socket', (socket) => {
+      socket.once('timeout', emitRequestTimeout);
+    });
+  }
+}
+
+ClientRequest.prototype.onSocket = function onSocket(socket, err) {
+  // Attach the error listener synchronously so that any errors emitted on
+  // the socket before onSocketNT runs (e.g. from a blocklist check or other
+  // next-tick error) are forwarded to the request and can be caught by the
+  // user's error handler. socketErrorListener requires socket._httpMessage
+  // to be set so we set it here too.
+  if (socket && !err) {
+    socket._httpMessage = this;
+    socket.on('error', socketErrorListener);
+  }
+  process.nextTick(onSocketNT, this, socket, err);
+};
+
+function onSocketNT(req, socket, err) {
+  if (req.destroyed || err) {
+    req.destroyed = true;
+
+    function _destroy(req, err) {
+      if (!req.aborted && !err) {
+        err = new ConnResetException('socket hang up');
+      }
+      // ERR_PROXY_TUNNEL is handled by the proxying logic.
+      // Skip if the error was already emitted by the early socketErrorListener.
+      if (err && err.code !== 'ERR_PROXY_TUNNEL' &&
+          !socket?._hadError) {
+        emitErrorEvent(req, err);
+      }
+      req._closed = true;
+      req.emit('close');
+    }
+
+    if (socket) {
+      if (!err && req.agent && !socket.destroyed) {
+        socket.emit('free');
+        socket.removeListener('error', socketErrorListener);
+      } else {
+        finished(socket.destroy(err || req[kError]), (er) => {
+          if (er?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
+            er = null;
+          }
+          _destroy(req, er || err);
+        });
+        return;
+      }
+    }
+
+    _destroy(req, err || req[kError]);
+  } else {
+    tickOnSocket(req, socket);
+    req._flush();
+  }
+}
+
+ClientRequest.prototype._deferToConnect = _deferToConnect;
+function _deferToConnect(method, arguments_) {
+  // This function is for calls that need to happen once the socket is
+  // assigned to this request and writable. It's an important promisy
+  // thing for all the socket calls that happen either now
+  // (when a socket is assigned) or in the future (when a socket gets
+  // assigned out of the pool and is eventually writable).
+
+  const callSocketMethod = () => {
+    if (method)
+      ReflectApply(this.socket[method], this.socket, arguments_);
+  };
+
+  const onSocket = () => {
+    if (this.socket.writable) {
+      callSocketMethod();
+    } else {
+      this.socket.once('connect', callSocketMethod);
+    }
+  };
+
+  if (!this.socket) {
+    this.once('socket', onSocket);
+  } else {
+    onSocket();
+  }
+}
+
+ClientRequest.prototype.setTimeout = function setTimeout(msecs, callback) {
+  if (this._ended) {
+    return this;
+  }
+
+  listenSocketTimeout(this);
+  msecs = getTimerDuration(msecs, 'msecs');
+  if (callback) this.once('timeout', callback);
+
+  if (this.socket) {
+    setSocketTimeout(this.socket, msecs);
+  } else {
+    this.once('socket', (sock) => setSocketTimeout(sock, msecs));
+  }
+
+  return this;
+};
+
+function setSocketTimeout(sock, msecs) {
+  if (sock.connecting) {
+    sock.once('connect', function() {
+      sock.setTimeout(msecs);
+    });
+  } else {
+    sock.setTimeout(msecs);
+  }
+}
+
+ClientRequest.prototype.setNoDelay = function setNoDelay(noDelay) {
+  this._deferToConnect('setNoDelay', [noDelay]);
+};
+
+ClientRequest.prototype.setSocketKeepAlive =
+    function setSocketKeepAlive(enable, initialDelay) {
+      this._deferToConnect('setKeepAlive', [enable, initialDelay]);
+    };
+
+ClientRequest.prototype.clearTimeout = function clearTimeout(cb) {
+  this.setTimeout(0, cb);
+};
+
+module.exports = {
+  ClientRequest,
+};
diff --git a/lib/internal/http/common.js b/lib/internal/http/common.js
new file mode 100644
index 00000000000..ed1defc49f4
--- /dev/null
+++ b/lib/internal/http/common.js
@@ -0,0 +1,345 @@
+// 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.
+
+'use strict';
+
+const {
+  MathMin,
+  Symbol,
+  Uint8Array,
+} = primordials;
+const { setImmediate } = require('timers');
+
+const { methods, allMethods, HTTPParser } = internalBinding('http_parser');
+const { getOptionValue } = require('internal/options');
+const insecureHTTPParser = getOptionValue('--insecure-http-parser');
+
+const FreeList = require('internal/freelist');
+const incoming = require('internal/http/incoming');
+const {
+  IncomingMessage,
+  readStart,
+  readStop,
+} = incoming;
+
+const kIncomingMessage = Symbol('IncomingMessage');
+const kSkipPendingData = Symbol('SkipPendingData');
+const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0;
+const kOnHeaders = HTTPParser.kOnHeaders | 0;
+const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
+const kOnBody = HTTPParser.kOnBody | 0;
+const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
+const kOnExecute = HTTPParser.kOnExecute | 0;
+const kOnTimeout = HTTPParser.kOnTimeout | 0;
+
+const MAX_HEADER_PAIRS = 2000;
+
+// Only called in the slow case where slow means
+// that the request headers were either fragmented
+// across multiple TCP packets or too large to be
+// processed in a single run. This method is also
+// called to process trailing HTTP headers.
+function parserOnHeaders(headers, url) {
+  // Once we exceeded headers limit - stop collecting them
+  const capacity = this.maxHeaderPairs - this._headers.length;
+  if (this.maxHeaderPairs <= 0 || capacity >= headers.length) {
+    this._headers.push(...headers);
+  } else if (capacity > 0) {
+    this._headers.push(...headers.slice(0, capacity));
+  }
+  this._url += url;
+}
+
+const HTTP_VERSION_1_1 = '1.1';
+
+// `headers` and `url` are set only if .onHeaders() has not been called for
+// this request.
+// `url` is not set for response parsers but that's not applicable here since
+// all our parsers are request parsers.
+function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
+                                 url, statusCode, statusMessage, upgrade,
+                                 shouldKeepAlive) {
+  const parser = this;
+  const { socket } = parser;
+
+  if (headers === undefined) {
+    headers = parser._headers;
+    parser._headers = [];
+  }
+
+  if (url === undefined) {
+    url = parser._url;
+    parser._url = '';
+  }
+
+  // Parser is also used by http client
+  const ParserIncomingMessage = (socket?.server?.[kIncomingMessage]) ||
+                                 IncomingMessage;
+
+  const incoming = parser.incoming = new ParserIncomingMessage(socket);
+  incoming.httpVersionMajor = versionMajor;
+  incoming.httpVersionMinor = versionMinor;
+  incoming.httpVersion = versionMajor === 1 && versionMinor === 1 ?
+    HTTP_VERSION_1_1 :
+    `${versionMajor}.${versionMinor}`;
+  incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders ||
+                                  parser.joinDuplicateHeaders;
+  incoming.url = url;
+  incoming.upgrade = upgrade;
+
+  let n = headers.length;
+
+  // If parser.maxHeaderPairs <= 0 assume that there's no limit.
+  if (parser.maxHeaderPairs > 0)
+    n = MathMin(n, parser.maxHeaderPairs);
+
+  incoming._addHeaderLines(headers, n);
+
+  if (typeof method === 'number') {
+    // server only
+    incoming.method = allMethods[method];
+  } else {
+    // client only
+    incoming.statusCode = statusCode;
+    incoming.statusMessage = statusMessage;
+  }
+
+  return parser.onIncoming(incoming, shouldKeepAlive);
+}
+
+function parserOnBody(b) {
+  const stream = this.incoming;
+
+  // If the stream has already been removed, then drop it.
+  if (stream === null || stream[kSkipPendingData])
+    return;
+
+  // Pretend this was the result of a stream._read call.
+  if (!stream._dumped) {
+    const ret = stream.push(b);
+    if (!ret)
+      readStop(this.socket);
+  }
+}
+
+function parserOnMessageComplete() {
+  const parser = this;
+  const stream = parser.incoming;
+
+  if (stream !== null && !stream[kSkipPendingData]) {
+    stream.complete = true;
+    // Emit any trailing headers.
+    const headers = parser._headers;
+    if (headers.length) {
+      stream._addHeaderLines(headers, headers.length);
+      parser._headers = [];
+      parser._url = '';
+    }
+
+    // For emit end event
+    stream.push(null);
+  }
+
+  // Force to read the next incoming message
+  readStart(parser.socket);
+}
+
+
+const parsers = new FreeList('parsers', 1000, function parsersCb() {
+  const parser = new HTTPParser();
+
+  cleanParser(parser);
+
+  parser[kOnHeaders] = parserOnHeaders;
+  parser[kOnHeadersComplete] = parserOnHeadersComplete;
+  parser[kOnBody] = parserOnBody;
+  parser[kOnMessageComplete] = parserOnMessageComplete;
+
+  return parser;
+});
+
+function closeParserInstance(parser) { parser.close(); }
+
+// Free the parser and also break any links that it
+// might have to any other things.
+// TODO: All parser data should be attached to a
+// single object, so that it can be easily cleaned
+// up by doing `parser.data = {}`, which should
+// be done in FreeList.free.  `parsers.free(parser)`
+// should be all that is needed.
+function freeParser(parser, req, socket) {
+  if (parser) {
+    if (parser._consumed)
+      parser.unconsume();
+    parser.remove();
+    cleanParser(parser);
+    if (parsers.free(parser) === false) {
+      // Make sure the parser's stack has unwound before deleting the
+      // corresponding C++ object through .close().
+      setImmediate(closeParserInstance, parser);
+    } else {
+      // Since the Parser destructor isn't going to run the destroy() callbacks
+      // it needs to be triggered manually.
+      parser.free();
+    }
+  }
+  if (req) {
+    req.parser = null;
+  }
+  if (socket) {
+    socket.parser = null;
+  }
+}
+
+// Character code ranges for valid HTTP tokens
+// Valid chars: ^_`a-zA-Z-0-9!#$%&'*+.|~
+// Based on RFC 7230 Section 3.2.6 token definition
+// See https://tools.ietf.org/html/rfc7230#section-3.2.6
+const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
+const validTokenChars = new Uint8Array([
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
+  0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32-47 (!"#$%&'()*+,-./)
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48-63 (0-9:;<=>?)
+  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64-79 (@A-O)
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80-95 (P-Z[\]^_)
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96-111 (`a-o)
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112-127 (p-z{|}~)
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128-143
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144-159
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-175
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176-191
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192-207
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208-223
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224-239
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 240-255
+]);
+
+/**
+ * Verifies that the given val is a valid HTTP token
+ * per the rules defined in RFC 7230
+ * See https://tools.ietf.org/html/rfc7230#section-3.2.6
+ * @param {string} val
+ * @returns {boolean}
+ */
+function checkIsHttpToken(val) {
+  if (val.length >= 10) {
+    return tokenRegExp.test(val);
+  }
+
+  if (val.length === 0) return false;
+
+  // Use lookup table for short strings, regex for longer ones
+  for (let i = 0; i < val.length; i++) {
+    if (!validTokenChars[val.charCodeAt(i)]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Strict header value regex per RFC 7230 (original/default behavior):
+// field-value = *( field-content / obs-fold )
+// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+// field-vchar = VCHAR / obs-text
+// This rejects control characters (0x00-0x1f except HTAB) and DEL (0x7f).
+const strictHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
+
+// Lenient header value regex per Fetch spec (https://fetch.spec.whatwg.org/#header-value):
+// - Must contain no 0x00 (NUL) or HTTP newline bytes (0x0a LF, 0x0d CR)
+// - Must be byte sequences (0x00-0xff), not arbitrary unicode
+// This allows most control characters except NUL, CR, and LF.
+// eslint-disable-next-line no-control-regex
+const lenientHeaderCharRegex = /[\x00\x0a\x0d]|[^\x00-\xff]/;
+
+/**
+ * True if val contains an invalid header value character.
+ * By default uses strict validation per RFC 7230.
+ * When lenient=true, uses relaxed validation per Fetch spec.
+ * @param {string} val
+ * @param {boolean} [lenient] - Use lenient validation (Fetch spec rules)
+ * @returns {boolean}
+ */
+function checkInvalidHeaderChar(val, lenient = false) {
+  const regex = lenient ? lenientHeaderCharRegex : strictHeaderCharRegex;
+  return regex.test(val);
+}
+
+function cleanParser(parser) {
+  parser._headers = [];
+  parser._url = '';
+  parser.socket = null;
+  parser.incoming = null;
+  parser.outgoing = null;
+  parser.maxHeaderPairs = MAX_HEADER_PAIRS;
+  parser[kOnMessageBegin] = null;
+  parser[kOnExecute] = null;
+  parser[kOnTimeout] = null;
+  parser._consumed = false;
+  parser.onIncoming = null;
+  parser.joinDuplicateHeaders = null;
+}
+
+function prepareError(err, parser, rawPacket) {
+  err.rawPacket = rawPacket || parser.getCurrentBuffer();
+  if (typeof err.reason === 'string')
+    err.message = `Parse Error: ${err.reason}`;
+}
+
+let warnedLenient = false;
+
+function isLenient() {
+  if (insecureHTTPParser && !warnedLenient) {
+    warnedLenient = true;
+    process.emitWarning('Using insecure HTTP parsing');
+  }
+  return insecureHTTPParser;
+}
+
+function calculateLenientFlags(httpValidation, insecureHTTPParserOption) {
+  if (httpValidation === 'strict') {
+    return HTTPParser.kLenientNone | 0;
+  } else if (httpValidation === 'relaxed') {
+    return HTTPParser.kLenientHeaderValueRelaxed | 0;
+  } else if (httpValidation === 'insecure') {
+    return HTTPParser.kLenientAll | 0;
+  }
+  const lenient = insecureHTTPParserOption === undefined ?
+    isLenient() : insecureHTTPParserOption;
+  return lenient ? HTTPParser.kLenientAll | 0 : HTTPParser.kLenientNone | 0;
+}
+
+module.exports = {
+  _checkInvalidHeaderChar: checkInvalidHeaderChar,
+  _checkIsHttpToken: checkIsHttpToken,
+  chunkExpression: /(?:^|\W)chunked(?:$|\W)/i,
+  continueExpression: /(?:^|\W)100-continue(?:$|\W)/i,
+  CRLF: '\r\n', // TODO: Deprecate this.
+  freeParser,
+  methods,
+  parsers,
+  kIncomingMessage,
+  HTTPParser,
+  isLenient,
+  calculateLenientFlags,
+  prepareError,
+  kSkipPendingData,
+};
diff --git a/lib/internal/http/incoming.js b/lib/internal/http/incoming.js
new file mode 100644
index 00000000000..0aa7d30580e
--- /dev/null
+++ b/lib/internal/http/incoming.js
@@ -0,0 +1,643 @@
+// 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.
+
+'use strict';
+
+const {
+  ObjectDefineProperty,
+  ObjectSetPrototypeOf,
+  Symbol,
+} = primordials;
+
+const { Readable, finished } = require('stream');
+const {
+  deprecateInstantiation,
+} = require('internal/util');
+
+const { AbortController } = require('internal/abort_controller');
+
+const kHeaders = Symbol('kHeaders');
+const kHeadersDistinct = Symbol('kHeadersDistinct');
+const kHeadersCount = Symbol('kHeadersCount');
+const kTrailers = Symbol('kTrailers');
+const kTrailersDistinct = Symbol('kTrailersDistinct');
+const kTrailersCount = Symbol('kTrailersCount');
+const kAbortController = Symbol('kAbortController');
+const kAbortSignalSocket = Symbol('kAbortSignalSocket');
+const kAbortSignalListener = Symbol('kAbortSignalListener');
+const kAbortSignalDetached = Symbol('kAbortSignalDetached');
+const kAttachAbortSignal = Symbol('kAttachAbortSignal');
+const kDetachAbortSignal = Symbol('kDetachAbortSignal');
+
+function readStart(socket) {
+  if (socket && !socket._paused && socket.readable)
+    socket.resume();
+}
+
+function readStop(socket) {
+  if (socket)
+    socket.pause();
+}
+
+/* Abstract base class for ServerRequest and ClientResponse. */
+function IncomingMessage(socket) {
+  if (!(this instanceof IncomingMessage)) {
+    return deprecateInstantiation(IncomingMessage, 'DEP0195', socket);
+  }
+
+  Readable.call(this);
+
+  if (socket) {
+    const hwm = socket.readableHighWaterMark;
+    if (this._readableState.highWaterMark !== hwm) {
+      this._readableState.highWaterMark = hwm;
+    }
+  }
+
+  this._readableState.readingMore = true;
+
+  this.socket = socket;
+
+  this.httpVersionMajor = null;
+  this.httpVersionMinor = null;
+  this.httpVersion = null;
+  this.complete = false;
+  this[kHeaders] = null;
+  this[kHeadersCount] = 0;
+  this.rawHeaders = [];
+  this[kTrailers] = null;
+  this[kTrailersCount] = 0;
+  this.rawTrailers = [];
+  this.joinDuplicateHeaders = false;
+  this.aborted = false;
+
+  this.upgrade = null;
+
+  // request (server) only
+  this.url = '';
+  this.method = null;
+
+  // response (client) only
+  this.statusCode = null;
+  this.statusMessage = null;
+  this.client = socket;
+
+  this._consuming = false;
+  // Flag for when we decide that this message cannot possibly be
+  // read by the user, so there's no point continuing to handle it.
+  this._dumped = false;
+  this[kAbortController] = null;
+  this[kAbortSignalSocket] = null;
+  this[kAbortSignalListener] = null;
+  this[kAbortSignalDetached] = false;
+}
+ObjectSetPrototypeOf(IncomingMessage.prototype, Readable.prototype);
+ObjectSetPrototypeOf(IncomingMessage, Readable);
+
+ObjectDefineProperty(IncomingMessage.prototype, 'connection', {
+  __proto__: null,
+  get: function() {
+    return this.socket;
+  },
+  set: function(val) {
+    this.socket = val;
+  },
+});
+
+ObjectDefineProperty(IncomingMessage.prototype, 'headers', {
+  __proto__: null,
+  get: function() {
+    if (!this[kHeaders]) {
+      this[kHeaders] = { __proto__: null };
+
+      const src = this.rawHeaders;
+      const dst = this[kHeaders];
+
+      for (let n = 0; n < this[kHeadersCount]; n += 2) {
+        this._addHeaderLine(src[n + 0], src[n + 1], dst);
+      }
+    }
+    return this[kHeaders];
+  },
+  set: function(val) {
+    this[kHeaders] = val;
+  },
+});
+
+ObjectDefineProperty(IncomingMessage.prototype, 'headersDistinct', {
+  __proto__: null,
+  get: function() {
+    if (!this[kHeadersDistinct]) {
+      this[kHeadersDistinct] = { __proto__: null };
+
+      const src = this.rawHeaders;
+      const dst = this[kHeadersDistinct];
+
+      for (let n = 0; n < this[kHeadersCount]; n += 2) {
+        this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
+      }
+    }
+    return this[kHeadersDistinct];
+  },
+  set: function(val) {
+    this[kHeadersDistinct] = val;
+  },
+});
+
+ObjectDefineProperty(IncomingMessage.prototype, 'trailers', {
+  __proto__: null,
+  get: function() {
+    if (!this[kTrailers]) {
+      this[kTrailers] = { __proto__: null };
+
+      const src = this.rawTrailers;
+      const dst = this[kTrailers];
+
+      for (let n = 0; n < this[kTrailersCount]; n += 2) {
+        this._addHeaderLine(src[n + 0], src[n + 1], dst);
+      }
+    }
+    return this[kTrailers];
+  },
+  set: function(val) {
+    this[kTrailers] = val;
+  },
+});
+
+ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', {
+  __proto__: null,
+  get: function() {
+    if (!this[kTrailersDistinct]) {
+      this[kTrailersDistinct] = { __proto__: null };
+
+      const src = this.rawTrailers;
+      const dst = this[kTrailersDistinct];
+
+      for (let n = 0; n < this[kTrailersCount]; n += 2) {
+        this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
+      }
+    }
+    return this[kTrailersDistinct];
+  },
+  set: function(val) {
+    this[kTrailersDistinct] = val;
+  },
+});
+
+ObjectDefineProperty(IncomingMessage.prototype, 'signal', {
+  __proto__: null,
+  configurable: true,
+  get: function() {
+    if (this[kAbortController] === null) {
+      const ac = new AbortController();
+      this[kAbortController] = ac;
+      if (this.destroyed && (!this.readableEnded || !this.complete)) {
+        ac.abort();
+      } else {
+        this[kAttachAbortSignal]();
+      }
+    }
+    return this[kAbortController].signal;
+  },
+});
+
+IncomingMessage.prototype[kAttachAbortSignal] = function() {
+  if (this[kAbortController].signal.aborted ||
+      this[kAbortSignalDetached] ||
+      this[kAbortSignalListener] !== null) {
+    return;
+  }
+
+  const socket = this.socket;
+  if (!socket) {
+    return;
+  }
+
+  if (socket.destroyed) {
+    abortSignal(this);
+    return;
+  }
+
+  this[kAbortSignalSocket] = socket;
+  this[kAbortSignalListener] = () => {
+    abortSignal(this);
+  };
+  socket.once('close', this[kAbortSignalListener]);
+};
+
+IncomingMessage.prototype[kDetachAbortSignal] = function() {
+  const socket = this[kAbortSignalSocket];
+  const listener = this[kAbortSignalListener];
+  this[kAbortSignalDetached] = true;
+  this[kAbortSignalSocket] = null;
+  this[kAbortSignalListener] = null;
+  if (socket !== null && listener !== null) {
+    socket.removeListener('close', listener);
+  }
+};
+
+IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
+  if (callback)
+    this.on('timeout', callback);
+  this.socket.setTimeout(msecs);
+  return this;
+};
+
+IncomingMessage.prototype._read = function _read() {
+  if (!this._consuming) {
+    this._readableState.readingMore = false;
+    this._consuming = true;
+  }
+
+  // We actually do almost nothing here, because the parserOnBody
+  // function fills up our internal buffer directly.  However, we
+  // do need to unpause the underlying socket so that it flows.
+  if (this.socket.readable)
+    readStart(this.socket);
+};
+
+// It's possible that the socket will be destroyed, and removed from
+// any messages, before ever calling this.  In that case, just skip
+// it, since something else is destroying this connection anyway.
+IncomingMessage.prototype._destroy = function _destroy(err, cb) {
+  if (!this.readableEnded || !this.complete) {
+    this.aborted = true;
+    this.emit('aborted');
+    abortSignal(this);
+  }
+
+  // If aborted and the underlying socket is not already destroyed,
+  // destroy it.
+  // We have to check if the socket is already destroyed because finished
+  // does not call the callback when this method is invoked from `_http_client`
+  // in `test/parallel/test-http-client-spurious-aborted.js`
+  if (this.socket && !this.socket.destroyed && this.aborted) {
+    this.socket.destroy(err);
+    const cleanup = finished(this.socket, (e) => {
+      if (e?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
+        e = null;
+      }
+      cleanup();
+      process.nextTick(onError, this, e || err, cb);
+    });
+  } else if (err == null && !this.aborted) {
+    // The message was received completely and is being destroyed cleanly:
+    // complete the destroy synchronously. 'close' is still emitted on a
+    // later tick by the stream machinery. The deferral below only exists
+    // so that 'error' listeners attached right after destroy(err) still
+    // receive the error, which cannot matter when there is no error.
+    cb();
+  } else {
+    process.nextTick(onError, this, err, cb);
+  }
+};
+
+function abortSignal(self) {
+  self[kDetachAbortSignal]();
+  if (self[kAbortController] !== null) {
+    self[kAbortController].abort();
+  }
+}
+
+IncomingMessage.prototype._addHeaderLines = _addHeaderLines;
+function _addHeaderLines(headers, n) {
+  if (headers?.length) {
+    let dest;
+    if (this.complete) {
+      this.rawTrailers = headers;
+      this[kTrailersCount] = n;
+      dest = this[kTrailers];
+    } else {
+      this.rawHeaders = headers;
+      this[kHeadersCount] = n;
+      dest = this[kHeaders];
+    }
+
+    if (dest) {
+      for (let i = 0; i < n; i += 2) {
+        this._addHeaderLine(headers[i], headers[i + 1], dest);
+      }
+    }
+  }
+}
+
+
+// How repeated instances of a header field are combined in the headers
+// object. Fields that are not known are joined with ', '.
+const kFirstWins = 0;
+const kJoinComma = 1;
+const kJoinSemicolon = 2;
+const kArray = 3;
+
+function knownField(name, merge) {
+  return { name, merge };
+}
+
+// Later values are dropped, unless joinDuplicateHeaders is set.
+const kAge = knownField('age', kFirstWins);
+const kHost = knownField('host', kFirstWins);
+const kFrom = knownField('from', kFirstWins);
+const kETag = knownField('etag', kFirstWins);
+const kServer = knownField('server', kFirstWins);
+const kReferer = knownField('referer', kFirstWins);
+const kExpires = knownField('expires', kFirstWins);
+const kLocation = knownField('location', kFirstWins);
+const kUserAgent = knownField('user-agent', kFirstWins);
+const kRetryAfter = knownField('retry-after', kFirstWins);
+const kContentType = knownField('content-type', kFirstWins);
+const kMaxForwards = knownField('max-forwards', kFirstWins);
+const kAuthorization = knownField('authorization', kFirstWins);
+const kLastModified = knownField('last-modified', kFirstWins);
+const kContentLength = knownField('content-length', kFirstWins);
+const kIfModifiedSince = knownField('if-modified-since', kFirstWins);
+const kProxyAuthorization = knownField('proxy-authorization', kFirstWins);
+const kIfUnmodifiedSince = knownField('if-unmodified-since', kFirstWins);
+
+// Values are joined with ', '.
+const kDate = knownField('date', kJoinComma);
+const kVary = knownField('vary', kJoinComma);
+const kOrigin = knownField('origin', kJoinComma);
+const kExpect = knownField('expect', kJoinComma);
+const kAccept = knownField('accept', kJoinComma);
+const kUpgrade = knownField('upgrade', kJoinComma);
+const kIfMatch = knownField('if-match', kJoinComma);
+const kConnection = knownField('connection', kJoinComma);
+const kCacheControl = knownField('cache-control', kJoinComma);
+const kIfNoneMatch = knownField('if-none-match', kJoinComma);
+const kAcceptEncoding = knownField('accept-encoding', kJoinComma);
+const kAcceptLanguage = knownField('accept-language', kJoinComma);
+const kXForwardedFor = knownField('x-forwarded-for', kJoinComma);
+const kContentEncoding = knownField('content-encoding', kJoinComma);
+const kXForwardedHost = knownField('x-forwarded-host', kJoinComma);
+const kTransferEncoding = knownField('transfer-encoding', kJoinComma);
+const kXForwardedProto = knownField('x-forwarded-proto', kJoinComma);
+
+// Values are joined with '; '.
+const kCookie = knownField('cookie', kJoinSemicolon);
+
+// Values are collected into an array.
+const kSetCookie = knownField('set-cookie', kArray);
+
+// Returns the descriptor of a known field, or the lowercased name of any other
+// field. The 'traditional cased' and lowercase spellings of known fields are
+// matched first to avoid calling toLowerCase() for them.
+// TODO: perhaps http_parser could be returning both raw and lowercased versions
+// of known header names to avoid us having to call toLowerCase() for those
+// headers.
+function matchKnownFields(field, lowercased) {
+  switch (field.length) {
+    case 3:
+      if (field === 'Age' || field === 'age') return kAge;
+      break;
+    case 4:
+      if (field === 'Host' || field === 'host') return kHost;
+      if (field === 'From' || field === 'from') return kFrom;
+      if (field === 'ETag' || field === 'etag') return kETag;
+      if (field === 'Date' || field === 'date') return kDate;
+      if (field === 'Vary' || field === 'vary') return kVary;
+      break;
+    case 6:
+      if (field === 'Server' || field === 'server') return kServer;
+      if (field === 'Cookie' || field === 'cookie') return kCookie;
+      if (field === 'Origin' || field === 'origin') return kOrigin;
+      if (field === 'Expect' || field === 'expect') return kExpect;
+      if (field === 'Accept' || field === 'accept') return kAccept;
+      break;
+    case 7:
+      if (field === 'Referer' || field === 'referer') return kReferer;
+      if (field === 'Expires' || field === 'expires') return kExpires;
+      if (field === 'Upgrade' || field === 'upgrade') return kUpgrade;
+      break;
+    case 8:
+      if (field === 'Location' || field === 'location')
+        return kLocation;
+      if (field === 'If-Match' || field === 'if-match')
+        return kIfMatch;
+      break;
+    case 10:
+      if (field === 'User-Agent' || field === 'user-agent')
+        return kUserAgent;
+      if (field === 'Set-Cookie' || field === 'set-cookie')
+        return kSetCookie;
+      if (field === 'Connection' || field === 'connection')
+        return kConnection;
+      break;
+    case 11:
+      if (field === 'Retry-After' || field === 'retry-after')
+        return kRetryAfter;
+      break;
+    case 12:
+      if (field === 'Content-Type' || field === 'content-type')
+        return kContentType;
+      if (field === 'Max-Forwards' || field === 'max-forwards')
+        return kMaxForwards;
+      break;
+    case 13:
+      if (field === 'Authorization' || field === 'authorization')
+        return kAuthorization;
+      if (field === 'Last-Modified' || field === 'last-modified')
+        return kLastModified;
+      if (field === 'Cache-Control' || field === 'cache-control')
+        return kCacheControl;
+      if (field === 'If-None-Match' || field === 'if-none-match')
+        return kIfNoneMatch;
+      break;
+    case 14:
+      if (field === 'Content-Length' || field === 'content-length')
+        return kContentLength;
+      break;
+    case 15:
+      if (field === 'Accept-Encoding' || field === 'accept-encoding')
+        return kAcceptEncoding;
+      if (field === 'Accept-Language' || field === 'accept-language')
+        return kAcceptLanguage;
+      if (field === 'X-Forwarded-For' || field === 'x-forwarded-for')
+        return kXForwardedFor;
+      break;
+    case 16:
+      if (field === 'Content-Encoding' || field === 'content-encoding')
+        return kContentEncoding;
+      if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host')
+        return kXForwardedHost;
+      break;
+    case 17:
+      if (field === 'If-Modified-Since' || field === 'if-modified-since')
+        return kIfModifiedSince;
+      if (field === 'Transfer-Encoding' || field === 'transfer-encoding')
+        return kTransferEncoding;
+      if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto')
+        return kXForwardedProto;
+      break;
+    case 19:
+      if (field === 'Proxy-Authorization' || field === 'proxy-authorization')
+        return kProxyAuthorization;
+      if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since')
+        return kIfUnmodifiedSince;
+      break;
+  }
+  if (lowercased) {
+    return field;
+  }
+  return matchKnownFields(field.toLowerCase(), true);
+}
+// Add the given (field, value) pair to the message
+//
+// Per RFC2616, section 4.2 it is acceptable to join multiple instances of the
+// same header with a ', ' if the header in question supports specification of
+// multiple values this way. The one exception to this is the Cookie header,
+// which has multiple values joined with a '; ' instead. If a header's values
+// cannot be joined in either of these ways, we declare the first instance the
+// winner and drop the second. Fields that are not known are always joined.
+IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
+function _addHeaderLine(field, value, dest) {
+  const known = matchKnownFields(field);
+  let merge = kJoinComma;
+  if (typeof known === 'string') {
+    field = known;
+  } else {
+    field = known.name;
+    merge = known.merge;
+  }
+  if (merge === kJoinComma || merge === kJoinSemicolon) {
+    // Make a delimited list
+    if (typeof dest[field] === 'string') {
+      dest[field] += (merge === kJoinComma ? ', ' : '; ') + value;
+    } else {
+      dest[field] = value;
+    }
+  } else if (merge === kArray) {
+    // Array header -- only Set-Cookie at the moment
+    if (dest['set-cookie'] !== undefined) {
+      dest['set-cookie'].push(value);
+    } else {
+      dest['set-cookie'] = [value];
+    }
+  } else if (this.joinDuplicateHeaders) {
+    // RFC 9110 https://www.rfc-editor.org/rfc/rfc9110#section-5.2
+    // https://github.com/nodejs/node/issues/45699
+    // allow authorization multiple fields
+    // Make a delimited list
+    if (dest[field] === undefined) {
+      dest[field] = value;
+    } else {
+      dest[field] += ', ' + value;
+    }
+  } else if (dest[field] === undefined) {
+    // Drop duplicates
+    dest[field] = value;
+  }
+}
+
+IncomingMessage.prototype._addHeaderLineDistinct = _addHeaderLineDistinct;
+function _addHeaderLineDistinct(field, value, dest) {
+  field = field.toLowerCase();
+  if (!dest[field]) {
+    dest[field] = [value];
+  } else {
+    dest[field].push(value);
+  }
+}
+
+IncomingMessage.prototype._dumpAndCloseReadable = function _dumpAndCloseReadable() {
+  this._dumped = true;
+  this._readableState.ended = true;
+  this._readableState.endEmitted = true;
+  this._readableState.destroyed = true;
+  this._readableState.closed = true;
+  this._readableState.closeEmitted = true;
+};
+
+
+// Call this instead of resume() if we want to just
+// dump all the data to /dev/null
+IncomingMessage.prototype._dump = function _dump() {
+  if (!this._dumped) {
+    this._dumped = true;
+    // If there is buffered data, it may trigger 'data' events.
+    // Remove 'data' event listeners explicitly.
+    this.removeAllListeners('data');
+    if (this.complete && !this.destroyed && this.readableLength === 0) {
+      // The message was fully received and never read: there is nothing to
+      // pull off the wire. Go straight to the 'end' emission instead of
+      // paying for the resume() and flow() scheduling machinery.
+      this.read(0);
+    } else {
+      this.resume();
+    }
+  }
+};
+
+// Case-insensitive ASCII compare against an already-lowercased name.
+// Avoids allocating a lowercased copy of every header name.
+function asciiEqualIgnoreCase(a, lower) {
+  const len = lower.length;
+  if (a.length !== len)
+    return false;
+  if (a === lower)
+    return true;
+  for (let i = 0; i < len; i++) {
+    let c = a.charCodeAt(i);
+    if (c >= 65 && c <= 90)
+      c += 32;
+    if (c !== lower.charCodeAt(i))
+      return false;
+  }
+  return true;
+}
+
+function getRawHeader(msg, lowerName, joinDuplicates) {
+  const rawHeaders = msg.rawHeaders;
+  const count = msg[kHeadersCount];
+  let result;
+  for (let i = 0; i < count; i += 2) {
+    if (!asciiEqualIgnoreCase(rawHeaders[i], lowerName))
+      continue;
+    const value = rawHeaders[i + 1];
+    if (result === undefined) {
+      result = value;
+      if (!joinDuplicates)
+        return result;
+    } else {
+      result += ', ' + value;
+    }
+  }
+  return result;
+}
+
+function onError(self, error, cb) {
+  // This is to keep backward compatible behavior.
+  // An error is emitted only if there are listeners attached to the event.
+  if (self.listenerCount('error') === 0) {
+    cb();
+  } else {
+    cb(error);
+  }
+}
+
+module.exports = {
+  IncomingMessage,
+  kDetachAbortSignal,
+  kHeadersCount,
+  asciiEqualIgnoreCase,
+  getRawHeader,
+  readStart,
+  readStop,
+};
diff --git a/lib/internal/http/outgoing.js b/lib/internal/http/outgoing.js
new file mode 100644
index 00000000000..ad0ad6dc372
--- /dev/null
+++ b/lib/internal/http/outgoing.js
@@ -0,0 +1,1489 @@
+// 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.
+
+'use strict';
+
+const {
+  ArrayIsArray,
+  MathFloor,
+  ObjectDefineProperty,
+  ObjectHasOwn,
+  ObjectKeys,
+  ObjectSetPrototypeOf,
+  ObjectValues,
+  SafeSet,
+  Symbol,
+} = primordials;
+
+const { getDefaultHighWaterMark } = require('internal/streams/state');
+const assert = require('internal/assert');
+const EE = require('events');
+const Stream = require('stream');
+const { kOutHeaders, utcDateHeader, kNeedDrain } = require('internal/http');
+const { Buffer } = require('buffer');
+const {
+  _checkIsHttpToken: checkIsHttpToken,
+  _checkInvalidHeaderChar: checkInvalidHeaderChar,
+  chunkExpression: RE_TE_CHUNKED,
+  isLenient,
+} = require('internal/http/common');
+const {
+  defaultTriggerAsyncIdScope,
+  symbols: { async_id_symbol },
+} = require('internal/async_hooks');
+const {
+  codes: {
+    ERR_HTTP_BODY_NOT_ALLOWED,
+    ERR_HTTP_CONTENT_LENGTH_MISMATCH,
+    ERR_HTTP_HEADERS_SENT,
+    ERR_HTTP_INVALID_HEADER_VALUE,
+    ERR_HTTP_TRAILER_INVALID,
+    ERR_INVALID_ARG_TYPE,
+    ERR_INVALID_ARG_VALUE,
+    ERR_INVALID_CHAR,
+    ERR_INVALID_HTTP_TOKEN,
+    ERR_METHOD_NOT_IMPLEMENTED,
+    ERR_STREAM_ALREADY_FINISHED,
+    ERR_STREAM_CANNOT_PIPE,
+    ERR_STREAM_DESTROYED,
+    ERR_STREAM_NULL_VALUES,
+    ERR_STREAM_WRITE_AFTER_END,
+    ERR_UNKNOWN_ENCODING,
+  },
+  hideStackFrames,
+} = require('internal/errors');
+const {
+  validateObject,
+  validateOneOf,
+  validateString,
+} = require('internal/validators');
+const {
+  assignFunctionName,
+  deprecateInstantiation,
+} = require('internal/util');
+const { isUint8Array } = require('internal/util/types');
+
+let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
+  debug = fn;
+});
+
+const kCorked = Symbol('corked');
+
+// Container for the headers set through `setHeader()`, keyed by the
+// lower-cased header name. It has no enumerable or inherited properties, like
+// a `{ __proto__: null }` literal, but instances are fast-mode objects: V8
+// creates null-prototype literals in dictionary mode, which costs a hash table
+// allocation per response and slower property access for every header.
+class OutgoingHeaders {}
+ObjectSetPrototypeOf(OutgoingHeaders.prototype, null);
+delete OutgoingHeaders.prototype.constructor;
+const kSocket = Symbol('kSocket');
+const kChunkedBuffer = Symbol('kChunkedBuffer');
+const kChunkedLength = Symbol('kChunkedLength');
+const kUniqueHeaders = Symbol('kUniqueHeaders');
+const kBytesWritten = Symbol('kBytesWritten');
+const kErrored = Symbol('errored');
+const kLenientCache = Symbol('kLenientCache');
+
+let keepAliveTimeoutCache = -1;
+let keepAliveMaxCache = -1;
+let keepAliveHeaderCache = '';
+const kWritableFinished = Symbol('kWritableFinished');
+const kEndCallbacks = Symbol('kEndCallbacks');
+const kFlushError = Symbol('kFlushError');
+const kHighWaterMark = Symbol('kHighWaterMark');
+const kRejectNonStandardBodyWrites = Symbol('kRejectNonStandardBodyWrites');
+
+const nop = () => {};
+
+const RE_CONN_CLOSE = /(?:^|\W)close(?:$|\W)/i;
+
+// isCookieField performs a case-insensitive comparison of a provided string
+// against the word "cookie." As of V8 6.6 this is faster than handrolling or
+// using a case-insensitive RegExp.
+function isCookieField(s) {
+  return s.length === 6 && s.toLowerCase() === 'cookie';
+}
+
+function isContentDispositionField(s) {
+  return s.length === 19 && s.toLowerCase() === 'content-disposition';
+}
+
+function OutgoingMessage(options) {
+  if (!(this instanceof OutgoingMessage)) {
+    return deprecateInstantiation(OutgoingMessage, 'DEP0195', options);
+  }
+
+  // Preset the listener table with the events that are commonly listened
+  // for, mirroring streams. The presence of the properties keeps `_events`
+  // in fast mode: the first `on()` call would otherwise have to allocate a
+  // dictionary for the `{ __proto__: null }` object created by EventEmitter.
+  this._events ??= {
+    close: undefined,
+    error: undefined,
+    finish: undefined,
+    prefinish: undefined,
+    drain: undefined,
+  };
+
+  Stream.call(this);
+
+  // Queue that holds all currently pending data, until the response will be
+  // assigned to the socket (until it will its turn in the HTTP pipeline).
+  this.outputData = [];
+
+  // `outputSize` is an approximate measure of how much data is queued on this
+  // response. `_onPendingData` will be invoked to update similar global
+  // per-connection counter. That counter will be used to pause/unpause the
+  // TCP socket and HTTP Parser and thus handle the backpressure.
+  this.outputSize = 0;
+
+  this.writable = true;
+  this.destroyed = false;
+
+  this._last = false;
+  this.chunkedEncoding = false;
+  this.shouldKeepAlive = true;
+  this.maxRequestsOnConnectionReached = false;
+  this._defaultKeepAlive = true;
+  this.useChunkedEncodingByDefault = true;
+  this.sendDate = false;
+  this._removedConnection = false;
+  this._removedContLen = false;
+  this._removedTE = false;
+
+  this.strictContentLength = false;
+  this[kBytesWritten] = 0;
+  this._contentLength = null;
+  this._hasBody = true;
+  this._trailer = '';
+  this[kNeedDrain] = false;
+
+  this.finished = false;
+  this._headerSent = false;
+  this[kCorked] = 0;
+  this[kChunkedBuffer] = [];
+  this[kChunkedLength] = 0;
+  this._closed = false;
+
+  this[kSocket] = null;
+  this._header = null;
+  this[kOutHeaders] = null;
+
+  this._keepAliveTimeout = 0;
+
+  this._onPendingData = nop;
+
+  this[kErrored] = null;
+  this[kWritableFinished] = false;
+  this[kEndCallbacks] = null;
+  this[kFlushError] = null;
+  this[kHighWaterMark] = options?.highWaterMark ?? getDefaultHighWaterMark();
+  this[kRejectNonStandardBodyWrites] = options?.rejectNonStandardBodyWrites ?? false;
+  this[kLenientCache] = null;
+}
+ObjectSetPrototypeOf(OutgoingMessage.prototype, Stream.prototype);
+ObjectSetPrototypeOf(OutgoingMessage, Stream);
+
+// Check if lenient header validation should be used.
+// For ClientRequest: checks this.httpValidation or this.insecureHTTPParser
+// For ServerResponse: checks the server's httpValidation or insecureHTTPParser
+// Falls back to global --insecure-http-parser flag.
+OutgoingMessage.prototype._isLenientHeaderValidation = function() {
+  // The underlying options cannot change during the lifetime of a message:
+  // compute the lookup chain only once per message.
+  this[kLenientCache] ??= isLenientHeaderValidation(this);
+  return this[kLenientCache];
+};
+
+function isLenientHeaderValidation(msg) {
+  // New httpValidation option takes priority (ClientRequest case)
+  if (msg.httpValidation !== undefined) {
+    return msg.httpValidation !== 'strict';
+  }
+  // ServerResponse: check server's httpValidation option
+  const serverHttpValidation = msg.req?.socket?.server?.httpValidation;
+  if (serverHttpValidation !== undefined) {
+    return serverHttpValidation !== 'strict';
+  }
+  // Legacy insecureHTTPParser - ClientRequest has it directly
+  if (typeof msg.insecureHTTPParser === 'boolean') {
+    return msg.insecureHTTPParser;
+  }
+  // ServerResponse can access via req.socket.server
+  const serverOption = msg.req?.socket?.server?.insecureHTTPParser;
+  if (typeof serverOption === 'boolean') {
+    return serverOption;
+  }
+  // Fall back to global option
+  return isLenient();
+}
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'errored', {
+  __proto__: null,
+  get() {
+    return this[kErrored];
+  },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'closed', {
+  __proto__: null,
+  get() {
+    return this._closed;
+  },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'writableFinished', {
+  __proto__: null,
+  get() {
+    return this[kWritableFinished];
+  },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'writableObjectMode', {
+  __proto__: null,
+  get() {
+    return false;
+  },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'writableLength', {
+  __proto__: null,
+  get() {
+    let bufferedLength = this[kChunkedLength];
+    if (bufferedLength !== 0) {
+      bufferedLength += bufferedLength.toString(16).length + 4 +
+        (!this._headerSent && this._header !== null ? this._header.length : 0);
+    }
+    return this.outputSize + bufferedLength + (this[kSocket] ? this[kSocket].writableLength : 0);
+  },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'writableHighWaterMark', {
+  __proto__: null,
+  get() {
+    return this[kSocket] ? this[kSocket].writableHighWaterMark : this[kHighWaterMark];
+  },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'writableCorked', {
+  __proto__: null,
+  get() {
+    return this[kCorked];
+  },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'connection', {
+  __proto__: null,
+  get: function() {
+    return this[kSocket];
+  },
+  set: function(val) {
+    this.socket = val;
+  },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'socket', {
+  __proto__: null,
+  get: function() {
+    return this[kSocket];
+  },
+  set: function(val) {
+    for (let n = 0; n < this[kCorked]; n++) {
+      val?.cork();
+      this[kSocket]?.uncork();
+    }
+    this[kSocket] = val;
+  },
+});
+
+OutgoingMessage.prototype._renderHeaders = function _renderHeaders() {
+  if (this._header) {
+    throw new ERR_HTTP_HEADERS_SENT('render');
+  }
+
+  const headersMap = this[kOutHeaders];
+  const headers = {};
+
+  if (headersMap !== null) {
+    const keys = ObjectKeys(headersMap);
+    // Retain for(;;) loop for performance reasons
+    // Refs: https://github.com/nodejs/node/pull/30958
+    for (let i = 0, l = keys.length; i < l; i++) {
+      const key = keys[i];
+      headers[headersMap[key][0]] = headersMap[key][1];
+    }
+  }
+  return headers;
+};
+
+OutgoingMessage.prototype.cork = function cork() {
+  this[kCorked]++;
+  if (this[kSocket]) {
+    this[kSocket].cork();
+  }
+};
+
+OutgoingMessage.prototype.uncork = function uncork() {
+  this[kCorked]--;
+  const socket = this[kSocket];
+  try {
+    if (this[kCorked] || this[kChunkedBuffer].length === 0) {
+      return;
+    }
+
+    const len = this[kChunkedLength];
+    const buf = this[kChunkedBuffer];
+    if (this.destroyed || socket?.destroyed) {
+      const error = this[kErrored] || socket?._writableState?.errored || new ERR_STREAM_DESTROYED('write');
+      for (let n = 2; n < buf.length; n += 3) {
+        if (buf[n] !== nop) {
+          process.nextTick(buf[n], error);
+        }
+      }
+      buf.length = 0;
+      this[kChunkedLength] = 0;
+      return;
+    }
+    assert(this.chunkedEncoding);
+
+    const callbacks = [];
+    this._send(len.toString(16), 'latin1', null);
+    this._send(crlf_buf, null, null);
+    for (let n = 0; n < buf.length; n += 3) {
+      this._send(buf[n + 0], buf[n + 1], null);
+      callbacks.push(buf[n + 2]);
+    }
+    this._send(crlf_buf, null, (err) => {
+      for (const callback of callbacks) {
+        callback(err);
+      }
+    });
+
+    this[kChunkedBuffer].length = 0;
+    this[kChunkedLength] = 0;
+  } finally {
+    socket?.uncork();
+  }
+
+  // If we had a pending drain and flushed all data, emit the drain event.
+  if (this[kNeedDrain] && this.writableLength === 0) {
+    this[kNeedDrain] = false;
+    this.emit('drain');
+  }
+};
+
+OutgoingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
+
+  if (callback) {
+    this.on('timeout', callback);
+  }
+
+  if (!this[kSocket]) {
+    this.once('socket', function socketSetTimeoutOnConnect(socket) {
+      socket.setTimeout(msecs);
+    });
+  } else {
+    this[kSocket].setTimeout(msecs);
+  }
+  return this;
+};
+
+
+// It's possible that the socket will be destroyed, and removed from
+// any messages, before ever calling this.  In that case, just skip
+// it, since something else is destroying this connection anyway.
+OutgoingMessage.prototype.destroy = function destroy(error) {
+  if (this.destroyed) {
+    return this;
+  }
+  this.destroyed = true;
+
+  this[kErrored] = error;
+  if (this[kChunkedBuffer].length !== 0) {
+    this[kCorked] = 1;
+    this.uncork();
+  }
+
+  if (this[kSocket]) {
+    this[kSocket].destroy(error);
+  } else {
+    process.nextTick(emitDestroyNT, this);
+  }
+
+  return this;
+};
+
+function emitDestroyNT(self) {
+  if (!self._closed) {
+    self._closed = true;
+    self.emit('close');
+  }
+}
+
+
+// This abstract either writing directly to the socket or buffering it.
+OutgoingMessage.prototype._send = function _send(data, encoding, callback, byteLength) {
+  // This is a shameful hack to get the headers and first body chunk onto
+  // the same packet. Future versions of Node are going to take care of
+  // this at a lower level and in a more general way.
+  if (!this._headerSent && this._header !== null) {
+    // `this._header` can be null if OutgoingMessage is used without a proper Socket
+    // See: /test/parallel/test-http-outgoing-message-inheritance.js
+    if (typeof data === 'string' &&
+        (encoding === 'utf8' || encoding === 'latin1' || !encoding)) {
+      data = this._header + data;
+    } else {
+      const header = this._header;
+      this.outputData.unshift({
+        data: header,
+        encoding: 'latin1',
+        callback: null,
+      });
+      this.outputSize += header.length;
+      this._onPendingData(header.length);
+    }
+    this._headerSent = true;
+  }
+  return this._writeRaw(data, encoding, callback, byteLength);
+};
+
+OutgoingMessage.prototype._writeRaw = _writeRaw;
+function _writeRaw(data, encoding, callback, size) {
+  const conn = this[kSocket];
+  if (conn?.destroyed) {
+    // The socket was destroyed. If we're still trying to write to it,
+    // then we haven't gotten the 'close' event yet.
+    return false;
+  }
+
+  if (typeof encoding === 'function') {
+    callback = encoding;
+    encoding = null;
+  }
+
+  if (conn && conn._httpMessage === this && conn.writable) {
+    // There might be pending data in the this.output buffer.
+    if (this.outputData.length) {
+      this._flushOutput(conn);
+    }
+    // Directly write to socket.
+    return conn.write(data, encoding, callback);
+  }
+  // Buffer, as long as we're not destroyed.
+  this.outputData.push({ data, encoding, callback });
+  this.outputSize += data.length;
+  this._onPendingData(data.length);
+  return this.outputSize < this[kHighWaterMark];
+}
+
+
+OutgoingMessage.prototype._storeHeader = _storeHeader;
+function _storeHeader(firstLine, headers) {
+  // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
+  // in the case of response it is: 'HTTP/1.1 200 OK\r\n'
+  const state = {
+    connection: false,
+    contLen: false,
+    te: false,
+    date: false,
+    expect: false,
+    trailer: false,
+    header: firstLine,
+  };
+  const lenient = this._isLenientHeaderValidation();
+
+  if (headers) {
+    if (headers === this[kOutHeaders]) {
+      for (const key in headers) {
+        const entry = headers[key];
+        processHeader(this, state, entry[0], entry[1], false, lenient);
+      }
+    } else if (ArrayIsArray(headers)) {
+      if (headers.length && ArrayIsArray(headers[0])) {
+        for (let i = 0; i < headers.length; i++) {
+          const entry = headers[i];
+          processHeader(this, state, entry[0], entry[1], true, lenient);
+        }
+      } else {
+        if (headers.length % 2 !== 0) {
+          throw new ERR_INVALID_ARG_VALUE('headers', headers);
+        }
+
+        for (let n = 0; n < headers.length; n += 2) {
+          processHeader(this, state, headers[n + 0], headers[n + 1], true, lenient);
+        }
+      }
+    } else {
+      for (const key in headers) {
+        if (ObjectHasOwn(headers, key)) {
+          processHeader(this, state, key, headers[key], true, lenient);
+        }
+      }
+    }
+  }
+
+  let { header } = state;
+
+  // Date header
+  if (this.sendDate && !state.date) {
+    header += utcDateHeader();
+  }
+
+  // Force the connection to close when the response is a 204 No Content or
+  // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked"
+  // header.
+  //
+  // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but
+  // node.js used to send out a zero chunk anyway to accommodate clients
+  // that don't have special handling for those responses.
+  //
+  // It was pointed out that this might confuse reverse proxies to the point
+  // of creating security liabilities, so suppress the zero chunk and force
+  // the connection to close.
+  if (this.chunkedEncoding && (this.statusCode === 204 ||
+                               this.statusCode === 304)) {
+    debug(this.statusCode + ' response should not use chunked encoding,' +
+          ' closing connection.');
+    this.chunkedEncoding = false;
+    this.shouldKeepAlive = false;
+  }
+
+  // keep-alive logic
+  if (this._removedConnection) {
+    // shouldKeepAlive is generally true for HTTP/1.1. In that common case,
+    // even if the connection header isn't sent, we still persist by default.
+    this._last = !this.shouldKeepAlive;
+  } else if (!state.connection) {
+    const shouldSendKeepAlive = this.shouldKeepAlive &&
+        (state.contLen || this.useChunkedEncodingByDefault || this.agent);
+    if (shouldSendKeepAlive && this.maxRequestsOnConnectionReached) {
+      header += 'Connection: close\r\n';
+    } else if (shouldSendKeepAlive) {
+      if (this._keepAliveTimeout && this._defaultKeepAlive) {
+        // The keep-alive header lines are identical for every response of a
+        // given server: cache the last rendered value.
+        const timeout = this._keepAliveTimeout;
+        const max = ~~this._maxRequestsPerSocket;
+        if (timeout !== keepAliveTimeoutCache || max !== keepAliveMaxCache) {
+          keepAliveTimeoutCache = timeout;
+          keepAliveMaxCache = max;
+          keepAliveHeaderCache = 'Connection: keep-alive\r\n' +
+            `Keep-Alive: timeout=${MathFloor(timeout / 1000)}` +
+            (max > 0 ? `, max=${max}` : '') + '\r\n';
+        }
+        header += keepAliveHeaderCache;
+      } else {
+        header += 'Connection: keep-alive\r\n';
+      }
+    } else {
+      this._last = true;
+      header += 'Connection: close\r\n';
+    }
+  }
+
+  if (!state.contLen && !state.te) {
+    if (!this._hasBody) {
+      // Make sure we don't end the 0\r\n\r\n at the end of the message.
+      this.chunkedEncoding = false;
+    } else if (!this.useChunkedEncodingByDefault) {
+      this._last = true;
+    } else if (!state.trailer &&
+               !this._removedContLen &&
+               typeof this._contentLength === 'number') {
+      header += 'Content-Length: ' + this._contentLength + '\r\n';
+    } else if (!this._removedTE) {
+      header += 'Transfer-Encoding: chunked\r\n';
+      this.chunkedEncoding = true;
+    } else {
+      // We should only be able to get here if both Content-Length and
+      // Transfer-Encoding are removed by the user.
+      // See: test/parallel/test-http-remove-header-stays-removed.js
+      debug('Both Content-Length and Transfer-Encoding are removed');
+
+      // We can't keep alive in this case, because with no header info the body
+      // is defined as all data until the connection is closed.
+      this._last = true;
+    }
+  }
+
+  // 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 (this.chunkedEncoding !== true && state.trailer) {
+    throw new ERR_HTTP_TRAILER_INVALID();
+  }
+
+  this._header = header + '\r\n';
+  this._headerSent = false;
+
+  // Wait until the first body chunk, or close(), is sent to flush,
+  // UNLESS we're sending Expect: 100-continue.
+  if (state.expect) this._send('');
+}
+
+function processHeader(self, state, key, value, validate, lenient) {
+  if (validate)
+    validateHeaderName(key);
+
+  // If key is content-disposition and there is content-length
+  // encode the value in latin1
+  // https://www.rfc-editor.org/rfc/rfc6266#section-4.3
+  // Refs: https://github.com/nodejs/node/pull/46528
+  if (isContentDispositionField(key) && self._contentLength) {
+    // The value could be an array here
+    if (ArrayIsArray(value)) {
+      for (let i = 0; i < value.length; i++) {
+        value[i] = Buffer.from(value[i], 'latin1');
+      }
+    } else {
+      value = Buffer.from(value, 'latin1');
+    }
+  }
+
+  if (ArrayIsArray(value)) {
+    if (
+      (value.length < 2 || !isCookieField(key)) &&
+      (!self[kUniqueHeaders] || !self[kUniqueHeaders].has(key.toLowerCase()))
+    ) {
+      // Retain for(;;) loop for performance reasons
+      // Refs: https://github.com/nodejs/node/pull/30958
+      for (let i = 0; i < value.length; i++)
+        storeHeader(self, state, key, value[i], validate, lenient);
+      return;
+    }
+    value = value.join('; ');
+  }
+  storeHeader(self, state, key, value, validate, lenient);
+}
+
+function storeHeader(self, state, key, value, validate, lenient) {
+  if (validate)
+    validateHeaderValue(key, value, lenient);
+  state.header += key + ': ' + value + '\r\n';
+  matchHeader(self, state, key, value);
+}
+
+function lowerOutgoingHeaderName(field) {
+  switch (field) {
+    case 'Connection':
+    case 'connection':
+      return 'connection';
+    case 'Content-Length':
+    case 'content-length':
+      return 'content-length';
+    case 'Transfer-Encoding':
+    case 'transfer-encoding':
+      return 'transfer-encoding';
+    case 'Date':
+    case 'date':
+      return 'date';
+    case 'Expect':
+    case 'expect':
+      return 'expect';
+    case 'Trailer':
+    case 'trailer':
+      return 'trailer';
+    case 'Keep-Alive':
+    case 'keep-alive':
+      return 'keep-alive';
+    default:
+      return field.toLowerCase();
+  }
+}
+
+function matchHeader(self, state, field, value) {
+  if (field.length < 4 || field.length > 17)
+    return;
+  field = lowerOutgoingHeaderName(field);
+  switch (field) {
+    case 'connection':
+      state.connection = true;
+      self._removedConnection = false;
+      if (RE_CONN_CLOSE.test(value))
+        self._last = true;
+      else
+        self.shouldKeepAlive = true;
+      break;
+    case 'transfer-encoding':
+      state.te = true;
+      self._removedTE = false;
+      if (RE_TE_CHUNKED.test(value))
+        self.chunkedEncoding = true;
+      break;
+    case 'content-length':
+      state.contLen = true;
+      self._contentLength = +value;
+      self._removedContLen = false;
+      break;
+    case 'date':
+    case 'expect':
+    case 'trailer':
+      state[field] = true;
+      break;
+    case 'keep-alive':
+      self._defaultKeepAlive = false;
+      break;
+  }
+}
+
+const validateHeaderName = assignFunctionName('validateHeaderName', hideStackFrames((name, label) => {
+  if (!isValidHeaderName(name)) {
+    throw new ERR_INVALID_HTTP_TOKEN.HideStackFramesError(label || 'Header name', name);
+  }
+}));
+
+/**
+ * Non-throwing counterpart of `validateHeaderName()`.
+ * @param {any} name
+ * @returns {boolean}
+ */
+function isValidHeaderName(name) {
+  return typeof name === 'string' && checkIsHttpToken(name);
+}
+
+const kHttpValidationValues = ['strict', 'relaxed'];
+
+/**
+ * Non-throwing counterpart of `validateHeaderValue()`.
+ * @param {any} value
+ * @param {{ httpValidation?: 'strict' | 'relaxed' }} [options]
+ * @returns {boolean}
+ */
+function isValidHeaderValue(value, options) {
+  let lenient = false;
+  if (options !== undefined) {
+    validateObject(options, 'options');
+    const { httpValidation } = options;
+    if (httpValidation === 'relaxed') {
+      lenient = true;
+    } else if (httpValidation !== undefined && httpValidation !== 'strict') {
+      validateOneOf(httpValidation, 'options.httpValidation', kHttpValidationValues);
+    }
+  }
+  if (value === undefined || typeof value === 'symbol') {
+    return false;
+  }
+  return !checkInvalidHeaderChar(value, lenient);
+}
+
+const validateHeaderValue = assignFunctionName('validateHeaderValue', hideStackFrames((name, value, lenient) => {
+  if (value === undefined) {
+    throw new ERR_HTTP_INVALID_HEADER_VALUE.HideStackFramesError(value, name);
+  }
+  if (checkInvalidHeaderChar(value, lenient)) {
+    debug('Header "%s" contains invalid characters', name);
+    throw new ERR_INVALID_CHAR.HideStackFramesError('header content', name);
+  }
+}));
+
+function parseUniqueHeadersOption(headers) {
+  if (!ArrayIsArray(headers)) {
+    return null;
+  }
+
+  const unique = new SafeSet();
+  const l = headers.length;
+  for (let i = 0; i < l; i++) {
+    unique.add(headers[i].toLowerCase());
+  }
+
+  return unique;
+}
+
+OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
+  if (this._header) {
+    throw new ERR_HTTP_HEADERS_SENT('set');
+  }
+  validateHeaderName(name);
+  if (value === undefined) {
+    throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
+  }
+  if (checkInvalidHeaderChar(value, this._isLenientHeaderValidation())) {
+    debug('Header "%s" contains invalid characters', name);
+    throw new ERR_INVALID_CHAR('header content', name);
+  }
+
+  let headers = this[kOutHeaders];
+  if (headers === null)
+    this[kOutHeaders] = headers = new OutgoingHeaders();
+
+  headers[name.toLowerCase()] = [name, value];
+  return this;
+};
+
+OutgoingMessage.prototype.setHeaders = function setHeaders(headers) {
+  if (this._header) {
+    throw new ERR_HTTP_HEADERS_SENT('set');
+  }
+
+
+  if (
+    !headers ||
+    ArrayIsArray(headers) ||
+    typeof headers.keys !== 'function' ||
+    typeof headers.get !== 'function'
+  ) {
+    throw new ERR_INVALID_ARG_TYPE('headers', ['Headers', 'Map'], headers);
+  }
+
+  // Headers object joins multiple cookies with a comma when using
+  // the getter to retrieve the value,
+  // unless iterating over the headers directly.
+  // We also cannot safely split by comma.
+  // To avoid setHeader overwriting the previous value we push
+  // set-cookie values in array and set them all at once.
+  let cookies = null;
+
+  for (const { 0: key, 1: value } of headers) {
+    if (key === 'set-cookie') {
+      if (ArrayIsArray(value)) {
+        cookies ??= [];
+        cookies.push(...value);
+      } else {
+        cookies ??= [];
+        cookies.push(value);
+      }
+      continue;
+    }
+    this.setHeader(key, value);
+  }
+  if (cookies != null) {
+    this.setHeader('set-cookie', cookies);
+  }
+
+  return this;
+};
+
+OutgoingMessage.prototype.appendHeader = function appendHeader(name, value) {
+  if (this._header) {
+    throw new ERR_HTTP_HEADERS_SENT('append');
+  }
+  validateHeaderName(name);
+  if (value === undefined) {
+    throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
+  }
+  if (checkInvalidHeaderChar(value, this._isLenientHeaderValidation())) {
+    debug('Header "%s" contains invalid characters', name);
+    throw new ERR_INVALID_CHAR('header content', name);
+  }
+
+  const field = name.toLowerCase();
+  const headers = this[kOutHeaders];
+  if (headers === null || !headers[field]) {
+    return this.setHeader(name, value);
+  }
+
+  // Prepare the field for appending, if required
+  if (!ArrayIsArray(headers[field][1])) {
+    headers[field][1] = [headers[field][1]];
+  }
+
+  const existingValues = headers[field][1];
+  if (ArrayIsArray(value)) {
+    for (let i = 0, length = value.length; i < length; i++) {
+      existingValues.push(value[i]);
+    }
+  } else {
+    existingValues.push(value);
+  }
+
+  return this;
+};
+
+
+OutgoingMessage.prototype.getHeader = function getHeader(name) {
+  validateString(name, 'name');
+
+  const headers = this[kOutHeaders];
+  if (headers === null)
+    return;
+
+  const entry = headers[name.toLowerCase()];
+  return entry?.[1];
+};
+
+
+// Returns an array of the names of the current outgoing headers.
+OutgoingMessage.prototype.getHeaderNames = function getHeaderNames() {
+  return this[kOutHeaders] !== null ? ObjectKeys(this[kOutHeaders]) : [];
+};
+
+
+// Returns an array of the names of the current outgoing raw headers.
+OutgoingMessage.prototype.getRawHeaderNames = function getRawHeaderNames() {
+  const headersMap = this[kOutHeaders];
+  if (headersMap === null) return [];
+
+  const headers = ObjectValues(headersMap);
+  // Retain for(;;) loop for performance reasons
+  // Refs: https://github.com/nodejs/node/pull/30958
+  for (let i = 0; i < headers.length; i++) {
+    headers[i] = headers[i][0];
+  }
+
+  return headers;
+};
+
+
+// Returns a shallow copy of the current outgoing headers.
+OutgoingMessage.prototype.getHeaders = function getHeaders() {
+  const headers = this[kOutHeaders];
+  const ret = { __proto__: null };
+  if (headers) {
+    const keys = ObjectKeys(headers);
+    // Retain for(;;) loop for performance reasons
+    // Refs: https://github.com/nodejs/node/pull/30958
+    for (let i = 0; i < keys.length; ++i) {
+      const key = keys[i];
+      const val = headers[key][1];
+      ret[key] = val;
+    }
+  }
+  return ret;
+};
+
+
+OutgoingMessage.prototype.hasHeader = function hasHeader(name) {
+  validateString(name, 'name');
+  return this[kOutHeaders] !== null &&
+    !!this[kOutHeaders][name.toLowerCase()];
+};
+
+
+OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
+  validateString(name, 'name');
+
+  if (this._header) {
+    throw new ERR_HTTP_HEADERS_SENT('remove');
+  }
+
+  const key = name.toLowerCase();
+
+  switch (key) {
+    case 'connection':
+      this._removedConnection = true;
+      break;
+    case 'content-length':
+      this._removedContLen = true;
+      break;
+    case 'transfer-encoding':
+      this._removedTE = true;
+      break;
+    case 'date':
+      this.sendDate = false;
+      break;
+  }
+
+  if (this[kOutHeaders] !== null) {
+    delete this[kOutHeaders][key];
+  }
+};
+
+
+OutgoingMessage.prototype._implicitHeader = function _implicitHeader() {
+  throw new ERR_METHOD_NOT_IMPLEMENTED('_implicitHeader()');
+};
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'headersSent', {
+  __proto__: null,
+  configurable: true,
+  enumerable: true,
+  get: function() { return !!this._header; },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'writableEnded', {
+  __proto__: null,
+  get: function() { return this.finished; },
+});
+
+ObjectDefineProperty(OutgoingMessage.prototype, 'writableNeedDrain', {
+  __proto__: null,
+  get: function() {
+    return !this.destroyed && !this.finished && this[kNeedDrain];
+  },
+});
+
+const crlf_buf = Buffer.from('\r\n');
+OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
+  if (typeof encoding === 'function') {
+    callback = encoding;
+    encoding = null;
+  }
+
+  const ret = write_(this, chunk, encoding, callback, false);
+  if (!ret)
+    this[kNeedDrain] = true;
+  return ret;
+};
+
+function onError(msg, err, callback) {
+  if (msg.destroyed) {
+    return;
+  }
+
+  const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined;
+  defaultTriggerAsyncIdScope(triggerAsyncId,
+                             process.nextTick,
+                             emitErrorNt,
+                             msg,
+                             err,
+                             callback);
+}
+
+function emitErrorNt(msg, err, callback) {
+  callback(err);
+  if (typeof msg.emit === 'function' && !msg.destroyed) {
+    msg.emit('error', err);
+  }
+}
+
+function strictContentLength(msg) {
+  return (
+    msg.strictContentLength &&
+    msg._contentLength != null &&
+    msg._hasBody &&
+    !msg._removedContLen &&
+    !msg.chunkedEncoding &&
+    !msg.hasHeader('transfer-encoding')
+  );
+}
+
+function write_(msg, chunk, encoding, callback, fromEnd) {
+  if (typeof callback !== 'function')
+    callback = nop;
+
+  if (chunk === null) {
+    throw new ERR_STREAM_NULL_VALUES();
+  } else if (typeof chunk !== 'string' && !isUint8Array(chunk)) {
+    throw new ERR_INVALID_ARG_TYPE(
+      'chunk', ['string', 'Buffer', 'Uint8Array'], chunk);
+  }
+
+  let err;
+  if (msg.finished) {
+    err = new ERR_STREAM_WRITE_AFTER_END();
+  } else if (msg.destroyed) {
+    err = new ERR_STREAM_DESTROYED('write');
+  }
+
+  if (err) {
+    if (!msg.destroyed) {
+      onError(msg, err, callback);
+    } else {
+      process.nextTick(callback, err);
+    }
+    return false;
+  }
+
+  let len;
+
+  if (msg.strictContentLength) {
+    len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
+
+    if (
+      strictContentLength(msg) &&
+      (fromEnd ? msg[kBytesWritten] + len !== msg._contentLength : msg[kBytesWritten] + len > msg._contentLength)
+    ) {
+      throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(len + msg[kBytesWritten], msg._contentLength);
+    }
+
+    msg[kBytesWritten] += len;
+  }
+
+  if (!msg._header) {
+    if (fromEnd) {
+      len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
+      msg._contentLength = len;
+    }
+    msg._implicitHeader();
+  }
+
+  if (!msg._hasBody) {
+    if (msg[kRejectNonStandardBodyWrites]) {
+      throw new ERR_HTTP_BODY_NOT_ALLOWED();
+    } else {
+      debug('This type of response MUST NOT have a body. ' +
+        'Ignoring write() calls.');
+      process.nextTick(callback);
+      return true;
+    }
+  }
+
+  if (msg.chunkedEncoding && typeof chunk === 'string' && encoding && !Buffer.isEncoding(encoding)) {
+    throw new ERR_UNKNOWN_ENCODING(encoding);
+  }
+
+  if (!fromEnd && msg.socket && !msg.socket.writableCorked) {
+    msg.cork();
+    process.nextTick(connectionCorkNT, msg);
+  }
+
+  let ret;
+  if (msg.chunkedEncoding && chunk.length !== 0) {
+    len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
+    if (msg[kCorked]) {
+      if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
+        chunk = Stream._uint8ArrayToBuffer(chunk);
+      }
+      msg[kChunkedBuffer].push(chunk, encoding, callback);
+      msg[kChunkedLength] += len;
+      ret = msg.writableLength < msg.writableHighWaterMark;
+    } else {
+      msg._send(len.toString(16), 'latin1', null);
+      msg._send(crlf_buf, null, null);
+      msg._send(chunk, encoding, null, len);
+      ret = msg._send(crlf_buf, null, callback);
+    }
+  } else if (msg.chunkedEncoding && msg[kCorked] && msg[kChunkedBuffer].length !== 0) {
+    msg[kChunkedBuffer].push(chunk, encoding, callback);
+    ret = msg.writableLength < msg.writableHighWaterMark;
+  } else {
+    ret = msg._send(chunk, encoding, callback, len);
+  }
+
+  debug('write ret = ' + ret);
+  return ret;
+}
+
+
+// If this last write can be delivered immediately as the final chunk, this
+// prepares to do so, and then returns true. If not, it returns false and
+// a separate _send call and tick will be required to finish up.
+function maybePrepareFinalChunk(msg, chunk, encoding) {
+  if (typeof chunk !== 'string' && !isUint8Array(chunk))
+    return false;
+
+  if (msg.destroyed || msg.strictContentLength)
+    return false;
+
+  if (!msg._header) {
+    msg._contentLength = typeof chunk === 'string' ?
+      Buffer.byteLength(chunk, encoding) : chunk.byteLength;
+    msg._implicitHeader();
+  }
+
+  return !!msg._header && msg._hasBody && !msg.chunkedEncoding;
+}
+
+function connectionCorkNT(msg) {
+  if (msg[kCorked]) {
+    msg.uncork();
+  }
+}
+
+OutgoingMessage.prototype.addTrailers = function addTrailers(headers) {
+  if (this.finished) {
+    throw new ERR_HTTP_HEADERS_SENT('set trailing');
+  }
+
+  this._trailer = '';
+  const keys = ObjectKeys(headers);
+  const isArray = ArrayIsArray(headers);
+  // Retain for(;;) loop for performance reasons
+  // Refs: https://github.com/nodejs/node/pull/30958
+  for (let i = 0, l = keys.length; i < l; i++) {
+    let field, value;
+    const key = keys[i];
+    if (isArray) {
+      field = headers[key][0];
+      value = headers[key][1];
+    } else {
+      field = key;
+      value = headers[key];
+    }
+    validateHeaderName(field, 'Trailer name');
+
+    // Check if the field must be sent several times
+    const isArrayValue = ArrayIsArray(value);
+    const lenient = this._isLenientHeaderValidation();
+    if (
+      isArrayValue && value.length > 1 &&
+      (!this[kUniqueHeaders] || !this[kUniqueHeaders].has(field.toLowerCase()))
+    ) {
+      for (let j = 0, l = value.length; j < l; j++) {
+        if (checkInvalidHeaderChar(value[j], lenient)) {
+          debug('Trailer "%s"[%d] contains invalid characters', field, j);
+          throw new ERR_INVALID_CHAR('trailer content', field);
+        }
+        this._trailer += field + ': ' + value[j] + '\r\n';
+      }
+    } else {
+      if (isArrayValue) {
+        value = value.join('; ');
+      }
+
+      if (checkInvalidHeaderChar(value, lenient)) {
+        debug('Trailer "%s" contains invalid characters', field);
+        throw new ERR_INVALID_CHAR('trailer content', field);
+      }
+      this._trailer += field + ': ' + value + '\r\n';
+    }
+  }
+};
+
+// Deliver end() callbacks, mirroring Writable: null on successful finish,
+// otherwise the error that prevented all data from being flushed.
+function flushEndCallbacks(msg, err) {
+  const callbacks = msg[kEndCallbacks];
+  if (callbacks === null)
+    return;
+  msg[kEndCallbacks] = null;
+  for (let i = 0; i < callbacks.length; i++)
+    callbacks[i](err);
+}
+
+function getEndCallbackError(msg) {
+  return msg[kErrored] ??
+    msg[kSocket]?.errored ??
+    new ERR_STREAM_DESTROYED('end');
+}
+
+function queueEndCallback(msg, callback) {
+  if (msg[kWritableFinished]) {
+    callback(new ERR_STREAM_ALREADY_FINISHED('end'));
+    return;
+  }
+  if (msg[kFlushError] !== null) {
+    process.nextTick(callback, msg[kFlushError]);
+    return;
+  }
+  msg[kEndCallbacks] ??= [];
+  msg[kEndCallbacks].push(callback);
+}
+
+function onFinish(outmsg, err) {
+  if (err ||
+      outmsg[kErrored] ||
+      outmsg[kSocket]?.errored ||
+      outmsg[kSocket]?._hadError) {
+    outmsg[kFlushError] = err ?? getEndCallbackError(outmsg);
+    flushEndCallbacks(outmsg, outmsg[kFlushError]);
+    return;
+  }
+
+  outmsg[kWritableFinished] = true;
+  flushEndCallbacks(outmsg, null);
+  outmsg.emit('finish');
+}
+
+OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
+  if (typeof chunk === 'function') {
+    callback = chunk;
+    chunk = null;
+    encoding = null;
+  } else if (typeof encoding === 'function') {
+    callback = encoding;
+    encoding = null;
+  }
+
+  let finishCallback = null;
+
+  if (chunk) {
+    if (this.finished) {
+      onError(this,
+              new ERR_STREAM_WRITE_AFTER_END(),
+              typeof callback !== 'function' ? nop : callback);
+      return this;
+    }
+
+    if (this[kSocket]) {
+      this[kSocket].cork();
+    }
+
+    if (maybePrepareFinalChunk(this, chunk, encoding)) {
+      // If just one final write is required, with nothing to follow, we
+      // attach finish to the write to avoid a separate send() & tick step
+      // later on - this is purely a performance optimization.
+      if (typeof callback === 'function') {
+        queueEndCallback(this, callback);
+        callback = undefined;
+      }
+      finishCallback = onFinish.bind(undefined, this);
+    }
+
+    write_(this, chunk, encoding, finishCallback, true);
+  } else if (this.finished) {
+    if (typeof callback === 'function') {
+      queueEndCallback(this, callback);
+    }
+    return this;
+  } else if (!this._header) {
+    if (this[kSocket]) {
+      this[kSocket].cork();
+    }
+
+    this._contentLength = 0;
+    this._implicitHeader();
+  }
+
+  if (typeof callback === 'function')
+    queueEndCallback(this, callback);
+
+  if (strictContentLength(this) && this[kBytesWritten] !== this._contentLength) {
+    throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], this._contentLength);
+  }
+
+  // Flush buffered chunks before terminating the response.
+  if (this[kChunkedBuffer].length !== 0) {
+    this[kSocket]?.cork();
+    this[kCorked] = 1;
+    this.uncork();
+  }
+
+  if (finishCallback === null) {
+    // If we didn't early finish, send the last data and schedule 'finish' now:
+    finishCallback = onFinish.bind(undefined, this);
+
+    if (this._hasBody && this.chunkedEncoding) {
+      this._send('0\r\n' + this._trailer + '\r\n', 'latin1', finishCallback);
+    } else if (!this._headerSent || this.writableLength || chunk) {
+      this._send('', 'latin1', finishCallback);
+    } else {
+      process.nextTick(finishCallback);
+    }
+  }
+
+  if (this[kSocket]) {
+    // Fully uncork connection on end().
+    this[kSocket]._writableState.corked = 1;
+    this[kSocket].uncork();
+  }
+  this[kCorked] = 1;
+  this.uncork();
+
+  this.finished = true;
+
+  // There is the first message on the outgoing queue, and we've sent
+  // everything to the socket.
+  debug('outgoing message end.');
+  if (this.outputData.length === 0 &&
+      this[kSocket] &&
+      this[kSocket]._httpMessage === this) {
+    this._finish();
+  }
+
+  return this;
+};
+
+
+// This function is called once all user data are flushed to the socket.
+// Note that it has a chance that the socket is not drained.
+OutgoingMessage.prototype._finish = function _finish() {
+  assert(this[kSocket]);
+  this.emit('prefinish');
+};
+
+
+// This logic is probably a bit confusing. Let me explain a bit:
+//
+// In both HTTP servers and clients it is possible to queue up several
+// outgoing messages. This is easiest to imagine in the case of a client.
+// Take the following situation:
+//
+//    req1 = client.request('GET', '/');
+//    req2 = client.request('POST', '/');
+//
+// When the user does
+//
+//   req2.write('hello world\n');
+//
+// it's possible that the first request has not been completely flushed to
+// the socket yet. Thus the outgoing messages need to be prepared to queue
+// up data internally before sending it on further to the socket's queue.
+//
+// This function, _flush(), is called by both the Server and Client
+// to attempt to flush any pending messages out to the socket.
+OutgoingMessage.prototype._flush = function _flush() {
+  const socket = this[kSocket];
+
+  if (socket?.writable) {
+    // There might be remaining data in this.output; write it out
+    const ret = this._flushOutput(socket);
+
+    if (this.finished) {
+      // This is a queue to the server or client to bring in the next this.
+      this._finish();
+    } else if (this[kNeedDrain] && ret !== false) {
+      // Socket accepted all data without backpressure - it won't emit
+      // drain, so we emit it since the OM buffer is now clear.
+      this[kNeedDrain] = false;
+      this.emit('drain');
+    }
+  }
+};
+
+OutgoingMessage.prototype._flushOutput = function _flushOutput(socket) {
+  const outputLength = this.outputData.length;
+  if (outputLength <= 0)
+    return undefined;
+
+  const outputData = this.outputData;
+  socket.cork();
+  let ret;
+  // Retain for(;;) loop for performance reasons
+  // Refs: https://github.com/nodejs/node/pull/30958
+  for (let i = 0; i < outputLength; i++) {
+    const { data, encoding, callback } = outputData[i];
+    // Avoid any potential ref to Buffer in new generation from old generation
+    outputData[i].data = null;
+    ret = socket.write(data, encoding, callback);
+  }
+  socket.uncork();
+
+  this.outputData = [];
+  this._onPendingData(-this.outputSize);
+  this.outputSize = 0;
+
+  return ret;
+};
+
+
+OutgoingMessage.prototype.flushHeaders = function flushHeaders() {
+  if (!this._header) {
+    this._implicitHeader();
+  }
+
+  // Force-flush the headers.
+  this._send('');
+};
+
+OutgoingMessage.prototype.pipe = function pipe() {
+  // OutgoingMessage should be write-only. Piping from it is disabled.
+  this.emit('error', new ERR_STREAM_CANNOT_PIPE());
+};
+
+OutgoingMessage.prototype[EE.captureRejectionSymbol] =
+function(err, event) {
+  this.destroy(err);
+};
+
+module.exports = {
+  kHighWaterMark,
+  kUniqueHeaders,
+  parseUniqueHeadersOption,
+  isValidHeaderName,
+  isValidHeaderValue,
+  validateHeaderName,
+  validateHeaderValue,
+  OutgoingMessage,
+};
diff --git a/lib/internal/http/server.js b/lib/internal/http/server.js
new file mode 100644
index 00000000000..406eede9300
--- /dev/null
+++ b/lib/internal/http/server.js
@@ -0,0 +1,1549 @@
+// 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.
+
+'use strict';
+
+const {
+  ArrayIsArray,
+  Error,
+  MathMin,
+  NumberIsFinite,
+  ObjectKeys,
+  ObjectSetPrototypeOf,
+  ReflectApply,
+  Symbol,
+  SymbolAsyncDispose,
+  SymbolFor,
+} = primordials;
+
+const { Duplex } = require('stream');
+const net = require('net');
+const EE = require('events');
+const assert = require('internal/assert');
+const {
+  parsers,
+  freeParser,
+  continueExpression,
+  chunkExpression,
+  kIncomingMessage,
+  kSocket,
+  HTTPParser,
+  calculateLenientFlags,
+  _checkInvalidHeaderChar: checkInvalidHeaderChar,
+  prepareError,
+} = require('internal/http/common');
+const { ConnectionsList } = internalBinding('http_parser');
+const {
+  kUniqueHeaders,
+  parseUniqueHeadersOption,
+  OutgoingMessage,
+  validateHeaderName,
+  validateHeaderValue,
+} = require('internal/http/outgoing');
+const {
+  kOutHeaders,
+  kNeedDrain,
+  isTraceHTTPEnabled,
+  traceBegin,
+  traceEnd,
+  getNextTraceEventId,
+} = require('internal/http');
+const {
+  defaultTriggerAsyncIdScope,
+  getOrSetAsyncId,
+} = require('internal/async_hooks');
+const {
+  IncomingMessage,
+  kDetachAbortSignal,
+  kHeadersCount,
+  asciiEqualIgnoreCase,
+  getRawHeader,
+} = require('internal/http/incoming');
+const {
+  ConnResetException,
+  codes: {
+    ERR_HTTP_HEADERS_SENT,
+    ERR_HTTP_INVALID_STATUS_CODE,
+    ERR_HTTP_REQUEST_TIMEOUT,
+    ERR_HTTP_SOCKET_ASSIGNED,
+    ERR_HTTP_SOCKET_ENCODING,
+    ERR_INVALID_ARG_VALUE,
+    ERR_INVALID_CHAR,
+    ERR_OUT_OF_RANGE,
+  },
+} = require('internal/errors');
+const {
+  assignFunctionName,
+  deprecateInstantiation,
+  kEmptyObject,
+  promisify,
+} = require('internal/util');
+const {
+  validateInteger,
+  validateBoolean,
+  validateOneOf,
+  validateLinkHeaderValue,
+  validateObject,
+  validateFunction,
+} = 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 onResponseCreatedChannel = dc.channel('http.server.response.created');
+const onResponseFinishChannel = dc.channel('http.server.response.finish');
+
+const kServerResponse = Symbol('ServerResponse');
+const kServerResponseStatistics = Symbol('ServerResponseStatistics');
+const kUpgradeStream = Symbol('UpgradeStream');
+
+const kOptimizeEmptyRequests = Symbol('OptimizeEmptyRequestsOption');
+const kConnectionState = Symbol('ConnectionState');
+const kResponseOptions = Symbol('ResponseOptions');
+
+const statusLineCache = [];
+
+const {
+  hasObserver,
+  startPerf,
+  stopPerf,
+} = require('internal/perf/observe');
+
+const STATUS_CODES = {
+  100: 'Continue',                   // RFC 7231 6.2.1
+  101: 'Switching Protocols',        // RFC 7231 6.2.2
+  102: 'Processing',                 // RFC 2518 10.1 (obsoleted by RFC 4918)
+  103: 'Early Hints',                // RFC 8297 2
+  200: 'OK',                         // RFC 7231 6.3.1
+  201: 'Created',                    // RFC 7231 6.3.2
+  202: 'Accepted',                   // RFC 7231 6.3.3
+  203: 'Non-Authoritative Information', // RFC 7231 6.3.4
+  204: 'No Content',                 // RFC 7231 6.3.5
+  205: 'Reset Content',              // RFC 7231 6.3.6
+  206: 'Partial Content',            // RFC 7233 4.1
+  207: 'Multi-Status',               // RFC 4918 11.1
+  208: 'Already Reported',           // RFC 5842 7.1
+  226: 'IM Used',                    // RFC 3229 10.4.1
+  300: 'Multiple Choices',           // RFC 7231 6.4.1
+  301: 'Moved Permanently',          // RFC 7231 6.4.2
+  302: 'Found',                      // RFC 7231 6.4.3
+  303: 'See Other',                  // RFC 7231 6.4.4
+  304: 'Not Modified',               // RFC 7232 4.1
+  305: 'Use Proxy',                  // RFC 7231 6.4.5
+  307: 'Temporary Redirect',         // RFC 7231 6.4.7
+  308: 'Permanent Redirect',         // RFC 7238 3
+  400: 'Bad Request',                // RFC 7231 6.5.1
+  401: 'Unauthorized',               // RFC 7235 3.1
+  402: 'Payment Required',           // RFC 7231 6.5.2
+  403: 'Forbidden',                  // RFC 7231 6.5.3
+  404: 'Not Found',                  // RFC 7231 6.5.4
+  405: 'Method Not Allowed',         // RFC 7231 6.5.5
+  406: 'Not Acceptable',             // RFC 7231 6.5.6
+  407: 'Proxy Authentication Required', // RFC 7235 3.2
+  408: 'Request Timeout',            // RFC 7231 6.5.7
+  409: 'Conflict',                   // RFC 7231 6.5.8
+  410: 'Gone',                       // RFC 7231 6.5.9
+  411: 'Length Required',            // RFC 7231 6.5.10
+  412: 'Precondition Failed',        // RFC 7232 4.2
+  413: 'Payload Too Large',          // RFC 7231 6.5.11
+  414: 'URI Too Long',               // RFC 7231 6.5.12
+  415: 'Unsupported Media Type',     // RFC 7231 6.5.13
+  416: 'Range Not Satisfiable',      // RFC 7233 4.4
+  417: 'Expectation Failed',         // RFC 7231 6.5.14
+  418: 'I\'m a Teapot',              // RFC 7168 2.3.3
+  421: 'Misdirected Request',        // RFC 7540 9.1.2
+  422: 'Unprocessable Entity',       // RFC 4918 11.2
+  423: 'Locked',                     // RFC 4918 11.3
+  424: 'Failed Dependency',          // RFC 4918 11.4
+  425: 'Too Early',                  // RFC 8470 5.2
+  426: 'Upgrade Required',           // RFC 2817 and RFC 7231 6.5.15
+  428: 'Precondition Required',      // RFC 6585 3
+  429: 'Too Many Requests',          // RFC 6585 4
+  431: 'Request Header Fields Too Large', // RFC 6585 5
+  451: 'Unavailable For Legal Reasons', // RFC 7725 3
+  500: 'Internal Server Error',      // RFC 7231 6.6.1
+  501: 'Not Implemented',            // RFC 7231 6.6.2
+  502: 'Bad Gateway',                // RFC 7231 6.6.3
+  503: 'Service Unavailable',        // RFC 7231 6.6.4
+  504: 'Gateway Timeout',            // RFC 7231 6.6.5
+  505: 'HTTP Version Not Supported', // RFC 7231 6.6.6
+  506: 'Variant Also Negotiates',    // RFC 2295 8.1
+  507: 'Insufficient Storage',       // RFC 4918 11.5
+  508: 'Loop Detected',              // RFC 5842 7.2
+  509: 'Bandwidth Limit Exceeded',
+  510: 'Not Extended',               // RFC 2774 7
+  511: 'Network Authentication Required', // RFC 6585 6
+};
+
+const kOnExecute = HTTPParser.kOnExecute | 0;
+const kOnTimeout = HTTPParser.kOnTimeout | 0;
+
+const kConnections = Symbol('http.server.connections');
+const kConnectionsCheckingInterval = Symbol('http.server.connectionsCheckingInterval');
+
+const HTTP_SERVER_TRACE_EVENT_NAME = 'http.server.request';
+
+class HTTPServerAsyncResource {
+  constructor(type, socket) {
+    this.type = type;
+    this.socket = socket;
+  }
+}
+
+function ServerResponse(req, options) {
+  if (!(this instanceof ServerResponse)) {
+    return deprecateInstantiation(ServerResponse, 'DEP0195', 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(getRawHeader(req, 'te', true));
+    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);
+  }
+  if (onResponseCreatedChannel.hasSubscribers) {
+    onResponseCreatedChannel.publish({
+      request: req,
+      response: this,
+    });
+  }
+}
+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() {
+  // 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. :-)
+  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.writeInformation = function writeInformation(
+  statusCode, headers, cb) {
+  if (this._header) {
+    throw new ERR_HTTP_HEADERS_SENT('write');
+  }
+
+  validateInteger(statusCode, 'statusCode', 100, 199);
+  if (statusCode === 101) {
+    throw new ERR_HTTP_INVALID_STATUS_CODE(statusCode);
+  }
+
+  const statusMessage = STATUS_CODES[statusCode] || 'unknown';
+  let head = `HTTP/1.1 ${statusCode} ${statusMessage}\r\n`;
+
+  const lenient = this._isLenientHeaderValidation();
+  if (headers !== undefined && headers !== null) {
+    if (ArrayIsArray(headers)) {
+      if (headers.length && ArrayIsArray(headers[0])) {
+        for (let i = 0; i < headers.length; i++) {
+          const entry = headers[i];
+          head += processInformationHeader(entry[0], entry[1], lenient);
+        }
+      } else {
+        if (headers.length % 2 !== 0) {
+          throw new ERR_INVALID_ARG_VALUE('headers', headers);
+        }
+        for (let i = 0; i < headers.length; i += 2) {
+          head += processInformationHeader(headers[i], headers[i + 1], lenient);
+        }
+      }
+    } else {
+      validateObject(headers, 'headers');
+      const keys = ObjectKeys(headers);
+      for (let i = 0; i < keys.length; i++) {
+        const key = keys[i];
+        head += processInformationHeader(key, headers[key], lenient);
+      }
+    }
+  }
+
+  head += '\r\n';
+
+  return this._writeRaw(head, 'ascii', cb);
+};
+
+function processInformationHeader(name, value, lenient) {
+  validateHeaderName(name);
+  validateHeaderValue(name, value, lenient);
+  return `${name}: ${value}\r\n`;
+}
+
+ServerResponse.prototype.writeContinue = function writeContinue(cb) {
+  this.writeInformation(100, null, cb);
+  this._sent100 = true;
+};
+
+ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
+  this.writeInformation(102, null, cb);
+};
+
+ServerResponse.prototype.writeEarlyHints = function writeEarlyHints(hints, cb) {
+  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');
+  }
+
+  const headers = { __proto__: null, Link: link };
+  const keys = ObjectKeys(hints);
+  for (let i = 0; i < keys.length; i++) {
+    const key = keys[i];
+    if (key !== 'link') {
+      headers[key] = hints[key];
+    }
+  }
+
+  this.writeInformation(103, headers, 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') {
+    // writeHead(statusCode, reasonPhrase[, headers])
+    this.statusMessage = reason;
+  } else {
+    // writeHead(statusCode[, headers])
+    this.statusMessage ||= STATUS_CODES[statusCode] || 'unknown';
+    obj ??= reason;
+  }
+  this.statusCode = statusCode;
+
+  let headers;
+  if (this[kOutHeaders]) {
+    // Slow-case: when progressive API and header fields are passed.
+    let k;
+    if (ArrayIsArray(obj)) {
+      if (obj.length % 2 !== 0) {
+        throw new ERR_INVALID_ARG_VALUE('headers', obj);
+      }
+
+      // 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 < obj.length; n += 2) {
+        k = obj[n + 0];
+        this.removeHeader(k);
+      }
+
+      for (let n = 0; n < obj.length; n += 2) {
+        k = obj[n + 0];
+        if (k) this.appendHeader(k, obj[n + 1]);
+      }
+    } else if (obj) {
+      const keys = ObjectKeys(obj);
+      // Retain for(;;) loop for performance reasons
+      // Refs: https://github.com/nodejs/node/pull/30958
+      for (let i = 0; i < keys.length; i++) {
+        k = keys[i];
+        if (k) this.setHeader(k, obj[k]);
+      }
+    }
+    // Only progressive api is used
+    headers = this[kOutHeaders];
+  } else {
+    // Only writeHead() called
+    headers = obj;
+  }
+
+  let statusLine;
+  if (this.statusMessage === STATUS_CODES[statusCode]) {
+    // Default reason phrases contain no invalid characters and are shared
+    // across responses: cache the whole status line.
+    statusLine = statusLineCache[statusCode] ??=
+      `HTTP/1.1 ${statusCode} ${this.statusMessage}\r\n`;
+  } else {
+    if (checkInvalidHeaderChar(this.statusMessage))
+      throw new ERR_INVALID_CHAR('statusMessage');
+
+    statusLine = `HTTP/1.1 ${statusCode} ${this.statusMessage}\r\n`;
+  }
+
+  if (statusCode === 204 || statusCode === 304 ||
+      (statusCode >= 100 && statusCode <= 199)) {
+    // RFC 2616, 10.2.5:
+    // The 204 response MUST NOT include a message-body, and thus is always
+    // terminated by the first empty line after the header fields.
+    // RFC 2616, 10.3.5:
+    // The 304 response MUST NOT contain a message-body, and thus is always
+    // terminated by the first empty line after the header fields.
+    // RFC 2616, 10.1 Informational 1xx:
+    // This class of status code indicates a provisional response,
+    // consisting only of the Status-Line and optional headers, and is
+    // terminated by an empty line.
+    this._hasBody = false;
+  }
+
+  // Don't keep alive connections where the client expects 100 Continue
+  // but we sent a final status; they may put extra bytes on the wire.
+  if (this._expect_continue && !this._sent100) {
+    this.shouldKeepAlive = false;
+  }
+
+  this._storeHeader(statusLine, headers);
+
+  return this;
+}
+
+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 optimizeEmptyRequests = options.optimizeEmptyRequests;
+  if (optimizeEmptyRequests !== undefined)
+    validateBoolean(optimizeEmptyRequests, 'options.optimizeEmptyRequests');
+  this[kOptimizeEmptyRequests] = optimizeEmptyRequests || false;
+
+  const insecureHTTPParser = options.insecureHTTPParser;
+  if (insecureHTTPParser !== undefined)
+    validateBoolean(insecureHTTPParser, 'options.insecureHTTPParser');
+  this.insecureHTTPParser = insecureHTTPParser;
+
+  const httpValidation = options.httpValidation;
+  if (httpValidation !== undefined) {
+    validateOneOf(httpValidation, 'options.httpValidation',
+                  ['strict', 'relaxed', 'insecure']);
+    if (insecureHTTPParser !== undefined) {
+      throw new ERR_INVALID_ARG_VALUE(
+        'options.httpValidation',
+        httpValidation,
+        'cannot be used together with options.insecureHTTPParser',
+      );
+    }
+  }
+  this.httpValidation = httpValidation;
+
+  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 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 = 65_000; // 65 seconds;
+  }
+
+  const keepAliveTimeoutBuffer = options.keepAliveTimeoutBuffer;
+  if (keepAliveTimeoutBuffer !== undefined) {
+    validateInteger(keepAliveTimeoutBuffer, 'keepAliveTimeoutBuffer', 0);
+    this.keepAliveTimeoutBuffer = keepAliveTimeoutBuffer;
+  } else {
+    this.keepAliveTimeoutBuffer = 1000;
+  }
+
+  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;
+  }
+
+  const shouldUpgradeCallback = options.shouldUpgradeCallback;
+  if (shouldUpgradeCallback !== undefined) {
+    validateFunction(shouldUpgradeCallback, 'options.shouldUpgradeCallback');
+    this.shouldUpgradeCallback = shouldUpgradeCallback;
+  } else {
+    this.shouldUpgradeCallback = function() {
+      return this.listenerCount('upgrade') > 0;
+    };
+  }
+}
+
+function setupConnectionsTracking() {
+  // Start connection handling
+  this[kConnections] ||= new ConnectionsList();
+
+  if (this[kConnectionsCheckingInterval]) {
+    clearInterval(this[kConnectionsCheckingInterval]);
+  }
+  // This checker is started without checking whether any headersTimeout or requestTimeout is non zero
+  // otherwise it would not be started if such timeouts are modified after createServer.
+  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 deprecateInstantiation(Server, 'DEP0195', 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);
+  }
+
+  // Similar option to this. Too lazy to write my own docs.
+  // http://www.squid-cache.org/Doc/config/half_closed_clients/
+  // https://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F
+  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 close() {
+  httpServerPreClose(this);
+  ReflectApply(net.Server.prototype.close, this, arguments);
+  return this;
+};
+
+Server.prototype[SymbolAsyncDispose] = assignFunctionName(SymbolAsyncDispose, async function() {
+  await promisify(this.close).call(this);
+});
+
+Server.prototype.closeAllConnections = function closeAllConnections() {
+  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 closeIdleConnections() {
+  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] =
+assignFunctionName(EE.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:
+      ReflectApply(
+        net.Server.prototype[SymbolFor('nodejs.rejection')],
+        this, arguments);
+  }
+});
+
+function checkConnections() {
+  const headersTimeout =
+    NumberIsFinite(this.headersTimeout) && this.headersTimeout >= 0 ?
+      this.headersTimeout : 0;
+  const requestTimeout =
+    NumberIsFinite(this.requestTimeout) && this.requestTimeout >= 0 ?
+      this.requestTimeout : 0;
+
+  if (headersTimeout === 0 && requestTimeout === 0) {
+    return;
+  }
+
+  const expired = this[kConnections].expired(headersTimeout, 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');
+
+  // Ensure that the server property of the socket is correctly set.
+  // See https://github.com/nodejs/node/issues/13435
+  socket.server = server;
+
+  // If the user has added a listener to the server,
+  // request, or response, then it's their responsibility.
+  // otherwise, destroy on timeout by default
+  if (server.timeout && typeof socket.setTimeout === 'function')
+    socket.setTimeout(server.timeout);
+  socket.on('timeout', socketOnTimeout);
+
+  const parser = parsers.alloc();
+
+  const lenientFlags = calculateLenientFlags(server.httpValidation, server.insecureHTTPParser);
+
+  // Propagate headers limit from server instance to parser
+  if (typeof server.maxHeadersCount === 'number') {
+    parser.maxHeaderPairs = server.maxHeadersCount << 1;
+  }
+
+  // TODO(addaleax): This doesn't play well with the
+  // `async_hooks.currentResource()` proposal, see
+  // https://github.com/nodejs/node/pull/21313
+  parser.initialize(
+    HTTPParser.REQUEST,
+    new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket),
+    server.maxHeaderSize || 0,
+    lenientFlags,
+    server[kConnections],
+    parser.maxHeaderPairs,
+  );
+  parser.socket = socket;
+  socket.parser = parser;
+
+  const state = {
+    onData: null,
+    onEnd: null,
+    onClose: null,
+    onDrain: null,
+    outgoing: [],
+    incoming: [],
+    // `outgoingData` is an approximate amount of bytes queued through all
+    // inactive responses. If more data than the high watermark is queued - we
+    // need to pause TCP socket/HTTP parser, and wait until the data will be
+    // sent to the client.
+    outgoingData: 0,
+    requestsCount: 0,
+    keepAliveTimeoutSet: false,
+    onPendingData: null,
+    server,
+    socket,
+  };
+  state.onPendingData = updateOutgoingData.bind(undefined, socket, state);
+  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);
+
+  // We are consuming socket, so it won't get any actual data
+  socket.on('resume', onSocketResume);
+  socket.on('pause', onSocketPause);
+
+  // Overrides to unconsume on `data`, `readable` listeners
+  socket.on = generateSocketListenerWrapper('on');
+  socket.addListener = generateSocketListenerWrapper('addListener');
+  socket.prependListener = generateSocketListenerWrapper('prependListener');
+  socket.setEncoding = socketSetEncoding;
+
+  // We only consume the socket if it has never been consumed before.
+  if (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 we previously paused, then start reading again.
+  if (socket._paused && !needPause) {
+    socket._paused = false;
+    if (socket.parser)
+      socket.parser.resume();
+    socket.resume();
+  }
+
+  const msg = socket._httpMessage;
+  // Only emit 'drain' once the message has no data pending anywhere, so that
+  // msg.writableLength === 0 when the event fires. socketOnDrain is called
+  // synchronously from updateOutgoingData during _flushOutput, at which point
+  // the bytes we just handed to the socket (or the stale outputSize) mean
+  // the message is not actually drained yet - we wait for the socket's
+  // own 'drain' event instead.
+  if (msg && !msg.finished && msg[kNeedDrain] && msg.writableLength === 0) {
+    msg[kNeedDrain] = false;
+    msg.emit('drain');
+  }
+}
+
+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) {
+  debug('server socket close');
+  freeParser(socket.parser, null, socket);
+  abortIncoming(state.incoming);
+  abortOutgoing(state.outgoing);
+}
+
+function abortIncoming(incoming) {
+  while (incoming.length) {
+    const req = incoming.shift();
+    req.destroy(new ConnResetException('aborted'));
+  }
+}
+
+function abortOutgoing(outgoing) {
+  while (outgoing.length) {
+    const req = outgoing.shift();
+    req.destroy(new ConnResetException('aborted'));
+  }
+}
+
+function socketOnEnd(server, socket, parser, state) {
+  const ret = parser.finish();
+
+  if (ret instanceof Error) {
+    debug('parse error');
+    // socketOnError has additional logic and will call socket.destroy(err).
+    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 has additional logic and will call socket.destroy(err).
+  socketOnError.call(socket, new ERR_HTTP_REQUEST_TIMEOUT());
+}
+
+function onParserExecute(server, socket, parser, state, ret) {
+  // When underlying `net.Socket` instance is consumed - no
+  // `data` events are emitted, and thus `socket.setTimeout` fires the
+  // callback even if the data is constantly flowing into the socket.
+  // See, https://github.com/nodejs/node/commit/ec2822adaad76b126b5cccdeaa1addf2376c9aa6
+  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) {
+  // Ignore further errors
+  this.removeListener('error', socketOnError);
+
+  if (this.listenerCount('error', noop) === 0) {
+    this.on('error', noop);
+  }
+
+  if (!this.server.emit('clientError', e, this)) {
+    // Caution must be taken to avoid corrupting the remote peer.
+    // Reply an error segment if there is no in-flight `ServerResponse`,
+    // or no data of the in-flight one has been written yet to this socket.
+    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);
+  }
+}
+
+class UpgradeStream extends Duplex {
+  constructor(socket, req) {
+    super({
+      allowHalfOpen: socket.allowHalfOpen,
+    });
+
+    this[kSocket] = socket;
+    this[kIncomingMessage] = req;
+
+    // Proxy error, end & closure events immediately.
+    socket.on('error', (err) => this.destroy(err));
+
+    socket.on('close', () => this.destroy());
+    this.on('close', () => socket.destroy());
+
+    socket.on('end', () => {
+      this.push(null);
+
+      // Match the socket behaviour, where 'end' will fire despite no 'data'
+      // listeners if a socket with no pending data ends:
+      if (this.readableLength === 0) {
+        this.resume();
+      }
+    });
+
+    // Other events (most notably, reading) all only
+    // activate after requestBodyCompleted is called.
+  }
+
+  requestBodyCompleted(upgradeHead) {
+    this[kIncomingMessage] = null;
+
+    // When the request body is completed, we begin streaming all the
+    // post-body data for the upgraded protocol:
+    if (upgradeHead?.length > 0) {
+      if (!this.push(upgradeHead)) {
+        this[kSocket].pause();
+      }
+    }
+
+    this[kSocket].on('data', (data) => {
+      if (!this.push(data)) {
+        this[kSocket].pause();
+      }
+    });
+  }
+
+  _read(size) {
+    // Reading the upgrade stream starts the request stream flowing. It's
+    // important that this happens, even if there are no listeners, or it
+    // would be impossible to read this without explicitly reading all the
+    // request body first, which is backward incompatible & awkward.
+    this[kIncomingMessage]?.resume();
+
+    this[kSocket].resume();
+  }
+
+  _final(callback) {
+    this[kSocket].end(callback);
+  }
+
+  _write(chunk, encoding, callback) {
+    this[kSocket].write(chunk, encoding, callback);
+  }
+
+  _destroy(err, callback) {
+    this[kSocket].destroy(err);
+    callback(err);
+  }
+}
+
+function onParserExecuteCommon(server, socket, parser, state, ret, d) {
+  if (ret instanceof Error) {
+    prepareError(ret, parser, d);
+    debug('parse error', ret);
+    socketOnError.call(socket, ret);
+  } else if (parser.incoming?.upgrade) {
+    // Upgrade or CONNECT
+    const req = parser.incoming;
+    debug('SERVER upgrade or connect', req.method);
+    const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
+
+    let upgradeStream;
+    if (req.complete) {
+      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;
+
+      // If the request is complete (no body, or all body read upfront) then
+      // we just emit the socket directly as the upgrade stream.
+      upgradeStream = socket;
+    } else {
+      // If the body hasn't been fully parsed yet, we emit immediately but
+      // we add a wrapper around the socket to not expose incoming data
+      // until the request body has finished.
+
+      if (socket[kUpgradeStream]) {
+        // We've already emitted the incomplete upgrade - nothing do to
+        // until actual body parsing completion.
+        return;
+      }
+
+      d ||= Buffer.alloc(0);
+
+      upgradeStream = new UpgradeStream(socket, req);
+      socket[kUpgradeStream] = upgradeStream;
+    }
+
+    if (server.listenerCount(eventName) > 0) {
+      debug('SERVER have listener for %s', eventName);
+
+      const bodyHead = d.slice(ret, d.length);
+
+      if (req.complete && socket[kUpgradeStream]) {
+        // Previously emitted, now completed - just activate the stream
+        socket[kUpgradeStream].requestBodyCompleted(bodyHead);
+      } else {
+        socket.readableFlowing = null;
+        server.emit(eventName, req, upgradeStream, bodyHead);
+      }
+    } else {
+      // Got upgrade or CONNECT method, but have no handler.
+      socket.destroy();
+    }
+  } else if (parser.incoming && parser.incoming.method === 'PRI') {
+    debug('SERVER got PRI request');
+    socket.destroy();
+  }
+
+  if (socket._paused && socket.parser) {
+    // onIncoming paused the socket, we should pause the parser as well
+    debug('pause parser');
+    socket.parser.pause();
+  }
+}
+
+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 resOnFinish(req, res, socket, state, server) {
+  if (onResponseFinishChannel.hasSubscribers) {
+    onResponseFinishChannel.publish({
+      request: req,
+      response: res,
+      socket,
+      server,
+    });
+  }
+
+  // Usually the first incoming element should be our request.  it may
+  // be that in the case abortIncoming() was called that the incoming
+  // array will be empty.
+  assert(state.incoming.length === 0 || state.incoming[0] === req);
+
+  req[kDetachAbortSignal]();
+  state.incoming.shift();
+
+  // If the user never called req.read(), and didn't pipe() or
+  // .resume() or .on('data'), then we call req._dump() so that the
+  // bytes will be pulled off the wire.
+  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) {
+    const keepAliveTimeout = NumberIsFinite(server.keepAliveTimeout) && server.keepAliveTimeout >= 0 ?
+      server.keepAliveTimeout : 0;
+    const keepAliveTimeoutBuffer = NumberIsFinite(server.keepAliveTimeoutBuffer) && server.keepAliveTimeoutBuffer >= 0 ?
+      server.keepAliveTimeoutBuffer : 1e3;
+
+    if (keepAliveTimeout && typeof socket.setTimeout === 'function') {
+      // Extend the internal timeout by the configured buffer to reduce
+      // the likelihood of ECONNRESET errors.
+      // This allows fine-tuning beyond the advertised keepAliveTimeout.
+      socket.setTimeout(keepAliveTimeout + keepAliveTimeoutBuffer);
+      state.keepAliveTimeoutSet = true;
+    }
+  } else {
+    // Start sending the next message
+    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');
+  }
+}
+
+// Check for the presence of a request header by scanning rawHeaders instead
+// of materializing the req.headers object. Only the first req[kHeadersCount]
+// entries are considered, matching what the req.headers getter exposes.
+function hasRequestHeader(req, length, lowerName) {
+  const rawHeaders = req.rawHeaders;
+  const count = req[kHeadersCount];
+  for (let i = 0; i < count; i += 2) {
+    const key = rawHeaders[i];
+    if (key.length === length && asciiEqualIgnoreCase(key, lowerName))
+      return true;
+  }
+  return false;
+}
+
+function hasBodyHeaders(req) {
+  const rawHeaders = req.rawHeaders;
+  const count = req[kHeadersCount];
+  for (let i = 0; i < count; i += 2) {
+    const key = rawHeaders[i];
+    const length = key.length;
+    if (length === 14) {
+      if (asciiEqualIgnoreCase(key, 'content-length'))
+        return true;
+    } else if (length === 17) {
+      if (asciiEqualIgnoreCase(key, 'transfer-encoding'))
+        return true;
+    }
+  }
+  return false;
+}
+
+// The options object passed to the ServerResponse constructor is identical
+// for every request of a given server: cache it. Custom response classes get
+// a fresh object since they may retain or mutate it.
+function getResponseOptions(server, socket) {
+  const highWaterMark = socket.writableHighWaterMark;
+  const rejectNonStandardBodyWrites = server.rejectNonStandardBodyWrites;
+  if (server[kServerResponse] !== ServerResponse) {
+    return { highWaterMark, rejectNonStandardBodyWrites };
+  }
+  let options = server[kResponseOptions];
+  if (options === undefined ||
+      options.highWaterMark !== highWaterMark ||
+      options.rejectNonStandardBodyWrites !== rejectNonStandardBodyWrites) {
+    options = server[kResponseOptions] =
+      { highWaterMark, rejectNonStandardBodyWrites };
+  }
+  return options;
+}
+
+// Shared 'finish' listener: everything resOnFinish needs is reachable from
+// the response, avoiding a bound function per request.
+function onResponseFinish() {
+  const state = this[kConnectionState];
+  resOnFinish(this.req, this, state.socket, state, state.server);
+}
+
+// The following callback is issued after the headers have been read on a
+// new message. In this callback we setup the response object and pass it
+// to the user.
+function parserOnIncoming(server, socket, state, req, keepAlive) {
+  resetSocketTimeout(server, socket, state);
+
+  if (req.upgrade) {
+    req.upgrade = req.method === 'CONNECT' ||
+                  !!server.shouldUpgradeCallback(req);
+    if (req.upgrade) {
+      return 0;
+    }
+  }
+
+  state.incoming.push(req);
+
+  // If the writable end isn't consuming, then stop reading
+  // so that we don't become overwhelmed by a flood of
+  // pipelined requests that may never be resolved.
+  if (!socket._paused) {
+    const ws = socket._writableState;
+    if (ws.needDrain || state.outgoingData >= socket.writableHighWaterMark) {
+      socket._paused = true;
+      // We also need to pause the parser, but don't do that until after
+      // the call to execute, because we may still be processing the last
+      // chunk.
+      socket.pause();
+    }
+  }
+
+  const res = new server[kServerResponse](req, getResponseOptions(server, socket));
+  res._keepAliveTimeout = server.keepAliveTimeout;
+  res._maxRequestsPerSocket = server.maxRequestsPerSocket;
+  res._onPendingData = state.onPendingData;
+  res[kConnectionState] = state;
+
+  res.shouldKeepAlive = keepAlive;
+  res[kUniqueHeaders] = server[kUniqueHeaders];
+
+  if (onRequestStartChannel.hasSubscribers) {
+    onRequestStartChannel.publish({
+      request: req,
+      response: res,
+      socket,
+      server,
+    });
+  }
+
+  // Check if we should optimize empty requests (those without Content-Length or Transfer-Encoding headers)
+  const shouldOptimize = server[kOptimizeEmptyRequests] === true && !hasBodyHeaders(req);
+
+  if (shouldOptimize) {
+    // Fast processing where emitting 'data', 'end' and 'close' events is
+    // skipped and data is dumped.
+    // This avoids a lot of unnecessary overhead otherwise introduced by
+    // stream.Readable life cycle rules. The downside is that this will
+    // break some servers that read bodies for methods that don't have body headers.
+    req._dumpAndCloseReadable();
+    req._read();
+  }
+
+  if (socket._httpMessage) {
+    // There are already pending outgoing res, append.
+    state.outgoing.push(res);
+  } else {
+    res.assignSocket(socket);
+  }
+
+  // When we're finished writing the response, check if this is the last
+  // response, if so destroy the socket.
+  res.on('finish', onResponseFinish);
+
+  let handled = false;
+
+
+  if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) {
+
+    // From RFC 7230 5.4 https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
+    // A server MUST respond with a 400 (Bad Request) status code to any
+    // HTTP/1.1 request message that lacks a Host header field
+    if (server.requireHostHeader && !hasRequestHeader(req, 4, 'host')) {
+      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 {
+      const expect = getRawHeader(req, 'expect', true);
+      if (expect !== undefined) {
+        handled = true;
+
+        if (continueExpression.test(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;  // No special treatment.
+}
+
+function resetSocketTimeout(server, socket, state) {
+  if (!state.keepAliveTimeoutSet)
+    return;
+
+  socket.setTimeout(server.timeout || 0);
+  state.keepAliveTimeoutSet = false;
+}
+
+function onSocketResume() {
+  // It may seem that the socket is resumed, but this is an enemy's trick to
+  // deceive us! `resume` is emitted asynchronously, and may be called from
+  // `incoming.readStart()`. Stop the socket again here, just to preserve the
+  // state.
+  //
+  // We don't care about stream semantics for the consumed socket anyway.
+  if (this._paused) {
+    this.pause();
+    return;
+  }
+
+  if (this._handle && !this._handle.reading) {
+    this._handle.reading = true;
+    this._handle.readStart();
+  }
+}
+
+function onSocketPause() {
+  if (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,
+};
diff --git a/lib/internal/http2/compat.js b/lib/internal/http2/compat.js
index 85abfa714fa..407d84e7fc9 100644
--- a/lib/internal/http2/compat.js
+++ b/lib/internal/http2/compat.js
@@ -60,7 +60,8 @@ const {
   assertValidPseudoHeader,
   getAuthority,
 } = require('internal/http2/util');
-const { _checkIsHttpToken: checkIsHttpToken } = require('_http_common');
+
+const { _checkIsHttpToken: checkIsHttpToken } = require('internal/http/common');

 const kBeginSend = Symbol('begin-send');
 const kState = Symbol('state');
diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js
index 9f14c5f2aef..011cf22210f 100644
--- a/lib/internal/http2/core.js
+++ b/lib/internal/http2/core.js
@@ -53,7 +53,7 @@ const {
   httpServerPreClose,
   setupConnectionsTracking,
   storeHTTPOptions,
-} = require('_http_server');
+} = require('internal/http/server');
 const JSStreamSocket = require('internal/js_stream_socket');

 const {
diff --git a/lib/internal/http2/util.js b/lib/internal/http2/util.js
index e2e6288b9ec..8007b300bd1 100644
--- a/lib/internal/http2/util.js
+++ b/lib/internal/http2/util.js
@@ -16,7 +16,7 @@ const {

 const {
   _checkIsHttpToken: checkIsHttpToken,
-} = require('_http_common');
+} = require('internal/http/common');

 const binding = internalBinding('http2');
 const {
diff --git a/test/async-hooks/test-httpparser.request.js b/test/async-hooks/test-httpparser.request.js
index 40c01bc8177..6379bf330d2 100644
--- a/test/async-hooks/test-httpparser.request.js
+++ b/test/async-hooks/test-httpparser.request.js
@@ -1,3 +1,5 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';

 const common = require('../common');
@@ -9,7 +11,7 @@ const { checkInvocations } = require('./hook-checks');
 const hooks = initHooks();
 hooks.enable();

-const { HTTPParser } = require('_http_common');
+const { HTTPParser } = require('internal/http/common');

 const REQUEST = HTTPParser.REQUEST;

diff --git a/test/async-hooks/test-httpparser.response.js b/test/async-hooks/test-httpparser.response.js
index 64f0893c5a9..2e600b12747 100644
--- a/test/async-hooks/test-httpparser.response.js
+++ b/test/async-hooks/test-httpparser.response.js
@@ -1,3 +1,5 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';

 const common = require('../common');
@@ -10,7 +12,7 @@ const hooks = initHooks();

 hooks.enable();

-const { HTTPParser } = require('_http_common');
+const { HTTPParser } = require('internal/http/common');

 const RESPONSE = HTTPParser.RESPONSE;
 const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
diff --git a/test/parallel/test-async-local-storage-http-parser-leak.js b/test/parallel/test-async-local-storage-http-parser-leak.js
index 2992db7c73f..b55aed8717a 100644
--- a/test/parallel/test-async-local-storage-http-parser-leak.js
+++ b/test/parallel/test-async-local-storage-http-parser-leak.js
@@ -1,11 +1,12 @@
-// Flags: --expose-gc
+// Flags: --expose-gc --expose-internals
+
 'use strict';

 const common = require('../common');
 const { onGC } = require('../common/gc');
 const assert = require('node:assert');
 const { AsyncLocalStorage } = require('node:async_hooks');
-const { freeParser, parsers, HTTPParser } = require('_http_common');
+const { freeParser, parsers, HTTPParser } = require('internal/http/common');

 let storeGCed = false;

diff --git a/test/parallel/test-http-agent-keepalive-delay.js b/test/parallel/test-http-agent-keepalive-delay.js
index 17a0c00c095..c15cdc6f594 100644
--- a/test/parallel/test-http-agent-keepalive-delay.js
+++ b/test/parallel/test-http-agent-keepalive-delay.js
@@ -3,7 +3,7 @@
 const common = require('../common');
 const assert = require('assert');
 const http = require('http');
-const { Agent } = require('_http_agent');
+const { Agent } = require('http');

 const agent = new Agent({
   keepAlive: true,
diff --git a/test/parallel/test-http-agent-keepalive.js b/test/parallel/test-http-agent-keepalive.js
index 1cf5a597d0a..ca50fa40bf2 100644
--- a/test/parallel/test-http-agent-keepalive.js
+++ b/test/parallel/test-http-agent-keepalive.js
@@ -23,7 +23,7 @@
 const common = require('../common');
 const assert = require('assert');
 const http = require('http');
-const Agent = require('_http_agent').Agent;
+const Agent = require('http').Agent;

 let name;

diff --git a/test/parallel/test-http-common.js b/test/parallel/test-http-common.js
index 1629856ce57..33b7d28ec13 100644
--- a/test/parallel/test-http-common.js
+++ b/test/parallel/test-http-common.js
@@ -1,7 +1,9 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';
 require('../common');
 const assert = require('assert');
-const httpCommon = require('_http_common');
+const httpCommon = require('internal/http/common');
 const checkIsHttpToken = httpCommon._checkIsHttpToken;
 const checkInvalidHeaderChar = httpCommon._checkInvalidHeaderChar;

diff --git a/test/parallel/test-http-header-value-relaxed.js b/test/parallel/test-http-header-value-relaxed.js
index d002b808963..68311f3ce96 100644
--- a/test/parallel/test-http-header-value-relaxed.js
+++ b/test/parallel/test-http-header-value-relaxed.js
@@ -1,10 +1,12 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';
 const common = require('../common');
 const assert = require('assert');
 const http = require('http');
 const net = require('net');
 const { duplexPair } = require('stream');
-const { HTTPParser } = require('_http_common');
+const { HTTPParser } = require('internal/http/common');
 // llhttp_set_lenient_header_value_relaxed() was added in llhttp 9.4.0.
 // On shared-library builds using an older system llhttp the constant is
 // exported as 0, so inbound-parsing tests must be skipped there.
diff --git a/test/parallel/test-http-invalidheaderfield2.js b/test/parallel/test-http-invalidheaderfield2.js
index c40de68011a..f1f83550a5a 100644
--- a/test/parallel/test-http-invalidheaderfield2.js
+++ b/test/parallel/test-http-invalidheaderfield2.js
@@ -1,8 +1,10 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';
 require('../common');
 const assert = require('assert');
 const inspect = require('util').inspect;
-const { _checkIsHttpToken, _checkInvalidHeaderChar } = require('_http_common');
+const { _checkIsHttpToken, _checkInvalidHeaderChar } = require('internal/http/common');

 // Good header field names
 [
diff --git a/test/parallel/test-http-parser-bad-ref.js b/test/parallel/test-http-parser-bad-ref.js
index e34054eca67..381f2cc2f99 100644
--- a/test/parallel/test-http-parser-bad-ref.js
+++ b/test/parallel/test-http-parser-bad-ref.js
@@ -2,11 +2,11 @@
 // Run this program with valgrind or efence with --expose_gc to expose the
 // problem.

-// Flags: --expose_gc
+// Flags: --expose_gc --expose-internals --no-warnings

 require('../common');
 const assert = require('assert');
-const { HTTPParser } = require('_http_common');
+const { HTTPParser } = require('internal/http/common');

 const kOnHeaders = HTTPParser.kOnHeaders | 0;
 const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
diff --git a/test/parallel/test-http-parser-lazy-loaded.js b/test/parallel/test-http-parser-lazy-loaded.js
index 44bb59f052e..4b8d7b696cc 100644
--- a/test/parallel/test-http-parser-lazy-loaded.js
+++ b/test/parallel/test-http-parser-lazy-loaded.js
@@ -20,7 +20,7 @@ binding.HTTPParser = DummyParser;

 const assert = require('assert');
 const { spawn } = require('child_process');
-const { parsers } = require('_http_common');
+const { parsers } = require('internal/http/common');

 // Test _http_common was not loaded before monkey patching
 const parser = parsers.alloc();
diff --git a/test/parallel/test-http-parser-memory-retention.js b/test/parallel/test-http-parser-memory-retention.js
index 316797fec3e..29d8dda98dd 100644
--- a/test/parallel/test-http-parser-memory-retention.js
+++ b/test/parallel/test-http-parser-memory-retention.js
@@ -1,9 +1,10 @@
+// Flags: --expose-internals --no-warnings
 'use strict';

 const common = require('../common');
 const assert = require('assert');
 const http = require('http');
-const { HTTPParser } = require('_http_common');
+const { HTTPParser } = require('internal/http/common');

 // Test that the `HTTPParser` instance is cleaned up before being returned to
 // the pool to avoid memory retention issues.
diff --git a/test/parallel/test-http-parser.js b/test/parallel/test-http-parser.js
index 2bf6271e2dd..03ad528c387 100644
--- a/test/parallel/test-http-parser.js
+++ b/test/parallel/test-http-parser.js
@@ -1,3 +1,5 @@
+// Flags: --expose-internals --no-warnings
+
 // Copyright Joyent, Inc. and other Node contributors.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a
@@ -23,7 +25,7 @@
 const { mustCall, mustNotCall, mustCallAtLeast } = require('../common');
 const assert = require('assert');

-const { methods, HTTPParser } = require('_http_common');
+const { methods, HTTPParser } = require('internal/http/common');
 const { REQUEST, RESPONSE } = HTTPParser;

 const kOnHeaders = HTTPParser.kOnHeaders | 0;
diff --git a/test/parallel/test-http-server-async-dispose.js b/test/parallel/test-http-server-async-dispose.js
index 8af11dcb7b0..29abde5bb8a 100644
--- a/test/parallel/test-http-server-async-dispose.js
+++ b/test/parallel/test-http-server-async-dispose.js
@@ -1,8 +1,10 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';
 const common = require('../common');
 const assert = require('assert');
 const { createServer } = require('http');
-const { kConnectionsCheckingInterval } = require('_http_server');
+const { kConnectionsCheckingInterval } = require('internal/http/server');

 const server = createServer();

diff --git a/test/parallel/test-http-server-clear-timer.js b/test/parallel/test-http-server-clear-timer.js
index 00a7b9f73e5..78e7208c57f 100644
--- a/test/parallel/test-http-server-clear-timer.js
+++ b/test/parallel/test-http-server-clear-timer.js
@@ -1,8 +1,10 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';
 const common = require('../common');
 const http = require('http');
 const assert = require('assert');
-const { kConnectionsCheckingInterval } = require('_http_server');
+const { kConnectionsCheckingInterval } = require('internal/http/server');

 let i = 0;
 let timer;
diff --git a/test/parallel/test-http-server-close-destroy-timeout.js b/test/parallel/test-http-server-close-destroy-timeout.js
index b1138ee36d5..b32d5fcdd6b 100644
--- a/test/parallel/test-http-server-close-destroy-timeout.js
+++ b/test/parallel/test-http-server-close-destroy-timeout.js
@@ -1,8 +1,11 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';
+
 const common = require('../common');
 const assert = require('assert');
 const { createServer } = require('http');
-const { kConnectionsCheckingInterval } = require('_http_server');
+const { kConnectionsCheckingInterval } = require('internal/http/server');

 const server = createServer(function(req, res) {});
 server.listen(0, common.mustCall(function() {
diff --git a/test/parallel/test-http-server-options-highwatermark.js b/test/parallel/test-http-server-options-highwatermark.js
index 9082550679d..f317d14810f 100644
--- a/test/parallel/test-http-server-options-highwatermark.js
+++ b/test/parallel/test-http-server-options-highwatermark.js
@@ -3,7 +3,7 @@
 const common = require('../common');
 const assert = require('assert');
 const http = require('http');
-const { kHighWaterMark } = require('_http_outgoing');
+const { kHighWaterMark } = require('internal/http/outgoing');

 const { getDefaultHighWaterMark } = require('internal/streams/state');

diff --git a/test/parallel/test-http-set-max-idle-http-parser.js b/test/parallel/test-http-set-max-idle-http-parser.js
index d935823a1ba..1d196174304 100644
--- a/test/parallel/test-http-set-max-idle-http-parser.js
+++ b/test/parallel/test-http-set-max-idle-http-parser.js
@@ -1,7 +1,9 @@
+// Flags: --expose-internals --no-warnings
+
 'use strict';
 require('../common');
 const assert = require('assert');
-const httpCommon = require('_http_common');
+const httpCommon = require('internal/http/common');
 const http = require('http');

 [Symbol(), {}, [], () => {}, 1n, true, '1', null, undefined].forEach((value) => {
diff --git a/test/parallel/test-https-server-async-dispose.js b/test/parallel/test-https-server-async-dispose.js
index 93b5cb74bbd..1cb3748a028 100644
--- a/test/parallel/test-https-server-async-dispose.js
+++ b/test/parallel/test-https-server-async-dispose.js
@@ -1,3 +1,4 @@
+// Flags: --expose-internals --no-warnings
 'use strict';

 const common = require('../common');
@@ -7,7 +8,7 @@ if (!common.hasCrypto)

 const assert = require('assert');
 const { createServer } = require('https');
-const { kConnectionsCheckingInterval } = require('_http_server');
+const { kConnectionsCheckingInterval } = require('internal/http/server');

 const server = createServer();

diff --git a/test/parallel/test-https-server-close-destroy-timeout.js b/test/parallel/test-https-server-close-destroy-timeout.js
index e876721f610..a99e6f837b7 100644
--- a/test/parallel/test-https-server-close-destroy-timeout.js
+++ b/test/parallel/test-https-server-close-destroy-timeout.js
@@ -1,3 +1,4 @@
+// Flags: --expose-internals --no-warnings
 'use strict';
 const common = require('../common');
 const assert = require('assert');
@@ -6,7 +7,7 @@ if (!common.hasCrypto) {
 }

 const { createServer } = require('https');
-const { kConnectionsCheckingInterval } = require('_http_server');
+const { kConnectionsCheckingInterval } = require('internal/http/server');

 const fixtures = require('../common/fixtures');

diff --git a/test/parallel/test-outgoing-message-pipe.js b/test/parallel/test-outgoing-message-pipe.js
index 049b41c9aaa..8b4632ebeeb 100644
--- a/test/parallel/test-outgoing-message-pipe.js
+++ b/test/parallel/test-outgoing-message-pipe.js
@@ -1,7 +1,7 @@
 'use strict';
 require('../common');
 const assert = require('assert');
-const OutgoingMessage = require('_http_outgoing').OutgoingMessage;
+const OutgoingMessage = require('http').OutgoingMessage;

 // Verify that an error is thrown upon a call to `OutgoingMessage.pipe`.

diff --git a/test/parallel/test-warn-http-agent-deprecation.js b/test/parallel/test-warn-http-agent-deprecation.js
new file mode 100644
index 00000000000..3a9456ad7f0
--- /dev/null
+++ b/test/parallel/test-warn-http-agent-deprecation.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const common = require('../common');
+
+// _http_agent is deprecated.
+
+common.expectWarning('DeprecationWarning',
+                     'The _http_agent module is deprecated. Use `node:http` instead.', 'DEP0199');
+
+require('_http_agent');
diff --git a/test/parallel/test-warn-http-client-deprecation.js b/test/parallel/test-warn-http-client-deprecation.js
new file mode 100644
index 00000000000..90bb02df8c7
--- /dev/null
+++ b/test/parallel/test-warn-http-client-deprecation.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const common = require('../common');
+
+// _http_client is deprecated.
+
+common.expectWarning('DeprecationWarning',
+                     'The _http_client module is deprecated. Use `node:http` instead.', 'DEP0199');
+
+require('_http_client');
diff --git a/test/parallel/test-warn-http-common-deprecation.js b/test/parallel/test-warn-http-common-deprecation.js
new file mode 100644
index 00000000000..9e0e18e4919
--- /dev/null
+++ b/test/parallel/test-warn-http-common-deprecation.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const common = require('../common');
+
+// _http_common is deprecated.
+
+common.expectWarning('DeprecationWarning',
+                     'The _http_common module is deprecated.', 'DEP0199');
+
+require('_http_common');
diff --git a/test/parallel/test-warn-http-incoming-deprecation.js b/test/parallel/test-warn-http-incoming-deprecation.js
new file mode 100644
index 00000000000..3b96ea9990b
--- /dev/null
+++ b/test/parallel/test-warn-http-incoming-deprecation.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const common = require('../common');
+
+// _http_incoming is deprecated.
+
+common.expectWarning('DeprecationWarning',
+                     'The _http_incoming module is deprecated. Use `node:http` instead.', 'DEP0199');
+
+require('_http_incoming');
diff --git a/test/parallel/test-warn-http-outgoing-deprecation.js b/test/parallel/test-warn-http-outgoing-deprecation.js
new file mode 100644
index 00000000000..7af44ee92b9
--- /dev/null
+++ b/test/parallel/test-warn-http-outgoing-deprecation.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const common = require('../common');
+
+// _http_outgoing is deprecated.
+
+common.expectWarning('DeprecationWarning',
+                     'The _http_outgoing module is deprecated. Use `node:http` instead.', 'DEP0199');
+
+require('_http_outgoing');
diff --git a/test/parallel/test-warn-http-server-deprecation.js b/test/parallel/test-warn-http-server-deprecation.js
new file mode 100644
index 00000000000..db14bc5ba3c
--- /dev/null
+++ b/test/parallel/test-warn-http-server-deprecation.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const common = require('../common');
+
+// _http_server is deprecated.
+
+common.expectWarning('DeprecationWarning',
+                     'The _http_server module is deprecated. Use `node:http` instead.', 'DEP0199');
+
+require('_http_server');
diff --git a/test/sequential/test-async-wrap-getasyncid.js b/test/sequential/test-async-wrap-getasyncid.js
index a2ec09775ce..2cb15e144ee 100644
--- a/test/sequential/test-async-wrap-getasyncid.js
+++ b/test/sequential/test-async-wrap-getasyncid.js
@@ -183,7 +183,7 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check


 {
-  const { HTTPParser } = require('_http_common');
+  const { HTTPParser } = require('internal/http/common');
   const parser = new HTTPParser();
   testUninitialized(parser, 'HTTPParser');
   parser.initialize(HTTPParser.REQUEST, {});
diff --git a/test/sequential/test-http-regr-gh-2928.js b/test/sequential/test-http-regr-gh-2928.js
index f6a9e160328..aa0f15167b6 100644
--- a/test/sequential/test-http-regr-gh-2928.js
+++ b/test/sequential/test-http-regr-gh-2928.js
@@ -1,11 +1,12 @@
+// Flags: --expose-internals --no-warnings
 // This test is designed to fail with a segmentation fault in Node.js 4.1.0 and
 // execute without issues in Node.js 4.1.1 and up.

 'use strict';
 const common = require('../common');
 const assert = require('assert');
-const httpCommon = require('_http_common');
-const { HTTPParser } = require('_http_common');
+const httpCommon = require('internal/http/common');
+const { HTTPParser } = require('internal/http/common');
 const net = require('net');

 httpCommon.parsers.max = 50;