Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/js/node/_http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,26 @@ const MathFloor = Math.floor;

let cluster;

// diagnostics_channel channels for the HTTP server. Mirrors Node's
// lib/_http_server.js. Inactive channels are no-ops until someone subscribes.
const dc = require("node: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");

function emitResponseFinishChannel(this: { req; res; socket; server }) {
// Re-checked here (not at listener-attach) so subscribers that attach
// between request-arrival and response-finish are still observed.
if (onResponseFinishChannel.hasSubscribers) {
onResponseFinishChannel.publish({
request: this.req,
response: this.res,
socket: this.socket,
server: this.server,
});
}
}

function emitCloseServer(self: Server) {
callCloseCallback(self);
self.emit("close");
Expand Down Expand Up @@ -643,6 +663,14 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort
if (!requestShouldKeepAlive(http_req)) {
http_res[kMustCloseConnection] = true;
}

// Attached unconditionally to match Node's resOnFinish; the
// hasSubscribers check happens in emitResponseFinishChannel. Registered
// before endSocketOnFinishIfNeeded so the publish observes the socket
// before its writable side is ended (as Node's resOnFinish publishes
// before socket.destroySoon()).
http_res.on("finish", emitResponseFinishChannel.bind({ req: http_req, res: http_res, socket, server }));

http_res.once("finish", endSocketOnFinishIfNeeded.bind(undefined, socket, http_res));

if (hasObserver("http")) {
Expand Down Expand Up @@ -737,6 +765,11 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort
http_req._dumpAndCloseReadable();
}

// Match Node's parserOnIncoming: publish once, before branching, for
// non-upgrade requests (fires on 503/checkContinue/417/normal paths).
if (!is_upgrade && onRequestStartChannel.hasSubscribers) {
onRequestStartChannel.publish({ request: http_req, response: http_res, socket, server });
}
Comment thread
claude[bot] marked this conversation as resolved.
if (reachedRequestsLimit) {
server.emit("dropRequest", http_req, socket);
http_res.writeHead(503);
Expand Down Expand Up @@ -1482,6 +1515,12 @@ function ServerResponse(req, options): void {
this.statusCode = 200;
this.statusMessage = undefined;
this.chunkedEncoding = false;

// Publish response.created from the constructor (matches Node) so it also
// fires for direct `new ServerResponse(req)` use — light-my-request etc.
if (onResponseCreatedChannel.hasSubscribers) {
onResponseCreatedChannel.publish({ request: req, response: this });
}
Comment thread
robobun marked this conversation as resolved.
}
$toClass(ServerResponse, "ServerResponse", OutgoingMessage);

Expand Down
22 changes: 22 additions & 0 deletions src/js/node/http2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5469,6 +5469,15 @@
const ServerResponseClass = http1Options.ServerResponse || http.ServerResponse;
const keepAliveTimeout = typeof server.keepAliveTimeout === "number" ? server.keepAliveTimeout : 5000;

// http.server.request.start / http.server.response.finish for the HTTP/1
// fallback path (allowHTTP1). response.created is published by the
// ServerResponse constructor; publish the other two here so subscribers see
// the same three events Node fires on this path. Same channel objects as
// node:_http_server (keyed by name in diagnostics_channel's registry).
const dc = require("node:diagnostics_channel");
const onRequestStartChannel = dc.channel("http.server.request.start");
const onResponseFinishChannel = dc.channel("http.server.response.finish");

Check warning on line 5479 in src/js/node/http2.ts

View check run for this annotation

Claude / Claude Code Review

Redundant require shadows module-level dc; channels re-resolved per connection

The `const dc = require("node:diagnostics_channel")` at line 5477 shadows the module-level `dc` already declared at line 48, and `onRequestStartChannel` / `onResponseFinishChannel` are re-resolved on every HTTP/1 connection instead of once at module load. Drop line 5477 and hoist the two channel constants up next to the existing 12 http2 channel constants at lines 51-62 — same pattern this file already uses, and the same way `_http_server.ts` declares them.
Comment thread
robobun marked this conversation as resolved.
Outdated

const connections = (server[kHttp1Connections] ??= new SafeSet());
connections.add(socket);
socket[kHttp1ActiveRequests] = 0;
Expand Down Expand Up @@ -5524,6 +5533,9 @@
};

const res = new ServerResponseClass(req);
// Stable reference for the diagnostics closure: the outer `req` is reused
// across pipelined requests on this connection.
const request = req;
const handle = createHttp1FallbackResponseHandle(socket, shouldKeepAlive, keepAliveTimeout);
handle.onfinished = function () {
socket[kHttp1ActiveRequests] = Math.max(0, (socket[kHttp1ActiveRequests] || 1) - 1);
Expand All @@ -5534,6 +5546,16 @@
res[kHttp1ResponseHandle] = handle;
res.assignSocket(socket);

// Attached unconditionally to match Node's resOnFinish; the hasSubscribers
// check happens inside.
res.on("finish", () => {
if (onResponseFinishChannel.hasSubscribers) {
onResponseFinishChannel.publish({ request, response: res, socket, server });
}
});
if (onRequestStartChannel.hasSubscribers) {
onRequestStartChannel.publish({ request, response: res, socket, server });
}
server.emit("request", req, res);
return 0;
};
Expand Down
Loading
Loading