undici 8.0.3 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/docs/api/Client.md +2 -0
- package/docs/docs/api/Dispatcher.md +2 -2
- package/lib/api/api-connect.js +1 -1
- package/lib/api/api-pipeline.js +2 -2
- package/lib/api/api-request.js +2 -2
- package/lib/api/api-stream.js +1 -1
- package/lib/api/api-upgrade.js +8 -2
- package/lib/api/readable.js +3 -2
- package/lib/cache/memory-cache-store.js +1 -1
- package/lib/cache/sqlite-cache-store.js +6 -4
- package/lib/core/connect.js +16 -0
- package/lib/core/constants.js +1 -24
- package/lib/core/errors.js +2 -2
- package/lib/core/request.js +17 -2
- package/lib/core/socks5-client.js +24 -9
- package/lib/core/socks5-utils.js +32 -23
- package/lib/core/util.js +28 -3
- package/lib/dispatcher/agent.js +38 -40
- package/lib/dispatcher/balanced-pool.js +21 -23
- package/lib/dispatcher/client-h1.js +34 -16
- package/lib/dispatcher/client-h2.js +400 -147
- package/lib/dispatcher/client.js +3 -2
- package/lib/dispatcher/dispatcher-base.js +18 -0
- package/lib/dispatcher/h2c-client.js +4 -4
- package/lib/dispatcher/pool-base.js +6 -6
- package/lib/dispatcher/pool.js +8 -3
- package/lib/dispatcher/proxy-agent.js +2 -0
- package/lib/dispatcher/round-robin-pool.js +5 -6
- package/lib/dispatcher/socks5-proxy-agent.js +23 -14
- package/lib/handler/cache-handler.js +1 -1
- package/lib/handler/redirect-handler.js +4 -0
- package/lib/interceptor/redirect.js +3 -3
- package/lib/llhttp/llhttp-wasm.js +1 -1
- package/lib/llhttp/llhttp_simd-wasm.js +1 -1
- package/lib/mock/mock-agent.js +8 -8
- package/lib/mock/mock-call-history.js +15 -15
- package/lib/util/cache.js +1 -1
- package/lib/web/eventsource/eventsource-stream.js +245 -150
- package/lib/web/fetch/formdata-parser.js +17 -6
- package/lib/web/fetch/index.js +38 -28
- package/lib/web/webidl/index.js +5 -5
- package/lib/web/websocket/frame.js +1 -7
- package/lib/web/websocket/permessage-deflate.js +13 -31
- package/lib/web/websocket/receiver.js +62 -22
- package/lib/web/websocket/stream/websocketstream.js +6 -5
- package/lib/web/websocket/websocket.js +6 -1
- package/package.json +1 -1
- package/types/client.d.ts +11 -0
- package/types/dispatcher.d.ts +4 -4
- package/types/header.d.ts +5 -0
- package/types/interceptors.d.ts +1 -1
- package/types/proxy-agent.d.ts +2 -2
- package/types/socks5-proxy-agent.d.ts +2 -2
- package/lib/llhttp/.gitkeep +0 -0
|
@@ -41,6 +41,12 @@ const {
|
|
|
41
41
|
const { channels } = require('../core/diagnostics.js')
|
|
42
42
|
|
|
43
43
|
const kOpenStreams = Symbol('open streams')
|
|
44
|
+
const kRequestStreamId = Symbol('request stream id')
|
|
45
|
+
const kRequestStream = Symbol('request stream')
|
|
46
|
+
const kRequestStreamCleanup = Symbol('request stream cleanup')
|
|
47
|
+
const kRequestStreamOnData = Symbol('request stream on data')
|
|
48
|
+
const kRequestStreamOnCloseError = Symbol('request stream on close error')
|
|
49
|
+
const kReceivedGoAway = Symbol('received goaway')
|
|
44
50
|
|
|
45
51
|
let extractBody
|
|
46
52
|
|
|
@@ -63,29 +69,73 @@ const {
|
|
|
63
69
|
HTTP2_HEADER_EXPECT,
|
|
64
70
|
HTTP2_HEADER_STATUS,
|
|
65
71
|
HTTP2_HEADER_PROTOCOL,
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
NGHTTP2_NO_ERROR,
|
|
73
|
+
NGHTTP2_REFUSED_STREAM
|
|
68
74
|
}
|
|
69
75
|
} = http2
|
|
70
76
|
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
function getGoAwayError (session, errorCode) {
|
|
78
|
+
return session[kError] ||
|
|
79
|
+
(errorCode === NGHTTP2_NO_ERROR
|
|
80
|
+
? new InformationalError(`HTTP/2: "GOAWAY" frame received with code ${errorCode}`)
|
|
81
|
+
: new SocketError(`HTTP/2: "GOAWAY" frame received with code ${errorCode}`, util.getSocketInfo(session[kSocket])))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getGoAwayPendingIdx (client, lastStreamID) {
|
|
85
|
+
const maxAcceptedStreamID = Number.isInteger(lastStreamID) ? lastStreamID : Number.MAX_SAFE_INTEGER
|
|
86
|
+
|
|
87
|
+
for (let i = client[kRunningIdx]; i < client[kPendingIdx]; i++) {
|
|
88
|
+
const request = client[kQueue][i]
|
|
89
|
+
|
|
90
|
+
if (request == null) {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof request[kRequestStreamId] !== 'number' || request[kRequestStreamId] > maxAcceptedStreamID) {
|
|
95
|
+
return i
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
|
|
88
|
-
return
|
|
99
|
+
return client[kPendingIdx]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function detachRequestFromStream (request) {
|
|
103
|
+
request[kRequestStreamId] = null
|
|
104
|
+
request[kRequestStream] = null
|
|
105
|
+
request[kRequestStreamCleanup] = null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function bindRequestToStream (request, stream, cleanup) {
|
|
109
|
+
const previousCleanup = request[kRequestStreamCleanup]
|
|
110
|
+
detachRequestFromStream(request)
|
|
111
|
+
previousCleanup?.()
|
|
112
|
+
request[kRequestStreamId] = stream.id
|
|
113
|
+
request[kRequestStream] = stream
|
|
114
|
+
request[kRequestStreamCleanup] = cleanup
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function clearRequestStream (request) {
|
|
118
|
+
const cleanup = request[kRequestStreamCleanup]
|
|
119
|
+
detachRequestFromStream(request)
|
|
120
|
+
cleanup?.()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function canRetryRequestAfterGoAway (request) {
|
|
124
|
+
const { body } = request
|
|
125
|
+
|
|
126
|
+
return body == null || util.isBuffer(body) || util.isBlobLike(body)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function closeRequestStream (request, code = NGHTTP2_REFUSED_STREAM) {
|
|
130
|
+
const stream = request[kRequestStream]
|
|
131
|
+
|
|
132
|
+
clearRequestStream(request)
|
|
133
|
+
|
|
134
|
+
if (stream != null && !stream.destroyed && !stream.closed) {
|
|
135
|
+
try {
|
|
136
|
+
stream.close(code)
|
|
137
|
+
} catch {}
|
|
138
|
+
}
|
|
89
139
|
}
|
|
90
140
|
|
|
91
141
|
function connectH2 (client, socket) {
|
|
@@ -113,6 +163,7 @@ function connectH2 (client, socket) {
|
|
|
113
163
|
interval: client[kPingInterval] === 0 ? null : setInterval(onHttp2SendPing, client[kPingInterval], session).unref()
|
|
114
164
|
}
|
|
115
165
|
}
|
|
166
|
+
session[kReceivedGoAway] = false
|
|
116
167
|
// We set it to true by default in a best-effort; however once connected to an H2 server
|
|
117
168
|
// we will check if extended CONNECT protocol is supported or not
|
|
118
169
|
// and set this value accordingly.
|
|
@@ -184,6 +235,14 @@ function connectH2 (client, socket) {
|
|
|
184
235
|
* @returns {boolean}
|
|
185
236
|
*/
|
|
186
237
|
busy (request) {
|
|
238
|
+
if (session[kRemoteSettings] === false && client[kRunning] > 0) {
|
|
239
|
+
return true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (client[kRunning] >= client[kMaxConcurrentStreams]) {
|
|
243
|
+
return true
|
|
244
|
+
}
|
|
245
|
+
|
|
187
246
|
if (request != null) {
|
|
188
247
|
if (client[kRunning] > 0) {
|
|
189
248
|
// We are already processing requests
|
|
@@ -273,7 +332,7 @@ function onHttp2SendPing (session) {
|
|
|
273
332
|
|
|
274
333
|
function onPing (err, duration) {
|
|
275
334
|
const client = this[kClient]
|
|
276
|
-
const socket = this[
|
|
335
|
+
const socket = this[kSocket]
|
|
277
336
|
|
|
278
337
|
if (err != null) {
|
|
279
338
|
const error = new InformationalError(`HTTP/2: "PING" errored - type ${err.message}`)
|
|
@@ -313,48 +372,68 @@ function onHttp2SessionEnd () {
|
|
|
313
372
|
*
|
|
314
373
|
* @this {import('http2').ClientHttp2Session}
|
|
315
374
|
* @param {number} errorCode
|
|
375
|
+
* @param {number} lastStreamID
|
|
316
376
|
*/
|
|
317
|
-
function onHttp2SessionGoAway (errorCode) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
377
|
+
function onHttp2SessionGoAway (errorCode, lastStreamID) {
|
|
378
|
+
if (this[kReceivedGoAway]) {
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
this[kReceivedGoAway] = true
|
|
321
383
|
|
|
322
|
-
const err = this
|
|
384
|
+
const err = getGoAwayError(this, errorCode)
|
|
323
385
|
const client = this[kClient]
|
|
386
|
+
const previousPendingIdx = client[kPendingIdx]
|
|
387
|
+
const pendingIdx = getGoAwayPendingIdx(client, lastStreamID)
|
|
388
|
+
const retriableRequests = []
|
|
324
389
|
|
|
325
|
-
|
|
326
|
-
|
|
390
|
+
for (let i = pendingIdx; i < previousPendingIdx; i++) {
|
|
391
|
+
const request = client[kQueue][i]
|
|
327
392
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
this[kHTTP2Session] = null
|
|
393
|
+
if (request != null) {
|
|
394
|
+
closeRequestStream(request)
|
|
331
395
|
|
|
332
|
-
|
|
396
|
+
if (canRetryRequestAfterGoAway(request)) {
|
|
397
|
+
retriableRequests.push(request)
|
|
398
|
+
} else {
|
|
399
|
+
util.errorRequest(client, request, err)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
333
403
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
client[kQueue]
|
|
338
|
-
util.errorRequest(client, request, err)
|
|
339
|
-
client[kPendingIdx] = client[kRunningIdx]
|
|
404
|
+
if (pendingIdx !== previousPendingIdx) {
|
|
405
|
+
const remainingPendingRequests = client[kQueue].slice(previousPendingIdx)
|
|
406
|
+
client[kQueue].length = pendingIdx
|
|
407
|
+
client[kQueue].push(...retriableRequests, ...remainingPendingRequests)
|
|
340
408
|
}
|
|
341
409
|
|
|
342
|
-
|
|
410
|
+
if (client[kHTTP2Session] === this) {
|
|
411
|
+
client[kSocket] = null
|
|
412
|
+
client[kHTTPContext] = null
|
|
413
|
+
client[kHTTP2Session] = null
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!this.closed && !this.destroyed) {
|
|
417
|
+
this.close()
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
client[kPendingIdx] = pendingIdx
|
|
343
421
|
|
|
344
422
|
client.emit('disconnect', client[kUrl], [client], err)
|
|
345
|
-
client.emit('connectionError', client[kUrl], [client], err)
|
|
346
423
|
|
|
347
424
|
client[kResume]()
|
|
348
425
|
}
|
|
349
426
|
|
|
350
427
|
function onHttp2SessionClose () {
|
|
351
|
-
const { [kClient]: client, [kHTTP2SessionState]: state } = this
|
|
352
|
-
const { [kSocket]: socket } = client
|
|
428
|
+
const { [kClient]: client, [kHTTP2SessionState]: state, [kSocket]: socket } = this
|
|
353
429
|
|
|
354
|
-
const err =
|
|
430
|
+
const err = socket[kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket))
|
|
355
431
|
|
|
356
|
-
client[
|
|
357
|
-
|
|
432
|
+
if (client[kHTTP2Session] === this) {
|
|
433
|
+
client[kSocket] = null
|
|
434
|
+
client[kHTTPContext] = null
|
|
435
|
+
client[kHTTP2Session] = null
|
|
436
|
+
}
|
|
358
437
|
|
|
359
438
|
if (state.ping.interval != null) {
|
|
360
439
|
clearInterval(state.ping.interval)
|
|
@@ -376,15 +455,27 @@ function onHttp2SessionClose () {
|
|
|
376
455
|
function onHttp2SocketClose () {
|
|
377
456
|
const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
|
|
378
457
|
|
|
379
|
-
const
|
|
458
|
+
const session = this[kHTTP2Session]
|
|
459
|
+
const client = session[kClient]
|
|
460
|
+
|
|
461
|
+
if (client[kSocket] !== this) {
|
|
462
|
+
// Ignore stale socket closes from a detached GOAWAY session and from any
|
|
463
|
+
// session that has already been replaced. If the session was detached
|
|
464
|
+
// without a GOAWAY and there is no replacement yet, we still need the
|
|
465
|
+
// close event to flush the client state.
|
|
466
|
+
if (session[kReceivedGoAway] || (client[kHTTP2Session] != null && client[kHTTP2Session] !== session)) {
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
}
|
|
380
470
|
|
|
381
471
|
client[kSocket] = null
|
|
382
472
|
client[kHTTPContext] = null
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this[kHTTP2Session].destroy(err)
|
|
473
|
+
if (client[kHTTP2Session] === session) {
|
|
474
|
+
client[kHTTP2Session] = null
|
|
386
475
|
}
|
|
387
476
|
|
|
477
|
+
session.destroy(err)
|
|
478
|
+
|
|
388
479
|
client[kPendingIdx] = client[kRunningIdx]
|
|
389
480
|
|
|
390
481
|
assert(client[kRunning] === 0)
|
|
@@ -410,6 +501,37 @@ function onSocketClose () {
|
|
|
410
501
|
this[kClosed] = true
|
|
411
502
|
}
|
|
412
503
|
|
|
504
|
+
function noop () {}
|
|
505
|
+
|
|
506
|
+
function closeStreamSession (stream) {
|
|
507
|
+
const session = stream[kHTTP2Session]
|
|
508
|
+
|
|
509
|
+
stream[kHTTP2Session] = null
|
|
510
|
+
session[kOpenStreams] -= 1
|
|
511
|
+
if (session[kOpenStreams] === 0) {
|
|
512
|
+
session.unref()
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function onUpgradeStreamClose () {
|
|
517
|
+
this.off('error', noop)
|
|
518
|
+
|
|
519
|
+
const failUpgradeStream = this[kRequestStreamOnCloseError]
|
|
520
|
+
this[kRequestStreamOnCloseError] = null
|
|
521
|
+
|
|
522
|
+
failUpgradeStream(new InformationalError('HTTP/2: stream closed before response headers'))
|
|
523
|
+
closeStreamSession(this)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function onRequestStreamClose () {
|
|
527
|
+
const onData = this[kRequestStreamOnData]
|
|
528
|
+
|
|
529
|
+
this[kRequestStreamOnData] = null
|
|
530
|
+
this.off('data', onData)
|
|
531
|
+
this.off('error', noop)
|
|
532
|
+
closeStreamSession(this)
|
|
533
|
+
}
|
|
534
|
+
|
|
413
535
|
// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
|
|
414
536
|
function shouldSendContentLength (method) {
|
|
415
537
|
return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT'
|
|
@@ -464,7 +586,23 @@ function writeH2 (client, request) {
|
|
|
464
586
|
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
|
465
587
|
headers[HTTP2_HEADER_METHOD] = method
|
|
466
588
|
|
|
467
|
-
|
|
589
|
+
let requestFinalized = false
|
|
590
|
+
const finalizeRequest = (resetPendingIdx = false) => {
|
|
591
|
+
if (requestFinalized) {
|
|
592
|
+
return
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
requestFinalized = true
|
|
596
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
597
|
+
|
|
598
|
+
if (resetPendingIdx) {
|
|
599
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
client[kResume]()
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const abort = (err, resetPendingIdx = false) => {
|
|
468
606
|
if (request.aborted || request.completed) {
|
|
469
607
|
return
|
|
470
608
|
}
|
|
@@ -474,16 +612,14 @@ function writeH2 (client, request) {
|
|
|
474
612
|
util.errorRequest(client, request, err)
|
|
475
613
|
|
|
476
614
|
if (stream != null) {
|
|
477
|
-
|
|
478
|
-
// let's ignore them
|
|
479
|
-
stream.removeAllListeners('data')
|
|
615
|
+
clearRequestStream(request)
|
|
480
616
|
|
|
481
617
|
// On Abort, we close the stream to send RST_STREAM frame
|
|
482
618
|
stream.close()
|
|
483
619
|
|
|
484
620
|
// We move the running index to the next request
|
|
485
621
|
client[kOnError](err)
|
|
486
|
-
|
|
622
|
+
finalizeRequest(resetPendingIdx)
|
|
487
623
|
}
|
|
488
624
|
|
|
489
625
|
// We do not destroy the socket as we can continue using the session
|
|
@@ -491,6 +627,26 @@ function writeH2 (client, request) {
|
|
|
491
627
|
util.destroy(body, err)
|
|
492
628
|
}
|
|
493
629
|
|
|
630
|
+
const requestStream = (headers, options) => {
|
|
631
|
+
try {
|
|
632
|
+
return session.request(headers, options)
|
|
633
|
+
} catch (err) {
|
|
634
|
+
if (err?.code !== 'ERR_HTTP2_INVALID_CONNECTION_HEADERS') {
|
|
635
|
+
throw err
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const wrappedErr = new InformationalError(err.message, { cause: err })
|
|
639
|
+
session[kError] = wrappedErr
|
|
640
|
+
session[kSocket][kError] = wrappedErr
|
|
641
|
+
|
|
642
|
+
session.destroy(wrappedErr)
|
|
643
|
+
util.destroy(session[kSocket], wrappedErr)
|
|
644
|
+
abort(wrappedErr)
|
|
645
|
+
|
|
646
|
+
return null
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
494
650
|
try {
|
|
495
651
|
// We are already connected, streams are pending.
|
|
496
652
|
// We can call on connect, and wait for abort
|
|
@@ -506,6 +662,84 @@ function writeH2 (client, request) {
|
|
|
506
662
|
if (upgrade || method === 'CONNECT') {
|
|
507
663
|
session.ref()
|
|
508
664
|
|
|
665
|
+
const setupUpgradeStream = (stream) => {
|
|
666
|
+
let responseReceived = false
|
|
667
|
+
|
|
668
|
+
const removeUpgradeStreamListeners = () => {
|
|
669
|
+
stream.off('response', onUpgradeResponse)
|
|
670
|
+
stream.off('error', onUpgradeStreamError)
|
|
671
|
+
stream.off('end', onUpgradeStreamEnd)
|
|
672
|
+
stream.off('timeout', onUpgradeStreamTimeout)
|
|
673
|
+
stream.off('error', noop)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const releaseUpgradeStream = () => {
|
|
677
|
+
if (request[kRequestStream] === stream) {
|
|
678
|
+
detachRequestFromStream(request)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
removeUpgradeStreamListeners()
|
|
682
|
+
|
|
683
|
+
if (!stream.destroyed && !stream.closed) {
|
|
684
|
+
stream.once('error', noop)
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const failUpgradeStream = (err) => {
|
|
689
|
+
if (responseReceived || request.aborted || request.completed) {
|
|
690
|
+
return
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
releaseUpgradeStream()
|
|
694
|
+
abort(err, true)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const onUpgradeStreamError = () => {
|
|
698
|
+
if (typeof stream.rstCode === 'number' && stream.rstCode !== 0) {
|
|
699
|
+
failUpgradeStream(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
|
|
700
|
+
} else {
|
|
701
|
+
failUpgradeStream(new InformationalError('HTTP/2: stream errored before response headers'))
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const onUpgradeStreamEnd = () => {
|
|
706
|
+
failUpgradeStream(new InformationalError('HTTP/2: stream half-closed (remote)'))
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const onUpgradeStreamTimeout = () => {
|
|
710
|
+
failUpgradeStream(new InformationalError(`HTTP/2: "stream timeout after ${requestTimeout}"`))
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const onUpgradeResponse = (headers, _flags) => {
|
|
714
|
+
responseReceived = true
|
|
715
|
+
|
|
716
|
+
const statusCode = headers[HTTP2_HEADER_STATUS]
|
|
717
|
+
delete headers[HTTP2_HEADER_STATUS]
|
|
718
|
+
|
|
719
|
+
request.onRequestUpgrade(statusCode, headers, stream)
|
|
720
|
+
|
|
721
|
+
if (request.aborted || request.completed) {
|
|
722
|
+
return
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
removeUpgradeStreamListeners()
|
|
726
|
+
detachRequestFromStream(request)
|
|
727
|
+
finalizeRequest()
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
bindRequestToStream(request, stream, releaseUpgradeStream)
|
|
731
|
+
stream.once('response', onUpgradeResponse)
|
|
732
|
+
stream.on('error', onUpgradeStreamError)
|
|
733
|
+
stream.once('end', onUpgradeStreamEnd)
|
|
734
|
+
stream.on('timeout', onUpgradeStreamTimeout)
|
|
735
|
+
stream[kHTTP2Session] = session
|
|
736
|
+
stream[kRequestStreamOnCloseError] = failUpgradeStream
|
|
737
|
+
stream.once('close', onUpgradeStreamClose)
|
|
738
|
+
|
|
739
|
+
++session[kOpenStreams]
|
|
740
|
+
stream.setTimeout(requestTimeout)
|
|
741
|
+
}
|
|
742
|
+
|
|
509
743
|
if (upgrade === 'websocket') {
|
|
510
744
|
// We cannot upgrade to websocket if extended CONNECT protocol is not supported
|
|
511
745
|
if (session[kEnableConnectProtocol] === false) {
|
|
@@ -528,33 +762,13 @@ function writeH2 (client, request) {
|
|
|
528
762
|
headers[HTTP2_HEADER_SCHEME] = protocol === 'http:' ? 'http' : 'https'
|
|
529
763
|
}
|
|
530
764
|
|
|
531
|
-
stream =
|
|
765
|
+
stream = requestStream(headers, { endStream: false, signal })
|
|
766
|
+
if (stream == null) {
|
|
767
|
+
session.unref()
|
|
768
|
+
return false
|
|
769
|
+
}
|
|
532
770
|
stream[kHTTP2Stream] = true
|
|
533
|
-
|
|
534
|
-
stream.once('response', (headers, _flags) => {
|
|
535
|
-
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
|
|
536
|
-
|
|
537
|
-
request.onRequestUpgrade(statusCode, parseH2Headers(realHeaders), stream)
|
|
538
|
-
|
|
539
|
-
++session[kOpenStreams]
|
|
540
|
-
client[kQueue][client[kRunningIdx]++] = null
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
stream.on('error', () => {
|
|
544
|
-
if (stream.rstCode === NGHTTP2_REFUSED_STREAM || stream.rstCode === NGHTTP2_CANCEL) {
|
|
545
|
-
// NGHTTP2_REFUSED_STREAM (7) or NGHTTP2_CANCEL (8)
|
|
546
|
-
// We do not treat those as errors as the server might
|
|
547
|
-
// not support websockets and refuse the stream
|
|
548
|
-
abort(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
|
|
549
|
-
}
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
stream.once('close', () => {
|
|
553
|
-
session[kOpenStreams] -= 1
|
|
554
|
-
if (session[kOpenStreams] === 0) session.unref()
|
|
555
|
-
})
|
|
556
|
-
|
|
557
|
-
stream.setTimeout(requestTimeout)
|
|
771
|
+
setupUpgradeStream(stream)
|
|
558
772
|
return true
|
|
559
773
|
}
|
|
560
774
|
|
|
@@ -563,20 +777,13 @@ function writeH2 (client, request) {
|
|
|
563
777
|
// will create a new stream. We trigger a request to create the stream and wait until
|
|
564
778
|
// `ready` event is triggered
|
|
565
779
|
// We disabled endStream to allow the user to write to the stream
|
|
566
|
-
stream =
|
|
780
|
+
stream = requestStream(headers, { endStream: false, signal })
|
|
781
|
+
if (stream == null) {
|
|
782
|
+
session.unref()
|
|
783
|
+
return false
|
|
784
|
+
}
|
|
567
785
|
stream[kHTTP2Stream] = true
|
|
568
|
-
stream
|
|
569
|
-
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
|
|
570
|
-
|
|
571
|
-
request.onRequestUpgrade(statusCode, parseH2Headers(realHeaders), stream)
|
|
572
|
-
++session[kOpenStreams]
|
|
573
|
-
client[kQueue][client[kRunningIdx]++] = null
|
|
574
|
-
})
|
|
575
|
-
stream.once('close', () => {
|
|
576
|
-
session[kOpenStreams] -= 1
|
|
577
|
-
if (session[kOpenStreams] === 0) session.unref()
|
|
578
|
-
})
|
|
579
|
-
stream.setTimeout(requestTimeout)
|
|
786
|
+
setupUpgradeStream(stream)
|
|
580
787
|
|
|
581
788
|
return true
|
|
582
789
|
}
|
|
@@ -622,7 +829,7 @@ function writeH2 (client, request) {
|
|
|
622
829
|
contentLength = request.contentLength
|
|
623
830
|
}
|
|
624
831
|
|
|
625
|
-
if (!expectsPayload) {
|
|
832
|
+
if (contentLength === 0 && !expectsPayload) {
|
|
626
833
|
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
627
834
|
// A user agent SHOULD NOT send a Content-Length header field when
|
|
628
835
|
// the request message does not contain a payload body and the method
|
|
@@ -658,21 +865,25 @@ function writeH2 (client, request) {
|
|
|
658
865
|
}
|
|
659
866
|
|
|
660
867
|
// TODO(metcoder95): add support for sending trailers
|
|
661
|
-
const shouldEndStream =
|
|
868
|
+
const shouldEndStream = body === null
|
|
662
869
|
if (expectContinue) {
|
|
663
870
|
headers[HTTP2_HEADER_EXPECT] = '100-continue'
|
|
664
|
-
stream =
|
|
871
|
+
stream = requestStream(headers, { endStream: shouldEndStream, signal })
|
|
872
|
+
if (stream == null) {
|
|
873
|
+
return false
|
|
874
|
+
}
|
|
665
875
|
stream[kHTTP2Stream] = true
|
|
666
|
-
|
|
667
|
-
stream.once('continue', writeBodyH2)
|
|
876
|
+
bindRequestToStream(request, stream, null)
|
|
668
877
|
} else {
|
|
669
|
-
stream =
|
|
878
|
+
stream = requestStream(headers, {
|
|
670
879
|
endStream: shouldEndStream,
|
|
671
880
|
signal
|
|
672
881
|
})
|
|
882
|
+
if (stream == null) {
|
|
883
|
+
return false
|
|
884
|
+
}
|
|
673
885
|
stream[kHTTP2Stream] = true
|
|
674
|
-
|
|
675
|
-
writeBodyH2()
|
|
886
|
+
bindRequestToStream(request, stream, null)
|
|
676
887
|
}
|
|
677
888
|
|
|
678
889
|
// Increment counter as we have new streams open
|
|
@@ -681,9 +892,46 @@ function writeH2 (client, request) {
|
|
|
681
892
|
|
|
682
893
|
// Track whether we received a response (headers)
|
|
683
894
|
let responseReceived = false
|
|
895
|
+
const onData = (chunk) => {
|
|
896
|
+
if (request.aborted || request.completed) {
|
|
897
|
+
return
|
|
898
|
+
}
|
|
684
899
|
|
|
685
|
-
|
|
686
|
-
|
|
900
|
+
if (request.onResponseData(chunk) === false) {
|
|
901
|
+
stream.pause()
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const removeRequestStreamListeners = () => {
|
|
906
|
+
stream.off('error', noop)
|
|
907
|
+
stream.off('continue', writeBodyH2)
|
|
908
|
+
stream.off('response', onResponse)
|
|
909
|
+
stream.off('end', onEnd)
|
|
910
|
+
stream.off('error', onError)
|
|
911
|
+
stream.off('frameError', onFrameError)
|
|
912
|
+
stream.off('aborted', onAborted)
|
|
913
|
+
stream.off('timeout', onTimeout)
|
|
914
|
+
stream.off('trailers', onTrailers)
|
|
915
|
+
stream.off('data', onData)
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const releaseRequestStream = () => {
|
|
919
|
+
if (request[kRequestStream] === stream) {
|
|
920
|
+
detachRequestFromStream(request)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
removeRequestStreamListeners()
|
|
924
|
+
|
|
925
|
+
if (!stream.destroyed && !stream.closed) {
|
|
926
|
+
stream.once('error', noop)
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const onResponse = (headers) => {
|
|
931
|
+
stream.off('response', onResponse)
|
|
932
|
+
|
|
933
|
+
const statusCode = headers[HTTP2_HEADER_STATUS]
|
|
934
|
+
delete headers[HTTP2_HEADER_STATUS]
|
|
687
935
|
request.onResponseStarted()
|
|
688
936
|
responseReceived = true
|
|
689
937
|
|
|
@@ -693,86 +941,91 @@ function writeH2 (client, request) {
|
|
|
693
941
|
// for those scenarios, best effort is to destroy the stream immediately
|
|
694
942
|
// as there's no value to keep it open.
|
|
695
943
|
if (request.aborted) {
|
|
696
|
-
|
|
944
|
+
releaseRequestStream()
|
|
697
945
|
return
|
|
698
946
|
}
|
|
699
947
|
|
|
700
|
-
if (request.onResponseStart(Number(statusCode),
|
|
948
|
+
if (request.onResponseStart(Number(statusCode), headers, stream.resume.bind(stream), '') === false) {
|
|
701
949
|
stream.pause()
|
|
702
950
|
}
|
|
703
951
|
|
|
704
|
-
stream.on('data',
|
|
705
|
-
|
|
706
|
-
return
|
|
707
|
-
}
|
|
952
|
+
stream.on('data', onData)
|
|
953
|
+
}
|
|
708
954
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
})
|
|
713
|
-
})
|
|
955
|
+
const onEnd = () => {
|
|
956
|
+
stream.off('end', onEnd)
|
|
714
957
|
|
|
715
|
-
|
|
716
|
-
stream.removeAllListeners('data')
|
|
958
|
+
releaseRequestStream()
|
|
717
959
|
// If we received a response, this is a normal completion
|
|
718
960
|
if (responseReceived) {
|
|
719
961
|
if (!request.aborted && !request.completed) {
|
|
720
962
|
request.onResponseEnd({})
|
|
721
963
|
}
|
|
722
964
|
|
|
723
|
-
|
|
724
|
-
client[kResume]()
|
|
965
|
+
finalizeRequest()
|
|
725
966
|
} else {
|
|
726
967
|
// Stream ended without receiving a response - this is an error
|
|
727
968
|
// (e.g., server destroyed the stream before sending headers)
|
|
728
|
-
abort(new InformationalError('HTTP/2: stream half-closed (remote)'))
|
|
729
|
-
client[kQueue][client[kRunningIdx]++] = null
|
|
730
|
-
client[kPendingIdx] = client[kRunningIdx]
|
|
731
|
-
client[kResume]()
|
|
969
|
+
abort(new InformationalError('HTTP/2: stream half-closed (remote)'), true)
|
|
732
970
|
}
|
|
733
|
-
}
|
|
971
|
+
}
|
|
734
972
|
|
|
735
|
-
stream
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
if (session[kOpenStreams] === 0) {
|
|
739
|
-
session.unref()
|
|
740
|
-
}
|
|
741
|
-
})
|
|
973
|
+
stream[kHTTP2Session] = session
|
|
974
|
+
stream[kRequestStreamOnData] = onData
|
|
975
|
+
stream.once('close', onRequestStreamClose)
|
|
742
976
|
|
|
743
|
-
|
|
744
|
-
stream.
|
|
977
|
+
const onError = function (err) {
|
|
978
|
+
stream.off('error', onError)
|
|
979
|
+
|
|
980
|
+
releaseRequestStream()
|
|
745
981
|
abort(err)
|
|
746
|
-
}
|
|
982
|
+
}
|
|
747
983
|
|
|
748
|
-
|
|
749
|
-
stream.
|
|
750
|
-
abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
|
|
751
|
-
})
|
|
984
|
+
const onFrameError = (type, code) => {
|
|
985
|
+
stream.off('frameError', onFrameError)
|
|
752
986
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
987
|
+
releaseRequestStream()
|
|
988
|
+
abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
|
|
989
|
+
}
|
|
756
990
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
session[kOpenStreams] -= 1
|
|
991
|
+
const onAborted = () => {
|
|
992
|
+
stream.off('data', onData)
|
|
993
|
+
}
|
|
761
994
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
995
|
+
const onTimeout = () => {
|
|
996
|
+
releaseRequestStream()
|
|
765
997
|
|
|
998
|
+
const err = new InformationalError(`HTTP/2: "stream timeout after ${requestTimeout}"`)
|
|
766
999
|
abort(err)
|
|
767
|
-
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const onTrailers = (trailers) => {
|
|
1003
|
+
stream.off('trailers', onTrailers)
|
|
768
1004
|
|
|
769
|
-
stream.once('trailers', trailers => {
|
|
770
1005
|
if (request.aborted || request.completed) {
|
|
771
1006
|
return
|
|
772
1007
|
}
|
|
773
1008
|
|
|
1009
|
+
releaseRequestStream()
|
|
774
1010
|
request.onResponseEnd(trailers)
|
|
775
|
-
|
|
1011
|
+
finalizeRequest()
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
bindRequestToStream(request, stream, releaseRequestStream)
|
|
1015
|
+
if (expectContinue) {
|
|
1016
|
+
stream.once('continue', writeBodyH2)
|
|
1017
|
+
}
|
|
1018
|
+
stream.once('response', onResponse)
|
|
1019
|
+
stream.once('end', onEnd)
|
|
1020
|
+
stream.once('error', onError)
|
|
1021
|
+
stream.once('frameError', onFrameError)
|
|
1022
|
+
stream.on('aborted', onAborted)
|
|
1023
|
+
stream.on('timeout', onTimeout)
|
|
1024
|
+
stream.once('trailers', onTrailers)
|
|
1025
|
+
|
|
1026
|
+
if (!expectContinue) {
|
|
1027
|
+
writeBodyH2()
|
|
1028
|
+
}
|
|
776
1029
|
|
|
777
1030
|
return true
|
|
778
1031
|
|