Skip to content

fix: cut the connection when a response body errors before its first chunk - #415

Open
eknathdj wants to merge 1 commit into
honojs:mainfrom
eknathdj:fix/errored-body-complete-empty-200
Open

eknathdj wants to merge 1 commit into
honojs:mainfrom
eknathdj:fix/errored-body-complete-empty-200

Conversation

@eknathdj

Copy link
Copy Markdown

Fixes #413

With overrideGlobalObjects: false, a handler that returns a Response whose
body stream errors before its first chunk is answered with a complete, empty
200 (Content-Length: 0). That is the reporter's repro, and it is easy to hit
in a gateway: a failing upstream turns into an empty success the client cannot
tell from a real one.

The early read in responseViaResponseObject wraps the read in
readWithoutBlocking(...).catch(...), and the catch sets done = true. That
flag means "the body has ended", so a read error was being treated as a clean
end of body — the response was written with Content-Length: 0 and ended
normally. With the default overrideGlobalObjects: true the same body cuts the
connection instead.

The fix records the error rather than treating it as end-of-body, and breaks out
of the early read when one happens. The rejected read is then handed to
writeFromReadableStreamDefaultReader, which destroys the connection — the same
teardown the default path already uses.

Verification

The reporter's repro, run against the built package on Node 24:

  • before: 200, content-length: 0, complete: true, empty body
  • after: the request errors with aborted (connection cut)

The new test in test/listener.test.ts covers this and fails on main without
the source change. It uses a captured native Response because the
overrideGlobalObjects block earlier in that file replaces globalThis.Response.

Full suite: 497 passed, 2 failed, 5 skipped. The two failures are the
/readable-stream chunk-boundary assertions in test/server.test.ts, which fail
the same way on unmodified main and are unrelated to this change.

…chunk

With overrideGlobalObjects: false, a handler returning a Response whose body
stream errors before its first chunk was answered with a complete, empty 200
(Content-Length: 0). The early read in responseViaResponseObject caught the
read rejection and set done = true - the marker for a body that has ended -
so the response was written and ended as if the stream had closed normally.
With the default overrideGlobalObjects: true the same body cuts the
connection instead.

Record the read error instead of treating it as the end of the body, and
break out of the early read when it happens, so the rejected read reaches the
existing streaming path and the connection is torn down the way the default
path already does it.

Adds a regression test for the case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A body failing before its first chunk is sent as a complete, empty 200 when overrideGlobalObjects is false

1 participant