@@ -31,10 +31,23 @@ export function getUrl(req: IncomingMessage | Http2ServerRequest, origin: string
3131 return normalizeUrl ( ( req as any ) . originalUrl || req . url || '/' , origin ) ;
3232}
3333
34- // when the user refreshes or cancels the stream there will be an error
35- function isIgnoredError ( message = '' ) {
36- const ignoredErrors = [ 'The stream has been destroyed' , 'write after end' ] ;
37- return ignoredErrors . some ( ( ignored ) => message . includes ( ignored ) ) ;
34+ // Client disconnects are expected during streaming SSR: the socket is gone,
35+ // so the adapter should stop writing instead of surfacing them as app errors.
36+ function isClientAbortWriteError ( error : unknown ) {
37+ if ( ! ( error instanceof Error ) ) {
38+ return false ;
39+ }
40+ const code = ( error as Error & { code ?: string } ) . code ;
41+ if (
42+ code === 'EPIPE' ||
43+ code === 'ECONNRESET' ||
44+ code === 'ERR_STREAM_DESTROYED' ||
45+ code === 'ERR_STREAM_WRITE_AFTER_END'
46+ ) {
47+ return true ;
48+ }
49+
50+ return false ;
3851}
3952
4053// ensure no HTTP/2-specific headers are being set
@@ -46,20 +59,39 @@ export function normalizeUrl(url: string, base: string) {
4659
4760function createNodeResponseSink ( res : ServerResponse ) {
4861 let closed = res . closed || res . destroyed ;
62+ let responseError : unknown ;
63+ let resolveClosed : ( ) => void ;
4964 const closedPromise = closed
5065 ? Promise . resolve ( )
5166 : new Promise < void > ( ( resolve ) => {
67+ resolveClosed = resolve ;
5268 res . once ( 'close' , ( ) => {
5369 closed = true ;
5470 resolve ( ) ;
5571 } ) ;
5672 } ) ;
5773
74+ res . on ( 'error' , ( error ) => {
75+ if ( responseError === undefined ) {
76+ responseError = error ;
77+ }
78+ if ( ! closed ) {
79+ closed = true ;
80+ resolveClosed ( ) ;
81+ }
82+ } ) ;
83+
84+ const shouldPropagateResponseError = ( ) =>
85+ responseError !== undefined && ! isClientAbortWriteError ( responseError ) ;
86+
5887 const write = ( chunk : Uint8Array ) => {
5988 if ( closed || res . closed || res . destroyed ) {
6089 // If the response has already been closed or destroyed (for example the client has disconnected)
6190 // then writing into it will cause an error. So just stop writing since no one
6291 // is listening.
92+ if ( shouldPropagateResponseError ( ) ) {
93+ return Promise . reject ( responseError ) ;
94+ }
6395 return ;
6496 }
6597
@@ -76,21 +108,33 @@ function createNodeResponseSink(res: ServerResponse) {
76108 setImmediate ( resolve ) ;
77109 } ;
78110
111+ const finishClosed = ( ) => {
112+ closed = true ;
113+ finish ( ) ;
114+ } ;
115+
79116 const fail = ( error : unknown ) => {
80117 if ( settled ) {
81118 return ;
82119 }
83120 settled = true ;
121+ closed = true ;
84122 reject ( error ) ;
85123 } ;
86124
87- closedPromise . then ( finish ) ;
125+ closedPromise . then ( ( ) => {
126+ if ( shouldPropagateResponseError ( ) ) {
127+ fail ( responseError ) ;
128+ return ;
129+ }
130+ finish ( ) ;
131+ } ) ;
88132
89133 try {
90134 res . write ( chunk , ( error ) => {
91135 if ( error ) {
92- if ( isIgnoredError ( error . message ) ) {
93- finish ( ) ;
136+ if ( isClientAbortWriteError ( error ) ) {
137+ finishClosed ( ) ;
94138 return ;
95139 }
96140 fail ( error ) ;
@@ -99,8 +143,8 @@ function createNodeResponseSink(res: ServerResponse) {
99143 finish ( ) ;
100144 } ) ;
101145 } catch ( error ) {
102- if ( error instanceof Error && isIgnoredError ( error . message ) ) {
103- finish ( ) ;
146+ if ( isClientAbortWriteError ( error ) ) {
147+ finishClosed ( ) ;
104148 return ;
105149 }
106150 fail ( error ) ;
@@ -110,13 +154,52 @@ function createNodeResponseSink(res: ServerResponse) {
110154
111155 const close = ( ) => {
112156 if ( closed || res . closed || res . destroyed ) {
157+ if ( shouldPropagateResponseError ( ) ) {
158+ return Promise . reject ( responseError ) ;
159+ }
113160 return ;
114161 }
115162
116- return new Promise < void > ( ( resolve ) => {
117- res . end ( ( ) => {
163+ return new Promise < void > ( ( resolve , reject ) => {
164+ let settled = false ;
165+
166+ const finish = ( ) => {
167+ if ( settled ) {
168+ return ;
169+ }
170+ settled = true ;
171+ closed = true ;
118172 resolve ( ) ;
173+ } ;
174+
175+ const fail = ( error : unknown ) => {
176+ if ( settled ) {
177+ return ;
178+ }
179+ settled = true ;
180+ closed = true ;
181+ reject ( error ) ;
182+ } ;
183+
184+ closedPromise . then ( ( ) => {
185+ if ( shouldPropagateResponseError ( ) ) {
186+ fail ( responseError ) ;
187+ return ;
188+ }
189+ finish ( ) ;
119190 } ) ;
191+
192+ try {
193+ res . end ( ( ) => {
194+ finish ( ) ;
195+ } ) ;
196+ } catch ( error ) {
197+ if ( isClientAbortWriteError ( error ) ) {
198+ finish ( ) ;
199+ return ;
200+ }
201+ fail ( error ) ;
202+ }
120203 } ) ;
121204 } ;
122205
@@ -161,16 +244,20 @@ export async function fromNodeHttp(
161244
162245 const body = req . method === 'HEAD' || req . method === 'GET' ? undefined : getRequestBody ( ) ;
163246 const controller = new AbortController ( ) ;
247+ const abort = ( ) => {
248+ controller . abort ( ) ;
249+ } ;
164250 const options = {
165251 method : req . method ,
166252 headers : requestHeaders ,
167253 body : body as any ,
168254 signal : controller . signal ,
169255 duplex : 'half' as any ,
170256 } ;
171- res . on ( 'close' , ( ) => {
172- controller . abort ( ) ;
173- } ) ;
257+ req . on ( 'aborted' , abort ) ;
258+ req . on ( 'error' , abort ) ;
259+ res . on ( 'close' , abort ) ;
260+ res . on ( 'error' , abort ) ;
174261 const serverRequestEv : ServerRequestEvent < boolean > = {
175262 mode,
176263 url,
@@ -180,7 +267,7 @@ export async function fromNodeHttp(
180267 return process . env [ key ] ;
181268 } ,
182269 } ,
183- getWritableStream : ( status , headers , cookies ) => {
270+ getWritableStream : ( status , headers , cookies , resolve ) => {
184271 res . statusCode = status ;
185272 const sink = createNodeResponseSink ( res ) ;
186273
@@ -199,6 +286,8 @@ export async function fromNodeHttp(
199286 console . error ( err ) ;
200287 }
201288
289+ resolve ( true ) ;
290+
202291 return new WritableStream < Uint8Array > ( {
203292 write ( chunk ) {
204293 return sink . write ( chunk ) ;
0 commit comments