Compare commits

...

2 Commits

Author SHA1 Message Date
autofix-ci[bot]
f1c5c6b802 [autofix.ci] apply automated fixes 2025-08-05 19:10:38 +00:00
Claude Bot
fd2720f076 feat: add process.binding('http_parser') foundation to node:http client
Add initial support for using process.binding('http_parser') in the
node:http client implementation to match Node.js internals. This is
a conservative foundation that:

- Imports HTTPParser and related utilities from _http_common
- Adds lenient parsing constants matching Node.js implementation
- Implements parserOnIncomingClient callback for response handling
- Adds _useHttpParser flag (defaults to false) for gradual rollout
- Provides _initParser and _cleanupParser methods for parser lifecycle
- Maintains full backward compatibility with existing fetch-based implementation

The changes are inactive by default (_useHttpParser: false) ensuring
no disruption to existing functionality while providing the foundation
for migrating to Node.js-compatible HTTP parsing.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-05 19:06:06 +00:00

View File

@@ -47,6 +47,7 @@ const {
const { Agent, NODE_HTTP_WARNING } = require("node:_http_agent");
const { IncomingMessage } = require("node:_http_incoming");
const { OutgoingMessage } = require("node:_http_outgoing");
const { freeParser, parsers, HTTPParser, isLenient, prepareError } = require("node:_http_common");
const globalReportError = globalThis.reportError;
const setTimeout = globalThis.setTimeout;
@@ -55,6 +56,29 @@ const fetch = Bun.fetch;
const { URL } = globalThis;
// HTTP parser constants for lenient parsing
const kLenientNone = 0;
const kLenientHeaders = 1 << 0;
const kLenientChunkedLength = 1 << 1;
const kLenientTransferEncoding = 1 << 2;
const kLenientVersion = 1 << 3;
const kLenientDataAfterClose = 1 << 4;
const kLenientOptionalLFAfterCR = 1 << 5;
const kLenientOptionalCRLFAfterChunk = 1 << 6;
const kLenientOptionalCRBeforeLF = 1 << 7;
const kLenientSpacesAfterChunkSize = 1 << 8;
const kLenientAll =
kLenientHeaders |
kLenientChunkedLength |
kLenientTransferEncoding |
kLenientVersion |
kLenientDataAfterClose |
kLenientOptionalLFAfterCR |
kLenientOptionalCRLFAfterChunk |
kLenientOptionalCRBeforeLF |
kLenientSpacesAfterChunkSize;
// Primordials
const ObjectAssign = Object.assign;
const RegExpPrototypeExec = RegExp.prototype.exec;
@@ -67,6 +91,54 @@ function emitErrorEventNT(self, err) {
}
}
function statusIsInformational(status) {
return status >= 100 && status < 200;
}
// Parser callback for handling incoming responses (similar to Node.js implementation)
function parserOnIncomingClient(res, shouldKeepAlive) {
const socket = this.socket;
const req = socket._httpMessage;
if (req.res) {
// We already have a response object, something is wrong
socket.destroy();
return 0;
}
req.res = res;
// Handle upgrade responses
if (res.upgrade) {
return 2; // Skip body and treat as Upgrade
}
// Handle CONNECT method responses
if (req.method === "CONNECT") {
res.upgrade = true;
return 2; // Skip body and treat as Upgrade
}
// Handle informational responses (1xx status codes)
if (statusIsInformational(res.statusCode)) {
req.res = null; // Clear res so we can handle the final response
if (res.statusCode === 100) {
req.emit("continue");
}
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
}
return 0; // No special treatment
}
function ClientRequest(input, options, cb) {
if (!(this instanceof ClientRequest)) {
return new (ClientRequest as any)(input, options, cb);
@@ -783,6 +855,10 @@ function ClientRequest(input, options, cb) {
$debug(`new ClientRequest: ${this[kMethod]} ${this[kProtocol]}//${this[kHost]}:${this[kPort]}${this[kPath]}`);
// Flag to control whether to use parser-based implementation
// For now, default to false (use existing fetch implementation) for conservative rollout
this._useHttpParser = options._useHttpParser ?? false;
// if (
// method === "GET" ||
// method === "HEAD" ||
@@ -805,6 +881,12 @@ function ClientRequest(input, options, cb) {
this[kHost] = host;
this[kProtocol] = protocol;
// Initialize parser-related properties
if (this._useHttpParser) {
this.parser = null;
this.socket = null;
}
if (options.timeout !== undefined) {
const timeout = getTimerDuration(options.timeout, "timeout");
this.timeout = timeout;
@@ -925,6 +1007,45 @@ function ClientRequest(input, options, cb) {
this.removeAllListeners("timeout");
}
};
// Method to initialize HTTP parser when socket is available (Node.js style)
this._initParser = function (socket) {
if (!this._useHttpParser) return;
const parser = parsers.alloc();
const lenient = this.insecureHTTPParser === undefined ? isLenient() : this.insecureHTTPParser;
// Initialize parser for response parsing
parser.initialize(
HTTPParser.RESPONSE,
undefined, // asyncResource - not implemented yet
this.maxHeaderSize || 0,
lenient ? kLenientAll : kLenientNone,
);
parser.socket = socket;
parser.outgoing = this;
this.parser = parser;
socket.parser = parser;
socket._httpMessage = this;
if (typeof this.maxHeadersCount === "number") {
parser.maxHeaderPairs = this.maxHeadersCount << 1;
}
parser.joinDuplicateHeaders = this.joinDuplicateHeaders;
parser.onIncoming = parserOnIncomingClient;
};
// Method to clean up HTTP parser
this._cleanupParser = function (socket) {
if (!this._useHttpParser || !this.parser) return;
freeParser(this.parser, this, socket);
this.parser = null;
if (socket) {
socket.parser = null;
}
};
}
const ClientRequestPrototype = {