Keep the request body a plain Readable after middleware so Readable.toWeb() doesn't hang#95370
Open
UditDewan wants to merge 1 commit into
Open
Keep the request body a plain Readable after middleware so Readable.toWeb() doesn't hang#95370UditDewan wants to merge 1 commit into
UditDewan wants to merge 1 commit into
Conversation
`replaceRequestBody` grafts the buffered body stream onto the original IncomingMessage by copying its enumerable properties. The buffered stream was a `PassThrough` (a Duplex), so its writable-side internals (`_writableState` and the Writable methods) were copied onto the request. Node stream utilities such as `Readable.toWeb()` then saw an unfinished writable side and hung, so a route handler reading a POST body after middleware ran (`NextResponse.next()`) never completed. The buffered stream is only ever fed via `.push()`, so it never needs the writable side. Making it a plain `Readable` keeps the request a pure Readable and fixes the hang. Fixes vercel#95335 Co-authored-by: Baradhan-Madhu <26barum@gmail.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What's the problem?
A
POST(orPUT/PATCH) request that passes through middleware returningNextResponse.next()hangs indefinitely when the downstream handler reads the body via Node'sReadable.toWeb(). The request never completes and eventually times out.Reproduction: https://github.com/abir-taheer/next-js-readable-stream-bug
Root cause
When middleware runs,
runMiddlewareclones the request body and later callsfinalize(), which grafts the buffered stream back onto the originalIncomingMessageviareplaceRequestBody()(packages/next/src/server/body-streams.ts).replaceRequestBodycopies the buffered stream's enumerable properties onto the request.The buffered stream (
p2) was aPassThrough— aDuplex— so its writable-side internals (_writableStateplus the enumerableWritablemethods likewrite/end) were copied onto theIncomingMessage. Because that_writableState.finishedisfalse, Node stream utilities that inspect it — includingReadable.toWeb(), which usesfinished()/end-of-stream detection — treat the request as a still-open writable stream and wait forever.NextResponse.rewrite()is unaffected (it builds a new internal request and skips this path), andGET/HEADrequests are fine because there is no body to clone.The fix
p2is only ever fed with.push(), so it never needs a writable side. Making it a plainReadableinstead of aPassThroughkeeps the finalized request a pureReadable, soReadable.toWeb()(and any other duck-typing based on_writableState) behaves correctly. No behavior change for the existing consumers, which only read the stream.Testing
Added
test/unit/body-streams.test.ts, which drives the real clone →finalize()flow and asserts the finalized request:_writableStateisundefined), andReadable.toWeb()(this hangs before the fix).Fixes #95335