@@ -32,7 +32,6 @@ const a: any = require('awaiting');
32
32
const request : any = require ( 'requisition' ) ;
33
33
import fs from 'fs' ;
34
34
import { Stopper } from '../../../plugin/drainHttpServer/stoppable' ;
35
- import child from 'child_process' ;
36
35
import path from 'path' ;
37
36
import type { AddressInfo } from 'net' ;
38
37
import { describe , it , expect , afterEach , beforeEach } from '@jest/globals' ;
@@ -113,7 +112,7 @@ Object.keys(schemes).forEach((schemeName) => {
113
112
const err = await a . failure (
114
113
request ( `${ schemeName } ://localhost:${ p } ` ) . agent ( scheme . agent ( ) ) ,
115
114
) ;
116
- expect ( err . message ) . toMatch ( / E C O N N R E F U S E D / ) ;
115
+ expect ( err . code ) . toMatch ( / E C O N N R E F U S E D / ) ;
117
116
expect ( closed ) . toBe ( 1 ) ;
118
117
} ) ;
119
118
@@ -135,9 +134,54 @@ Object.keys(schemes).forEach((schemeName) => {
135
134
scheme . agent ( { keepAlive : true } ) ,
136
135
) ,
137
136
) ;
138
- expect ( err . message ) . toMatch ( / E C O N N R E F U S E D / ) ;
139
- expect ( closed ) . toBe ( 0 ) ;
137
+ expect ( err . code ) . toMatch ( / E C O N N R E F U S E D / ) ;
138
+
139
+ // Node 19 (http) and 20.4+ (https) more aggressively close idle
140
+ // connections. `Stopper` is no longer needed for the purpose of closing
141
+ // idle connections in these versions. However, `Stopper` _is_ still
142
+ // useful for gracefully finishing in-flight requests within the timeout
143
+ // (and aborting requests beyond the timeout).
144
+ const isNode20 = ! ! process . version . match ( / ^ v 2 0 \. / ) ;
145
+ expect ( closed ) . toBe ( isNode20 ? 1 : 0 ) ;
140
146
} ) ;
147
+
148
+ // This test specifically added for Node 20 fails for Node 14. Just going
149
+ // to skip it since we're dropping Node 14 soon anyway.
150
+ const node14 = ! ! process . version . match ( / ^ v 1 4 \. / ) ;
151
+ ( node14 ? it . skip : it ) (
152
+ 'with unfinished requests' ,
153
+ async ( ) => {
154
+ const server = scheme . server ( async ( _req , res ) => {
155
+ res . writeHead ( 200 ) ;
156
+ res . write ( 'hi' ) ; // note lack of end()!
157
+ } ) ;
158
+ // The server will prevent itself from closing while the connection
159
+ // remains open (default no timeout). This will close the connection
160
+ // after 100ms so the test can finish.
161
+ server . setTimeout ( 100 ) ;
162
+
163
+ server . listen ( 0 ) ;
164
+ const p = port ( server ) ;
165
+
166
+ const response = await request (
167
+ `${ schemeName } ://localhost:${ p } ` ,
168
+ ) . agent ( scheme . agent ( { keepAlive : true } ) ) ;
169
+ // ensure we got the headers, etc.
170
+ expect ( response . status ) . toBe ( 200 ) ;
171
+
172
+ server . close ( ) ;
173
+ await a . event ( server , 'close' ) ;
174
+
175
+ try {
176
+ await response . text ( ) ;
177
+ } catch ( e : any ) {
178
+ expect ( e . code ) . toMatch ( / E C O N N R E S E T / ) ;
179
+ }
180
+ // ensure the expectation in the catch block is reached (+ the one above)
181
+ expect . assertions ( 2 ) ;
182
+ } ,
183
+ 35000 ,
184
+ ) ;
141
185
} ) ;
142
186
143
187
describe ( 'Stopper' , function ( ) {
@@ -159,7 +203,7 @@ Object.keys(schemes).forEach((schemeName) => {
159
203
const err = await a . failure (
160
204
request ( `${ schemeName } ://localhost:${ p } ` ) . agent ( scheme . agent ( ) ) ,
161
205
) ;
162
- expect ( err . message ) . toMatch ( / E C O N N R E F U S E D / ) ;
206
+ expect ( err . code ) . toMatch ( / E C O N N R E F U S E D / ) ;
163
207
164
208
expect ( closed ) . toBe ( 1 ) ;
165
209
expect ( gracefully ) . toBe ( true ) ;
@@ -185,7 +229,7 @@ Object.keys(schemes).forEach((schemeName) => {
185
229
scheme . agent ( { keepAlive : true } ) ,
186
230
) ,
187
231
) ;
188
- expect ( err . message ) . toMatch ( / E C O N N R E F U S E D / ) ;
232
+ expect ( err . code ) . toMatch ( / E C O N N R E F U S E D / ) ;
189
233
190
234
expect ( closed ) . toBe ( 1 ) ;
191
235
expect ( gracefully ) . toBe ( true ) ;
@@ -301,12 +345,21 @@ Object.keys(schemes).forEach((schemeName) => {
301
345
302
346
if ( schemeName === 'http' ) {
303
347
it ( 'with in-flights finishing before grace period ends' , async ( ) => {
304
- const file = path . join ( __dirname , 'stoppable' , 'server.js' ) ;
305
- const server = child . spawn ( 'node' , [ file ] ) ;
306
- const [ data ] = await a . event ( server . stdout , 'data' ) ;
307
- const port = + data . toString ( ) ;
308
- expect ( typeof port ) . toBe ( 'number' ) ;
309
- const res = await request ( `${ schemeName } ://localhost:${ port } /` ) . agent (
348
+ let stopper : Stopper ;
349
+ const killServerBarrier = resolvable ( ) ;
350
+ const server = http . createServer ( async ( _ , res ) => {
351
+ res . writeHead ( 200 ) ;
352
+ res . write ( 'hello' ) ;
353
+
354
+ await killServerBarrier ;
355
+ res . end ( 'world' ) ;
356
+ await stopper . stop ( ) ;
357
+ } ) ;
358
+ stopper = new Stopper ( server ) ;
359
+ server . listen ( 0 ) ;
360
+ const p = port ( server ) ;
361
+
362
+ const res = await request ( `${ schemeName } ://localhost:${ p } /` ) . agent (
310
363
scheme . agent ( { keepAlive : true } ) ,
311
364
) ;
312
365
let gotBody = false ;
@@ -320,13 +373,13 @@ Object.keys(schemes).forEach((schemeName) => {
320
373
expect ( gotBody ) . toBe ( false ) ;
321
374
322
375
// Tell the server that its request should finish.
323
- server . kill ( 'SIGUSR1' ) ;
376
+ killServerBarrier . resolve ( ) ;
324
377
325
378
const body = await bodyPromise ;
326
379
expect ( gotBody ) . toBe ( true ) ;
327
380
expect ( body ) . toBe ( 'helloworld' ) ;
328
381
329
- // Wait for subprocess to go away .
382
+ // Wait for server to close .
330
383
await a . event ( server , 'close' ) ;
331
384
} ) ;
332
385
}
0 commit comments