1
1
import { AbortController } from "@aws-sdk/abort-controller" ;
2
2
import { HttpRequest } from "@aws-sdk/protocol-http" ;
3
3
import { rejects } from "assert" ;
4
- import http2 , { constants , Http2Stream } from "http2" ;
4
+ import http2 , { ClientHttp2Session , ClientHttp2Stream , constants , Http2Stream } from "http2" ;
5
5
import { Duplex } from "stream" ;
6
6
import { promisify } from "util" ;
7
7
@@ -44,7 +44,20 @@ describe(NodeHttp2Handler.name, () => {
44
44
} ) ;
45
45
46
46
describe ( "without options" , ( ) => {
47
+ let createdSessions ! : ClientHttp2Session [ ] ;
48
+ const connectReal = http2 . connect ;
49
+ let connectSpy ! : jest . SpiedFunction < typeof http2 . connect > ;
50
+
47
51
beforeEach ( ( ) => {
52
+ createdSessions = [ ] ;
53
+ connectSpy = jest . spyOn ( http2 , "connect" ) . mockImplementation ( ( ...args ) => {
54
+ const session = connectReal ( ...args ) ;
55
+ jest . spyOn ( session , "ref" ) ;
56
+ jest . spyOn ( session , "unref" ) ;
57
+ createdSessions . push ( session ) ;
58
+ return session ;
59
+ } ) ;
60
+
48
61
nodeH2Handler = new NodeHttp2Handler ( ) ;
49
62
} ) ;
50
63
@@ -58,51 +71,99 @@ describe(NodeHttp2Handler.name, () => {
58
71
59
72
describe ( "number calls to http2.connect" , ( ) => {
60
73
it ( "is zero on initialization" , ( ) => {
61
- const connectSpy = jest . spyOn ( http2 , "connect" ) ;
62
74
expect ( connectSpy ) . not . toHaveBeenCalled ( ) ;
63
75
} ) ;
64
76
65
77
it ( "is one when request is made" , async ( ) => {
66
- const connectSpy = jest . spyOn ( http2 , "connect" ) ;
67
-
68
78
// Make single request.
69
- await nodeH2Handler . handle ( new HttpRequest ( getMockReqOptions ( ) ) , { } ) ;
79
+ const response = await nodeH2Handler . handle ( new HttpRequest ( getMockReqOptions ( ) ) , { } ) ;
80
+ const responseBody = response . response . body as ClientHttp2Stream ;
70
81
71
82
const authority = `${ protocol } //${ hostname } :${ port } ` ;
72
83
expect ( connectSpy ) . toHaveBeenCalledTimes ( 1 ) ;
73
84
expect ( connectSpy ) . toHaveBeenCalledWith ( authority ) ;
85
+
86
+ // Keeping node alive while request is open.
87
+ expect ( createdSessions [ 0 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
88
+ expect ( createdSessions [ 0 ] . unref ) . toHaveBeenCalledTimes ( 1 ) ;
89
+
90
+ const closed = new Promise < void > ( ( resolve ) => responseBody . once ( "close" , resolve ) ) ;
91
+ ( response . response . body as ClientHttp2Stream ) . destroy ( ) ;
92
+ await closed ;
93
+
94
+ // No longer keeping node alive
95
+ expect ( createdSessions [ 0 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
96
+ expect ( createdSessions [ 0 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
74
97
} ) ;
75
98
76
99
it ( "is one if multiple requests are made on same URL" , async ( ) => {
77
100
const connectSpy = jest . spyOn ( http2 , "connect" ) ;
78
101
79
102
// Make two requests.
80
- await nodeH2Handler . handle ( new HttpRequest ( getMockReqOptions ( ) ) , { } ) ;
81
- await nodeH2Handler . handle ( new HttpRequest ( getMockReqOptions ( ) ) , { } ) ;
103
+ const response1 = await nodeH2Handler . handle ( new HttpRequest ( getMockReqOptions ( ) ) , { } ) ;
104
+ const response1Body = response1 . response . body as ClientHttp2Stream ;
105
+ const response2 = await nodeH2Handler . handle ( new HttpRequest ( getMockReqOptions ( ) ) , { } ) ;
106
+ const response2Body = response2 . response . body as ClientHttp2Stream ;
82
107
83
108
const authority = `${ protocol } //${ hostname } :${ port } ` ;
84
109
expect ( connectSpy ) . toHaveBeenCalledTimes ( 1 ) ;
85
110
expect ( connectSpy ) . toHaveBeenCalledWith ( authority ) ;
111
+
112
+ // Keeping node alive while requests are open.
113
+ expect ( createdSessions [ 0 ] . ref ) . toHaveBeenCalledTimes ( 2 ) ;
114
+ expect ( createdSessions [ 0 ] . unref ) . toHaveBeenCalledTimes ( 1 ) ;
115
+
116
+ const closed1 = new Promise < void > ( ( resolve ) => response1Body . once ( "close" , resolve ) ) ;
117
+ response1Body . destroy ( ) ;
118
+ await closed1 ;
119
+ const closed2 = new Promise < void > ( ( resolve ) => response2Body . once ( "close" , resolve ) ) ;
120
+ response2Body . destroy ( ) ;
121
+ await closed2 ;
122
+
123
+ // No longer keeping node alive
124
+ expect ( createdSessions [ 0 ] . ref ) . toHaveBeenCalledTimes ( 2 ) ;
125
+ expect ( createdSessions [ 0 ] . unref ) . toHaveBeenCalledTimes ( 3 ) ;
86
126
} ) ;
87
127
88
128
it ( "is many if requests are made on different URLs" , async ( ) => {
89
129
const connectSpy = jest . spyOn ( http2 , "connect" ) ;
90
130
91
131
// Make first request on default URL.
92
- await nodeH2Handler . handle ( new HttpRequest ( getMockReqOptions ( ) ) , { } ) ;
132
+ const response1 = await nodeH2Handler . handle ( new HttpRequest ( getMockReqOptions ( ) ) , { } ) ;
133
+ const response1Body = response1 . response . body as ClientHttp2Stream ;
93
134
94
135
const port2 = port + 1 ;
95
136
const mockH2Server2 = createMockHttp2Server ( ) . listen ( port2 ) ;
96
137
mockH2Server2 . on ( "request" , createResponseFunction ( mockResponse ) ) ;
97
138
98
139
// Make second request on URL with port2.
99
- await nodeH2Handler . handle ( new HttpRequest ( { ...getMockReqOptions ( ) , port : port2 } ) , { } ) ;
140
+ const response2 = await nodeH2Handler . handle ( new HttpRequest ( { ...getMockReqOptions ( ) , port : port2 } ) , { } ) ;
141
+ const response2Body = response2 . response . body as ClientHttp2Stream ;
100
142
101
143
const authorityPrefix = `${ protocol } //${ hostname } ` ;
102
144
expect ( connectSpy ) . toHaveBeenCalledTimes ( 2 ) ;
103
145
expect ( connectSpy ) . toHaveBeenNthCalledWith ( 1 , `${ authorityPrefix } :${ port } ` ) ;
104
146
expect ( connectSpy ) . toHaveBeenNthCalledWith ( 2 , `${ authorityPrefix } :${ port2 } ` ) ;
105
147
mockH2Server2 . close ( ) ;
148
+
149
+ // Keeping node alive while requests are open.
150
+ expect ( createdSessions [ 0 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
151
+ expect ( createdSessions [ 0 ] . unref ) . toHaveBeenCalledTimes ( 1 ) ;
152
+ expect ( createdSessions [ 1 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
153
+ expect ( createdSessions [ 1 ] . unref ) . toHaveBeenCalledTimes ( 1 ) ;
154
+
155
+ const closed1 = new Promise < void > ( ( resolve ) => response1Body . once ( "close" , resolve ) ) ;
156
+ response1Body . destroy ( ) ;
157
+ await closed1 ;
158
+ const closed2 = new Promise < void > ( ( resolve ) => response2Body . once ( "close" , resolve ) ) ;
159
+ response2Body . destroy ( ) ;
160
+ await closed2 ;
161
+
162
+ // No longer keeping node alive
163
+ expect ( createdSessions [ 0 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
164
+ expect ( createdSessions [ 0 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
165
+ expect ( createdSessions [ 1 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
166
+ expect ( createdSessions [ 1 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
106
167
} ) ;
107
168
} ) ;
108
169
@@ -151,26 +212,44 @@ describe(NodeHttp2Handler.name, () => {
151
212
expect ( establishedConnections ) . toBe ( 3 ) ;
152
213
expect ( numRequests ) . toBe ( 3 ) ;
153
214
215
+ // Not keeping node alive
216
+ expect ( createdSessions ) . toHaveLength ( 3 ) ;
217
+ expect ( createdSessions [ 0 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
218
+ expect ( createdSessions [ 0 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
219
+ expect ( createdSessions [ 1 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
220
+ expect ( createdSessions [ 1 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
221
+ expect ( createdSessions [ 2 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
222
+ expect ( createdSessions [ 2 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
223
+
154
224
// should be able to recover from goaway after reconnecting to a server
155
225
// that doesn't send goaway, and reuse the TCP connection (Http2Session)
156
226
shouldSendGoAway = false ;
157
227
mockH2Server3 . on ( "request" , createResponseFunction ( mockResponse ) ) ;
158
- await nodeH2Handler . handle ( req , { } ) ;
159
228
const result = await nodeH2Handler . handle ( req , { } ) ;
160
229
const resultReader = result . response . body ;
161
230
231
+ // Keeping node alive
232
+ expect ( createdSessions ) . toHaveLength ( 4 ) ;
233
+ expect ( createdSessions [ 3 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
234
+ expect ( createdSessions [ 3 ] . unref ) . toHaveBeenCalledTimes ( 1 ) ;
235
+
162
236
// ...and validate that the mocked response is received
163
237
const responseBody = await new Promise ( ( resolve ) => {
164
238
const buffers = [ ] ;
165
239
resultReader . on ( "data" , ( chunk ) => buffers . push ( chunk ) ) ;
166
- resultReader . on ( "end " , ( ) => {
240
+ resultReader . on ( "close " , ( ) => {
167
241
resolve ( Buffer . concat ( buffers ) . toString ( "utf8" ) ) ;
168
242
} ) ;
169
243
} ) ;
170
244
expect ( responseBody ) . toBe ( "test" ) ;
171
245
expect ( establishedConnections ) . toBe ( 4 ) ;
172
- expect ( numRequests ) . toBe ( 5 ) ;
246
+ expect ( numRequests ) . toBe ( 4 ) ;
173
247
mockH2Server3 . close ( ) ;
248
+
249
+ // Not keeping node alive
250
+ expect ( createdSessions ) . toHaveLength ( 4 ) ;
251
+ expect ( createdSessions [ 3 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
252
+ expect ( createdSessions [ 3 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
174
253
} ) ;
175
254
176
255
it ( "handles connections destroyed by servers" , async ( ) => {
@@ -212,6 +291,15 @@ describe(NodeHttp2Handler.name, () => {
212
291
expect ( establishedConnections ) . toBe ( 3 ) ;
213
292
expect ( numRequests ) . toBe ( 3 ) ;
214
293
mockH2Server3 . close ( ) ;
294
+
295
+ // Not keeping node alive
296
+ expect ( createdSessions ) . toHaveLength ( 3 ) ;
297
+ expect ( createdSessions [ 0 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
298
+ expect ( createdSessions [ 0 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
299
+ expect ( createdSessions [ 1 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
300
+ expect ( createdSessions [ 1 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
301
+ expect ( createdSessions [ 2 ] . ref ) . toHaveBeenCalledTimes ( 1 ) ;
302
+ expect ( createdSessions [ 2 ] . unref ) . toHaveBeenCalledTimes ( 2 ) ;
215
303
} ) ;
216
304
} ) ;
217
305
@@ -380,7 +468,7 @@ describe(NodeHttp2Handler.name, () => {
380
468
const authority = `${ protocol } //${ hostname } :${ port } ` ;
381
469
// @ts -ignore: access private property
382
470
const session : ClientHttp2Session = nodeH2Handler . sessionCache . get ( authority ) [ 0 ] ;
383
- const fakeStream = new Duplex ( ) ;
471
+ const fakeStream = new Duplex ( ) as ClientHttp2Stream ;
384
472
const fakeRstCode = 1 ;
385
473
// @ts -ignore: fake result code
386
474
fakeStream . rstCode = fakeRstCode ;
@@ -405,7 +493,7 @@ describe(NodeHttp2Handler.name, () => {
405
493
const authority = `${ protocol } //${ hostname } :${ port } ` ;
406
494
// @ts -ignore: access private property
407
495
const session : ClientHttp2Session = nodeH2Handler . sessionCache . get ( authority ) [ 0 ] ;
408
- const fakeStream = new Duplex ( ) ;
496
+ const fakeStream = new Duplex ( ) as ClientHttp2Stream ;
409
497
jest . spyOn ( session , "request" ) . mockImplementation ( ( ) => fakeStream ) ;
410
498
// @ts -ignore: access private property
411
499
nodeH2Handler . sessionCache . set ( `${ protocol } //${ hostname } :${ port } ` , [ session ] ) ;
0 commit comments