Repository navigation
node:http: emit 'pause' on req.socket once an unread body fills the IncomingMessage buffer #34740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Jarred-Sumner
merged 12 commits into
main
from
farm/7605e109/http-server-req-socket-pause
Jul 20, 2026
Merged
Changes from 7 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
20f02cc
node:http: push request body eagerly and readStop the socket on backp…
robobun 7eb2f2a
NodeHTTPResponse: guard do_resume from re-arming onData/onTimeout onc…
robobun e2b534f
node:http: skip readStop for upgrade-with-body; move pause tests to t…
robobun c8aa589
ci: retrigger
robobun aafc38d
NodeHTTPResponse: apply the same body_read_state guard to do_pause
robobun a315c2d
NodeHTTPResponse: let do_pause reach pause_socket() when body is not …
robobun 85b9e06
node:http: skip readStop once the socket's writable side has ended
robobun 9839ada
NodeHTTPResponse: fold IS_DATA_BUFFERED_DURING_PAUSE_LAST into the bo…
robobun 235d1e8
node:http: pause the fd around socketHandle.end()'s shutdown; readSta…
robobun 637dd2c
Move onDataIncomingMessage to internal/http; declare us_socket_pause;…
robobun c34fb04
_http_incoming: import onDataIncomingMessage from internal/http inste…
robobun f996a40
test: assert both keep-alive requests reached the same server socket
robobun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| /** | ||
| * All tests in this file should also run in Node.js. | ||
| */ | ||
| import { expect, it } from "bun:test"; | ||
| import { once } from "node:events"; | ||
| import { createServer, request } from "node:http"; | ||
| import type { AddressInfo } from "node:net"; | ||
|
|
||
| it("req.socket emits 'pause' once an unread request body fills the IncomingMessage buffer", async () => { | ||
| // Node's test-http-no-read-no-dump: a handler that never reads the body sees | ||
| // 'pause' on req.connection once the IncomingMessage push() backpressures. | ||
| const { promise: paused, resolve: onPause, reject } = Promise.withResolvers<number>(); | ||
| const server = createServer((req, res) => { | ||
| req.connection!.on("pause", () => { | ||
| onPause((req as any).readableLength); | ||
| res.end("ok"); | ||
| }); | ||
| res.writeHead(200); | ||
| res.flushHeaders(); | ||
| }); | ||
| try { | ||
| await once(server.listen(0), "listening"); | ||
| const port = (server.address() as AddressInfo).port; | ||
| const post = request({ method: "POST", port }); | ||
| post.on("error", reject); | ||
| post.flushHeaders(); | ||
| // One body chunk at the default highWaterMark: the first parserOnBody push | ||
| // returns false and Node's readStop pauses the socket. | ||
| post.write(Buffer.alloc(64 * 1024, "X")); | ||
| const buffered = await paused; | ||
| expect(buffered).toBeGreaterThan(0); | ||
| post.destroy(); | ||
| } finally { | ||
| server.closeAllConnections(); | ||
| server.close(); | ||
| } | ||
| }); | ||
|
|
||
| it("body reading from 'pause' still delivers every byte and 'end'", async () => { | ||
| // A handler that keys its slow-reader flow off the socket's 'pause' event | ||
| // (the pattern from Node's test-http-no-read-no-dump) must still be able to | ||
| // drain the full body once it attaches a 'data' listener. | ||
| const { promise: done, resolve, reject } = Promise.withResolvers<{ pauses: number; received: number }>(); | ||
| const server = createServer((req, res) => { | ||
| let pauses = 0; | ||
| let received = 0; | ||
| req.connection!.on("pause", () => { | ||
| pauses++; | ||
| if (pauses > 1) return; | ||
| req.on("data", chunk => (received += chunk.length)); | ||
| req.on("end", () => { | ||
| res.end("ok"); | ||
| resolve({ pauses, received }); | ||
| }); | ||
| }); | ||
| res.writeHead(200); | ||
| res.flushHeaders(); | ||
| }); | ||
| try { | ||
| await once(server.listen(0), "listening"); | ||
| const port = (server.address() as AddressInfo).port; | ||
| const payload = 256 * 1024; | ||
| const post = request({ method: "POST", port }); | ||
| post.on("error", reject); | ||
| post.flushHeaders(); | ||
| await once(post, "response"); | ||
| post.end(Buffer.alloc(payload, "X")); | ||
| const { pauses, received } = await done; | ||
| expect(received).toBe(payload); | ||
| expect(pauses).toBeGreaterThanOrEqual(1); | ||
| } finally { | ||
| server.closeAllConnections(); | ||
| server.close(); | ||
| } | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| 'use strict'; | ||
| const common = require('../common'); | ||
| const http = require('http'); | ||
|
|
||
| let onPause = null; | ||
|
|
||
| const server = http.createServer(common.mustCallAtLeast((req, res) => { | ||
| if (req.method === 'GET') | ||
| return res.end(); | ||
|
|
||
| res.writeHead(200); | ||
| res.flushHeaders(); | ||
|
|
||
| req.on('close', common.mustCall(() => { | ||
| req.on('end', common.mustNotCall()); | ||
| })); | ||
|
|
||
| req.connection.on('pause', () => { | ||
| res.end(); | ||
| onPause(); | ||
| }); | ||
| })).listen(0, common.mustCall(() => { | ||
| const agent = new http.Agent({ | ||
| maxSockets: 1, | ||
| keepAlive: true | ||
| }); | ||
|
|
||
| const port = server.address().port; | ||
|
|
||
| const post = http.request({ | ||
| agent, | ||
| method: 'POST', | ||
| port, | ||
| }, common.mustCall((res) => { | ||
| res.resume(); | ||
|
|
||
| post.write(Buffer.alloc(64 * 1024).fill('X')); | ||
| onPause = () => { | ||
| post.end('something'); | ||
| }; | ||
| })); | ||
|
|
||
| // What happens here is that the server `end`s the response before we send | ||
| // `something`, and the client thought that this is a green light for sending | ||
| // next GET request | ||
| post.write('initial'); | ||
|
|
||
| http.request({ | ||
| agent, | ||
| method: 'GET', | ||
| port, | ||
| }, common.mustCall((res) => { | ||
| server.close(); | ||
| res.connection.end(); | ||
| })).end(); | ||
| })); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.