Skip to content
17 changes: 7 additions & 10 deletions src/js/node/_http_incoming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,11 +389,10 @@ IncomingMessage.prototype._read = function _read(_n) {

const bodyReadState = handle.hasBody;

if (
(bodyReadState & NodeHTTPBodyReadState.done) !== 0 ||
bodyReadState === NodeHTTPBodyReadState.none ||
this._dumped
) {
// A dumped request (res.end() before the body was read) is not complete
// here: like Node, it only ends once the rest of its body has actually
// arrived, which onDataIncomingMessage reports with isLast.
Comment thread
robobun marked this conversation as resolved.
Outdated
if ((bodyReadState & NodeHTTPBodyReadState.done) !== 0 || bodyReadState === NodeHTTPBodyReadState.none) {
emitEOFIncomingMessage(this);
}

Expand All @@ -406,7 +405,6 @@ IncomingMessage.prototype._read = function _read(_n) {

if (!handle.ondata) {
handle.ondata = onDataIncomingMessage.bind(this);
handle.hasCustomOnData = false;
}
};

Expand Down Expand Up @@ -759,10 +757,9 @@ IncomingMessage.prototype._dump = function _dump() {
// If there is buffered data, it may trigger 'data' events.
// Remove 'data' event listeners explicitly.
this.removeAllListeners("data");
const handle = this[kHandle];
if (handle) {
handle.ondata = undefined;
}
// The native ondata callback stays armed: onDataIncomingMessage drops the
// chunks of a dumped request and still delivers the body's fin, which is
// what completes the request (see _read).
Comment thread
robobun marked this conversation as resolved.
Outdated
this.resume();
}
};
Expand Down
22 changes: 15 additions & 7 deletions src/js/node/_http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,10 +898,12 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort
handle.onabort = socket[kBoundOnAbort] ??= onServerRequestEvent.bind(socket);
// Like Node's connectionListener -> parserOnBody: body bytes flow into
// the IncomingMessage as they arrive, and the push callback readStop()s
// the socket (which emits 'pause' on it) once the buffer fills.
// the socket (which emits 'pause' on it) once the buffer fills. The
// callback stays armed until the body's fin chunk even if the response
// is ended first (native keeps delivering), so req 'end'/'close' and
// req.complete track the body actually being received, like Node.
Comment thread
robobun marked this conversation as resolved.
Outdated
if (hasBody) {
handle.ondata = onDataIncomingMessage.bind(http_req);
handle.hasCustomOnData = false;
}
drainMicrotasks();

Expand Down Expand Up @@ -2463,10 +2465,17 @@ function stopServerResponsePerf(this: any) {
// arm keep-alive) runs first because onResponseFinishHandleSocket's guards
// read pre-detach state, then detach the socket and advance the pipeline.
function emitResponseFinish() {
const req = this.req;
// 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.
Comment thread
robobun marked this conversation as resolved.
Outdated
if (req && !req._consuming && !req._readableState?.resumeScheduled) {
req._dump();
}
// req.socket is nulled by the stream destroyer (pipeline/compose cleanup);
// the response's own socket (set by assignSocket, cleared only by
// detachSocket) still references the connection then.
const socket = this.req?.socket ?? this.socket;
const socket = req?.socket ?? this.socket;
onResponseFinishHandleSocket(socket?.server, socket, this);
// The dispatcher detached a synchronously-finished response itself;
// advancing the pipeline again here would skip a queued response.
Expand Down Expand Up @@ -3248,10 +3257,9 @@ ServerResponse.prototype.end = function (chunk, encoding, callback) {
}
}
this._header = " ";
const req = this.req;
if (!req._consuming && !req?._readableState?.resumeScheduled) {
req._dump();
}
// The unread-body dump decision is made on 'finish' (emitResponseFinish),
// like Node.js's resOnFinish: a consumer attached in the same tick as
// res.end() still gets the body.
Comment thread
robobun marked this conversation as resolved.
Outdated
// The socket is NOT detached here: like Node.js, res.socket stays assigned
// until the response 'finish' machinery runs (the dispatcher detaches it
// right after a synchronously-finished handler returns, or via its 'finish'
Expand Down
Loading