fix(web-incoming): close downstream stream when upstream SSE aborts#103
fix(web-incoming): close downstream stream when upstream SSE aborts#103
Conversation
📝 WalkthroughWalkthroughUpdated proxy middleware to handle upstream stream aborts and errors by adding proxyRes Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(66,135,245,0.5)
participant Client
end
rect rgba(45,185,115,0.5)
participant Proxy
end
rect rgba(245,90,90,0.5)
participant Upstream
end
rect rgba(120,120,120,0.5)
participant Server
end
Client->>Proxy: HTTP request (SSE)
Proxy->>Upstream: forward request
Upstream-->>Proxy: streaming response (proxyRes)
Proxy->>Client: pipe streaming data
Upstream->>Proxy: socket destroyed / aborts
Proxy->>Proxy: proxyRes "close"/"error" handlers run
Proxy-->>Client: destroy/end client response (notify closure)
Proxy->>Server: emit "error" (only if server has listeners)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/middleware/web-incoming.ts (1)
278-286: Inconsistent error handling pattern within the same file.This handler checks
listenerCount("error") > 0before emitting, butcreateErrorHandlerat lines 141-145 emits errors unconditionally when no callback is provided. This creates inconsistent behavior—some upstream errors will be silently swallowed here while others fromcreateErrorHandlerwill throw if unhandled.Consider aligning with the existing pattern for consistency, or update both to use the guarded approach. The relevant code snippets show
ws-incoming.tsalso emits unconditionally.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/middleware/web-incoming.ts` around lines 278 - 286, The file currently guards emitting errors in the proxyRes.on("error") handler (checking server.listenerCount("error") > 0) but createErrorHandler emits errors unconditionally; to make behavior consistent, change createErrorHandler to only call server.emit("error", ...) when server.listenerCount("error") > 0 (same guard used in the proxyRes.on handler) so both the createErrorHandler function and the proxyRes.on("error") block use the same guarded emit pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/middleware/web-incoming.ts`:
- Around line 273-277: Replace the deprecated proxyRes.on("aborted", ...)
handler with a proxyRes.on("close", ...) handler that checks if the upstream
response terminated prematurely by testing !proxyRes.complete and then destroys
the downstream res if it isn't already destroyed; locate the current listener
attached to proxyRes (the variable named proxyRes in this middleware) and change
the event and condition accordingly (remove the "aborted" listener and use
"close" + !proxyRes.complete to detect premature termination).
---
Nitpick comments:
In `@src/middleware/web-incoming.ts`:
- Around line 278-286: The file currently guards emitting errors in the
proxyRes.on("error") handler (checking server.listenerCount("error") > 0) but
createErrorHandler emits errors unconditionally; to make behavior consistent,
change createErrorHandler to only call server.emit("error", ...) when
server.listenerCount("error") > 0 (same guard used in the proxyRes.on handler)
so both the createErrorHandler function and the proxyRes.on("error") block use
the same guarded emit pattern.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 88b9b518-fef8-4797-8003-18fb7b85bb3c
📒 Files selected for processing (2)
src/middleware/web-incoming.tstest/http-proxy.test.ts
93b262f to
f2ba2a9
Compare
f2ba2a9 to
65fc860
Compare
Restore unconditional server.emit("error") so unhandled proxy errors
throw instead of being silently dropped when no listener exists.
pi0
left a comment
There was a problem hiding this comment.
Core fix is solid — proxyRes.on("close") with !proxyRes.complete is the correct modern pattern for detecting premature upstream termination, and the proxyRes.on("error") handler properly destroys the downstream response.
Pushed one fix: reverted the createErrorHandler change that guarded server.emit("error") with listenerCount > 0. That change was unrelated to the SSE fix and would silently swallow errors when no listener exists (breaking Node.js EventEmitter semantics).
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #103 +/- ##
==========================================
- Coverage 96.47% 96.19% -0.29%
==========================================
Files 8 8
Lines 596 604 +8
Branches 222 225 +3
==========================================
+ Hits 575 581 +6
- Misses 21 23 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/middleware/web-incoming.ts (1)
278-286: Add null check forserverfor consistency with the rest of the file.Lines 253, 266, and 288 guard
serveraccess withif (server), but line 283 accessesserver.listenerCountdirectly. Whileserveris always passed in practice, adding the guard maintains consistency and defensive coding.♻️ Suggested fix
proxyRes.on("error", function (err) { if (!res.destroyed) { res.destroy(err); } - if (server.listenerCount("error") > 0) { + if (server && server.listenerCount("error") > 0) { server.emit("error", err, req, res, currentUrl); } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/middleware/web-incoming.ts` around lines 278 - 286, The proxyRes "error" handler currently calls server.listenerCount and server.emit without checking server; add a guard (if (server)) around the block that checks server.listenerCount and emits the error so it matches the other guards in this file — update the proxyRes.on("error", ...) handler to call res.destroy(err) as before, then only call server.listenerCount(...) and server.emit(...) when server is truthy to avoid potential null/undefined access.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/middleware/web-incoming.ts`:
- Around line 278-286: The proxyRes "error" handler currently calls
server.listenerCount and server.emit without checking server; add a guard (if
(server)) around the block that checks server.listenerCount and emits the error
so it matches the other guards in this file — update the proxyRes.on("error",
...) handler to call res.destroy(err) as before, then only call
server.listenerCount(...) and server.emit(...) when server is truthy to avoid
potential null/undefined access.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: df3afbda-63e1-44f3-9de3-6c935131335b
📒 Files selected for processing (2)
src/middleware/web-incoming.tstest/http-proxy.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- test/http-proxy.test.ts
resolves #84
Problem
When an SSE stream is terminated abruptly by the upstream server (e.g., backend crash or forced socket close), the downstream response remains open. SSE clients cannot detect the disconnection and appear stuck waiting for data.
Root Cause
The HTTP stream pipeline in
web-incominghandles normal response end events but does not explicitly propagate upstream aborted or error events to the downstream response. Without this propagation, client-side streams can remain open despite the upstream being closed.Solution
Added response lifecycle handlers in
web-incomings.tsSummary by CodeRabbit
Bug Fixes
Tests