undici 8.2.0 → 8.3.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/README.md +8 -5
- package/docs/docs/api/GlobalInstallation.md +7 -5
- package/docs/docs/api/SnapshotAgent.md +23 -0
- package/lib/api/api-pipeline.js +4 -0
- package/lib/api/api-stream.js +51 -5
- package/lib/core/symbols.js +1 -0
- package/lib/core/util.js +2 -2
- package/lib/dispatcher/client-h1.js +59 -18
- package/lib/dispatcher/client-h2.js +373 -294
- package/lib/dispatcher/client.js +3 -1
- package/lib/dispatcher/pool-base.js +21 -3
- package/lib/dispatcher/pool.js +23 -0
- package/lib/dispatcher/proxy-agent.js +21 -4
- package/lib/dispatcher/round-robin-pool.js +26 -0
- package/lib/dispatcher/socks5-proxy-agent.js +19 -19
- package/lib/handler/retry-handler.js +14 -0
- package/lib/mock/mock-utils.js +3 -1
- package/lib/mock/snapshot-agent.js +2 -0
- package/lib/mock/snapshot-recorder.js +38 -3
- package/lib/web/fetch/body.js +2 -7
- package/lib/web/fetch/formdata.js +21 -2
- package/lib/web/fetch/index.js +2 -0
- package/package.json +4 -4
- package/types/client.d.ts +7 -7
- package/types/dispatcher.d.ts +0 -2
- package/types/formdata.d.ts +0 -6
- package/types/snapshot-agent.d.ts +4 -0
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
kHTTP2Session,
|
|
29
29
|
kHTTP2InitialWindowSize,
|
|
30
30
|
kHTTP2ConnectionWindowSize,
|
|
31
|
+
kHostAuthority,
|
|
31
32
|
kResume,
|
|
32
33
|
kSize,
|
|
33
34
|
kHTTPContext,
|
|
@@ -44,8 +45,7 @@ const kOpenStreams = Symbol('open streams')
|
|
|
44
45
|
const kRequestStreamId = Symbol('request stream id')
|
|
45
46
|
const kRequestStream = Symbol('request stream')
|
|
46
47
|
const kRequestStreamCleanup = Symbol('request stream cleanup')
|
|
47
|
-
const
|
|
48
|
-
const kRequestStreamOnCloseError = Symbol('request stream on close error')
|
|
48
|
+
const kRequestStreamState = Symbol('request stream state')
|
|
49
49
|
const kReceivedGoAway = Symbol('received goaway')
|
|
50
50
|
|
|
51
51
|
let extractBody
|
|
@@ -107,8 +107,9 @@ function detachRequestFromStream (request) {
|
|
|
107
107
|
|
|
108
108
|
function bindRequestToStream (request, stream, cleanup) {
|
|
109
109
|
const previousCleanup = request[kRequestStreamCleanup]
|
|
110
|
+
const previousStream = request[kRequestStream]
|
|
110
111
|
detachRequestFromStream(request)
|
|
111
|
-
previousCleanup?.()
|
|
112
|
+
previousCleanup?.(previousStream)
|
|
112
113
|
request[kRequestStreamId] = stream.id
|
|
113
114
|
request[kRequestStream] = stream
|
|
114
115
|
request[kRequestStreamCleanup] = cleanup
|
|
@@ -116,8 +117,9 @@ function bindRequestToStream (request, stream, cleanup) {
|
|
|
116
117
|
|
|
117
118
|
function clearRequestStream (request) {
|
|
118
119
|
const cleanup = request[kRequestStreamCleanup]
|
|
120
|
+
const stream = request[kRequestStream]
|
|
119
121
|
detachRequestFromStream(request)
|
|
120
|
-
cleanup?.()
|
|
122
|
+
cleanup?.(stream)
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
function canRetryRequestAfterGoAway (request) {
|
|
@@ -516,20 +518,18 @@ function closeStreamSession (stream) {
|
|
|
516
518
|
function onUpgradeStreamClose () {
|
|
517
519
|
this.off('error', noop)
|
|
518
520
|
|
|
519
|
-
const
|
|
520
|
-
this[
|
|
521
|
+
const state = this[kRequestStreamState]
|
|
522
|
+
this[kRequestStreamState] = null
|
|
521
523
|
|
|
522
|
-
failUpgradeStream(new InformationalError('HTTP/2: stream closed before response headers'))
|
|
524
|
+
failUpgradeStream(state, new InformationalError('HTTP/2: stream closed before response headers'))
|
|
523
525
|
closeStreamSession(this)
|
|
524
526
|
}
|
|
525
527
|
|
|
526
528
|
function onRequestStreamClose () {
|
|
527
|
-
const onData = this[kRequestStreamOnData]
|
|
528
|
-
|
|
529
|
-
this[kRequestStreamOnData] = null
|
|
530
529
|
this.off('data', onData)
|
|
531
530
|
this.off('error', noop)
|
|
532
531
|
closeStreamSession(this)
|
|
532
|
+
this[kRequestStreamState] = null
|
|
533
533
|
}
|
|
534
534
|
|
|
535
535
|
// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
|
|
@@ -537,25 +537,17 @@ function shouldSendContentLength (method) {
|
|
|
537
537
|
return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT'
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
-
function
|
|
541
|
-
const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout]
|
|
542
|
-
const session = client[kHTTP2Session]
|
|
543
|
-
const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request
|
|
544
|
-
let { body } = request
|
|
545
|
-
|
|
546
|
-
if (upgrade != null && upgrade !== 'websocket') {
|
|
547
|
-
util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`))
|
|
548
|
-
return false
|
|
549
|
-
}
|
|
550
|
-
|
|
540
|
+
function buildRequestHeaders (reqHeaders) {
|
|
551
541
|
const headers = {}
|
|
542
|
+
|
|
552
543
|
for (let n = 0; n < reqHeaders.length; n += 2) {
|
|
553
544
|
const key = reqHeaders[n + 0]
|
|
554
545
|
const val = reqHeaders[n + 1]
|
|
546
|
+
const current = headers[key]
|
|
555
547
|
|
|
556
548
|
if (key === 'cookie') {
|
|
557
|
-
if (
|
|
558
|
-
headers[key] = Array.isArray(
|
|
549
|
+
if (current != null) {
|
|
550
|
+
headers[key] = Array.isArray(current) ? (current.push(val), current) : [current, val]
|
|
559
551
|
} else {
|
|
560
552
|
headers[key] = val
|
|
561
553
|
}
|
|
@@ -563,27 +555,140 @@ function writeH2 (client, request) {
|
|
|
563
555
|
continue
|
|
564
556
|
}
|
|
565
557
|
|
|
566
|
-
if (
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
headers[key] += `, ${val[i]}`
|
|
570
|
-
} else {
|
|
571
|
-
headers[key] = val[i]
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
} else if (headers[key]) {
|
|
575
|
-
headers[key] += `, ${val}`
|
|
576
|
-
} else {
|
|
577
|
-
headers[key] = val
|
|
558
|
+
if (typeof val === 'string') {
|
|
559
|
+
headers[key] = current ? `${current}, ${val}` : val
|
|
560
|
+
continue
|
|
578
561
|
}
|
|
562
|
+
|
|
563
|
+
for (let i = 0; i < val.length; i++) {
|
|
564
|
+
headers[key] = headers[key] ? `${headers[key]}, ${val[i]}` : val[i]
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return headers
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function removeUpgradeStreamListeners (stream) {
|
|
572
|
+
stream.off('response', onUpgradeResponse)
|
|
573
|
+
stream.off('error', onUpgradeStreamError)
|
|
574
|
+
stream.off('end', onUpgradeStreamEnd)
|
|
575
|
+
stream.off('timeout', onUpgradeStreamTimeout)
|
|
576
|
+
stream.off('error', noop)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function releaseUpgradeStream (stream) {
|
|
580
|
+
if (stream == null) {
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const state = stream[kRequestStreamState]
|
|
585
|
+
if (state == null) {
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const { request } = state
|
|
590
|
+
|
|
591
|
+
if (request[kRequestStream] === stream) {
|
|
592
|
+
detachRequestFromStream(request)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
removeUpgradeStreamListeners(stream)
|
|
596
|
+
|
|
597
|
+
if (!stream.destroyed && !stream.closed) {
|
|
598
|
+
stream.once('error', noop)
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function failUpgradeStream (state, err) {
|
|
603
|
+
if (state == null) {
|
|
604
|
+
return
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const { request } = state
|
|
608
|
+
if (state.responseReceived || request.aborted || request.completed) {
|
|
609
|
+
return
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
releaseUpgradeStream(state.stream)
|
|
613
|
+
state.abort(err, true)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function onUpgradeStreamError () {
|
|
617
|
+
const state = this[kRequestStreamState]
|
|
618
|
+
|
|
619
|
+
if (typeof this.rstCode === 'number' && this.rstCode !== 0) {
|
|
620
|
+
failUpgradeStream(state, new InformationalError(`HTTP/2: "stream error" received - code ${this.rstCode}`))
|
|
621
|
+
} else {
|
|
622
|
+
failUpgradeStream(state, new InformationalError('HTTP/2: stream errored before response headers'))
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function onUpgradeStreamEnd () {
|
|
627
|
+
failUpgradeStream(this[kRequestStreamState], new InformationalError('HTTP/2: stream half-closed (remote)'))
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function onUpgradeStreamTimeout () {
|
|
631
|
+
const state = this[kRequestStreamState]
|
|
632
|
+
failUpgradeStream(state, new InformationalError(`HTTP/2: "stream timeout after ${state.requestTimeout}"`))
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function onUpgradeResponse (headers, _flags) {
|
|
636
|
+
const stream = this
|
|
637
|
+
const state = stream[kRequestStreamState]
|
|
638
|
+
const { request } = state
|
|
639
|
+
|
|
640
|
+
state.responseReceived = true
|
|
641
|
+
|
|
642
|
+
const statusCode = headers[HTTP2_HEADER_STATUS]
|
|
643
|
+
delete headers[HTTP2_HEADER_STATUS]
|
|
644
|
+
|
|
645
|
+
request.onRequestUpgrade(statusCode, headers, stream)
|
|
646
|
+
|
|
647
|
+
if (request.aborted || request.completed) {
|
|
648
|
+
return
|
|
579
649
|
}
|
|
580
650
|
|
|
651
|
+
removeUpgradeStreamListeners(stream)
|
|
652
|
+
detachRequestFromStream(request)
|
|
653
|
+
state.finalizeRequest()
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function setupUpgradeStream (stream, state) {
|
|
657
|
+
const { request, requestTimeout, session } = state
|
|
658
|
+
|
|
659
|
+
stream[kHTTP2Stream] = true
|
|
660
|
+
stream[kHTTP2Session] = session
|
|
661
|
+
stream[kRequestStreamState] = state
|
|
662
|
+
state.stream = stream
|
|
663
|
+
|
|
664
|
+
bindRequestToStream(request, stream, releaseUpgradeStream)
|
|
665
|
+
stream.once('response', onUpgradeResponse)
|
|
666
|
+
stream.on('error', onUpgradeStreamError)
|
|
667
|
+
stream.once('end', onUpgradeStreamEnd)
|
|
668
|
+
stream.on('timeout', onUpgradeStreamTimeout)
|
|
669
|
+
stream.once('close', onUpgradeStreamClose)
|
|
670
|
+
|
|
671
|
+
++session[kOpenStreams]
|
|
672
|
+
stream.setTimeout(requestTimeout)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function writeH2 (client, request) {
|
|
676
|
+
const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout]
|
|
677
|
+
const session = client[kHTTP2Session]
|
|
678
|
+
const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request
|
|
679
|
+
let { body } = request
|
|
680
|
+
|
|
681
|
+
if (upgrade != null && upgrade !== 'websocket') {
|
|
682
|
+
util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`))
|
|
683
|
+
return false
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const headers = buildRequestHeaders(reqHeaders)
|
|
687
|
+
|
|
581
688
|
/** @type {import('node:http2').ClientHttp2Stream} */
|
|
582
689
|
let stream = null
|
|
583
690
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
|
691
|
+
headers[HTTP2_HEADER_AUTHORITY] = host || client[kHostAuthority]
|
|
587
692
|
headers[HTTP2_HEADER_METHOD] = method
|
|
588
693
|
|
|
589
694
|
let requestFinalized = false
|
|
@@ -662,82 +767,14 @@ function writeH2 (client, request) {
|
|
|
662
767
|
if (upgrade || method === 'CONNECT') {
|
|
663
768
|
session.ref()
|
|
664
769
|
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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)
|
|
770
|
+
const upgradeState = {
|
|
771
|
+
abort,
|
|
772
|
+
finalizeRequest,
|
|
773
|
+
request,
|
|
774
|
+
requestTimeout,
|
|
775
|
+
responseReceived: false,
|
|
776
|
+
session,
|
|
777
|
+
stream: null
|
|
741
778
|
}
|
|
742
779
|
|
|
743
780
|
if (upgrade === 'websocket') {
|
|
@@ -767,8 +804,7 @@ function writeH2 (client, request) {
|
|
|
767
804
|
session.unref()
|
|
768
805
|
return false
|
|
769
806
|
}
|
|
770
|
-
stream
|
|
771
|
-
setupUpgradeStream(stream)
|
|
807
|
+
setupUpgradeStream(stream, upgradeState)
|
|
772
808
|
return true
|
|
773
809
|
}
|
|
774
810
|
|
|
@@ -782,8 +818,7 @@ function writeH2 (client, request) {
|
|
|
782
818
|
session.unref()
|
|
783
819
|
return false
|
|
784
820
|
}
|
|
785
|
-
stream
|
|
786
|
-
setupUpgradeStream(stream)
|
|
821
|
+
setupUpgradeStream(stream, upgradeState)
|
|
787
822
|
|
|
788
823
|
return true
|
|
789
824
|
}
|
|
@@ -805,7 +840,10 @@ function writeH2 (client, request) {
|
|
|
805
840
|
const expectsPayload = (
|
|
806
841
|
method === 'PUT' ||
|
|
807
842
|
method === 'POST' ||
|
|
808
|
-
method === 'PATCH'
|
|
843
|
+
method === 'PATCH' ||
|
|
844
|
+
method === 'QUERY' ||
|
|
845
|
+
method === 'PROPFIND' ||
|
|
846
|
+
method === 'PROPPATCH'
|
|
809
847
|
)
|
|
810
848
|
|
|
811
849
|
if (body && typeof body.read === 'function') {
|
|
@@ -865,230 +903,251 @@ function writeH2 (client, request) {
|
|
|
865
903
|
}
|
|
866
904
|
|
|
867
905
|
// TODO(metcoder95): add support for sending trailers
|
|
868
|
-
const shouldEndStream = body === null
|
|
906
|
+
const shouldEndStream = body === null || contentLength === 0
|
|
907
|
+
const state = {
|
|
908
|
+
abort,
|
|
909
|
+
body,
|
|
910
|
+
client,
|
|
911
|
+
contentLength,
|
|
912
|
+
expectsPayload,
|
|
913
|
+
finalizeRequest,
|
|
914
|
+
request,
|
|
915
|
+
requestTimeout,
|
|
916
|
+
responseReceived: false,
|
|
917
|
+
session,
|
|
918
|
+
stream: null
|
|
919
|
+
}
|
|
920
|
+
|
|
869
921
|
if (expectContinue) {
|
|
870
922
|
headers[HTTP2_HEADER_EXPECT] = '100-continue'
|
|
871
|
-
stream = requestStream(headers, { endStream: shouldEndStream, signal })
|
|
872
|
-
if (stream == null) {
|
|
873
|
-
return false
|
|
874
|
-
}
|
|
875
|
-
stream[kHTTP2Stream] = true
|
|
876
|
-
bindRequestToStream(request, stream, null)
|
|
877
|
-
} else {
|
|
878
|
-
stream = requestStream(headers, {
|
|
879
|
-
endStream: shouldEndStream,
|
|
880
|
-
signal
|
|
881
|
-
})
|
|
882
|
-
if (stream == null) {
|
|
883
|
-
return false
|
|
884
|
-
}
|
|
885
|
-
stream[kHTTP2Stream] = true
|
|
886
|
-
bindRequestToStream(request, stream, null)
|
|
887
923
|
}
|
|
888
924
|
|
|
925
|
+
stream = requestStream(headers, { endStream: shouldEndStream, signal })
|
|
926
|
+
if (stream == null) {
|
|
927
|
+
return false
|
|
928
|
+
}
|
|
929
|
+
stream[kHTTP2Stream] = true
|
|
930
|
+
stream[kRequestStreamState] = state
|
|
931
|
+
state.stream = stream
|
|
932
|
+
bindRequestToStream(request, stream, null)
|
|
933
|
+
|
|
889
934
|
// Increment counter as we have new streams open
|
|
890
935
|
++session[kOpenStreams]
|
|
891
936
|
stream.setTimeout(requestTimeout)
|
|
892
937
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
const onData = (chunk) => {
|
|
896
|
-
if (request.aborted || request.completed) {
|
|
897
|
-
return
|
|
898
|
-
}
|
|
938
|
+
stream[kHTTP2Session] = session
|
|
939
|
+
stream.once('close', onRequestStreamClose)
|
|
899
940
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
941
|
+
bindRequestToStream(request, stream, releaseRequestStream)
|
|
942
|
+
if (expectContinue) {
|
|
943
|
+
stream.once('continue', writeBodyH2)
|
|
903
944
|
}
|
|
945
|
+
stream.once('response', onResponse)
|
|
946
|
+
stream.once('end', onEnd)
|
|
947
|
+
stream.once('error', onError)
|
|
948
|
+
stream.once('frameError', onFrameError)
|
|
949
|
+
stream.on('aborted', onAborted)
|
|
950
|
+
stream.on('timeout', onTimeout)
|
|
951
|
+
stream.once('trailers', onTrailers)
|
|
904
952
|
|
|
905
|
-
|
|
906
|
-
|
|
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)
|
|
953
|
+
if (!expectContinue) {
|
|
954
|
+
writeBodyH2.call(stream)
|
|
916
955
|
}
|
|
917
956
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
detachRequestFromStream(request)
|
|
921
|
-
}
|
|
957
|
+
return true
|
|
958
|
+
}
|
|
922
959
|
|
|
923
|
-
|
|
960
|
+
function removeRequestStreamListeners (stream) {
|
|
961
|
+
stream.off('error', noop)
|
|
962
|
+
stream.off('continue', writeBodyH2)
|
|
963
|
+
stream.off('response', onResponse)
|
|
964
|
+
stream.off('end', onEnd)
|
|
965
|
+
stream.off('error', onError)
|
|
966
|
+
stream.off('frameError', onFrameError)
|
|
967
|
+
stream.off('aborted', onAborted)
|
|
968
|
+
stream.off('timeout', onTimeout)
|
|
969
|
+
stream.off('trailers', onTrailers)
|
|
970
|
+
stream.off('data', onData)
|
|
971
|
+
}
|
|
924
972
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
973
|
+
function releaseRequestStream (stream) {
|
|
974
|
+
if (stream == null) {
|
|
975
|
+
return
|
|
928
976
|
}
|
|
929
977
|
|
|
930
|
-
const
|
|
931
|
-
|
|
978
|
+
const state = stream[kRequestStreamState]
|
|
979
|
+
if (state == null) {
|
|
980
|
+
return
|
|
981
|
+
}
|
|
932
982
|
|
|
933
|
-
|
|
934
|
-
delete headers[HTTP2_HEADER_STATUS]
|
|
935
|
-
request.onResponseStarted()
|
|
936
|
-
responseReceived = true
|
|
983
|
+
const { request } = state
|
|
937
984
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
// for those scenarios, best effort is to destroy the stream immediately
|
|
942
|
-
// as there's no value to keep it open.
|
|
943
|
-
if (request.aborted) {
|
|
944
|
-
releaseRequestStream()
|
|
945
|
-
return
|
|
946
|
-
}
|
|
985
|
+
if (request[kRequestStream] === stream) {
|
|
986
|
+
detachRequestFromStream(request)
|
|
987
|
+
}
|
|
947
988
|
|
|
948
|
-
|
|
949
|
-
stream.pause()
|
|
950
|
-
}
|
|
989
|
+
removeRequestStreamListeners(stream)
|
|
951
990
|
|
|
952
|
-
|
|
991
|
+
if (!stream.destroyed && !stream.closed) {
|
|
992
|
+
stream.once('error', noop)
|
|
953
993
|
}
|
|
994
|
+
}
|
|
954
995
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
releaseRequestStream()
|
|
959
|
-
// If we received a response, this is a normal completion
|
|
960
|
-
if (responseReceived) {
|
|
961
|
-
if (!request.aborted && !request.completed) {
|
|
962
|
-
request.onResponseEnd({})
|
|
963
|
-
}
|
|
996
|
+
function onData (chunk) {
|
|
997
|
+
const stream = this
|
|
998
|
+
const { request } = stream[kRequestStreamState]
|
|
964
999
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
// Stream ended without receiving a response - this is an error
|
|
968
|
-
// (e.g., server destroyed the stream before sending headers)
|
|
969
|
-
abort(new InformationalError('HTTP/2: stream half-closed (remote)'), true)
|
|
970
|
-
}
|
|
1000
|
+
if (request.aborted || request.completed) {
|
|
1001
|
+
return
|
|
971
1002
|
}
|
|
972
1003
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1004
|
+
if (request.onResponseData(chunk) === false) {
|
|
1005
|
+
stream.pause()
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
976
1008
|
|
|
977
|
-
|
|
978
|
-
|
|
1009
|
+
function onResponse (headers) {
|
|
1010
|
+
const stream = this
|
|
1011
|
+
const state = stream[kRequestStreamState]
|
|
1012
|
+
const { request } = state
|
|
979
1013
|
|
|
980
|
-
|
|
981
|
-
abort(err)
|
|
982
|
-
}
|
|
1014
|
+
stream.off('response', onResponse)
|
|
983
1015
|
|
|
984
|
-
const
|
|
985
|
-
|
|
1016
|
+
const statusCode = headers[HTTP2_HEADER_STATUS]
|
|
1017
|
+
delete headers[HTTP2_HEADER_STATUS]
|
|
1018
|
+
request.onResponseStarted()
|
|
1019
|
+
state.responseReceived = true
|
|
986
1020
|
|
|
987
|
-
|
|
988
|
-
|
|
1021
|
+
// Due to the stream nature, it is possible we face a race condition
|
|
1022
|
+
// where the stream has been assigned, but the request has been aborted
|
|
1023
|
+
// the request remains in-flight and headers hasn't been received yet
|
|
1024
|
+
// for those scenarios, best effort is to destroy the stream immediately
|
|
1025
|
+
// as there's no value to keep it open.
|
|
1026
|
+
if (request.aborted) {
|
|
1027
|
+
releaseRequestStream(stream)
|
|
1028
|
+
return
|
|
989
1029
|
}
|
|
990
1030
|
|
|
991
|
-
|
|
992
|
-
stream.
|
|
1031
|
+
if (request.onResponseStart(Number(statusCode), headers, stream.resume.bind(stream), '') === false) {
|
|
1032
|
+
stream.pause()
|
|
993
1033
|
}
|
|
994
1034
|
|
|
995
|
-
|
|
996
|
-
|
|
1035
|
+
stream.on('data', onData)
|
|
1036
|
+
}
|
|
997
1037
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1038
|
+
function onEnd () {
|
|
1039
|
+
const stream = this
|
|
1040
|
+
const state = stream[kRequestStreamState]
|
|
1041
|
+
const { request } = state
|
|
1001
1042
|
|
|
1002
|
-
|
|
1003
|
-
stream.off('trailers', onTrailers)
|
|
1043
|
+
stream.off('end', onEnd)
|
|
1004
1044
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1045
|
+
releaseRequestStream(stream)
|
|
1046
|
+
// If we received a response, this is a normal completion
|
|
1047
|
+
if (state.responseReceived) {
|
|
1048
|
+
if (!request.aborted && !request.completed) {
|
|
1049
|
+
request.onResponseEnd({})
|
|
1007
1050
|
}
|
|
1008
1051
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1052
|
+
state.finalizeRequest()
|
|
1053
|
+
} else {
|
|
1054
|
+
// Stream ended without receiving a response - this is an error
|
|
1055
|
+
// (e.g., server destroyed the stream before sending headers)
|
|
1056
|
+
state.abort(new InformationalError('HTTP/2: stream half-closed (remote)'), true)
|
|
1012
1057
|
}
|
|
1058
|
+
}
|
|
1013
1059
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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)
|
|
1060
|
+
function onError (err) {
|
|
1061
|
+
const stream = this
|
|
1062
|
+
const state = stream[kRequestStreamState]
|
|
1025
1063
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1064
|
+
stream.off('error', onError)
|
|
1065
|
+
|
|
1066
|
+
releaseRequestStream(stream)
|
|
1067
|
+
state.abort(err)
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function onFrameError (type, code) {
|
|
1071
|
+
const stream = this
|
|
1072
|
+
const state = stream[kRequestStreamState]
|
|
1073
|
+
|
|
1074
|
+
stream.off('frameError', onFrameError)
|
|
1075
|
+
|
|
1076
|
+
releaseRequestStream(stream)
|
|
1077
|
+
state.abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function onAborted () {
|
|
1081
|
+
this.off('data', onData)
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function onTimeout () {
|
|
1085
|
+
const stream = this
|
|
1086
|
+
const state = stream[kRequestStreamState]
|
|
1087
|
+
|
|
1088
|
+
releaseRequestStream(stream)
|
|
1089
|
+
|
|
1090
|
+
const err = new InformationalError(`HTTP/2: "stream timeout after ${state.requestTimeout}"`)
|
|
1091
|
+
state.abort(err)
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function onTrailers (trailers) {
|
|
1095
|
+
const stream = this
|
|
1096
|
+
const state = stream[kRequestStreamState]
|
|
1097
|
+
const { request } = state
|
|
1098
|
+
|
|
1099
|
+
stream.off('trailers', onTrailers)
|
|
1100
|
+
|
|
1101
|
+
if (request.aborted || request.completed) {
|
|
1102
|
+
return
|
|
1028
1103
|
}
|
|
1029
1104
|
|
|
1030
|
-
|
|
1105
|
+
releaseRequestStream(stream)
|
|
1106
|
+
request.onResponseEnd(trailers)
|
|
1107
|
+
state.finalizeRequest()
|
|
1108
|
+
}
|
|
1031
1109
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1110
|
+
function writeBodyH2 () {
|
|
1111
|
+
const stream = this
|
|
1112
|
+
const state = stream[kRequestStreamState]
|
|
1113
|
+
const { abort, body, client, contentLength, expectsPayload, request } = state
|
|
1114
|
+
|
|
1115
|
+
if (!body || contentLength === 0) {
|
|
1116
|
+
writeBuffer(
|
|
1117
|
+
abort,
|
|
1118
|
+
stream,
|
|
1119
|
+
null,
|
|
1120
|
+
client,
|
|
1121
|
+
request,
|
|
1122
|
+
client[kSocket],
|
|
1123
|
+
contentLength,
|
|
1124
|
+
expectsPayload
|
|
1125
|
+
)
|
|
1126
|
+
} else if (util.isBuffer(body)) {
|
|
1127
|
+
writeBuffer(
|
|
1128
|
+
abort,
|
|
1129
|
+
stream,
|
|
1130
|
+
body,
|
|
1131
|
+
client,
|
|
1132
|
+
request,
|
|
1133
|
+
client[kSocket],
|
|
1134
|
+
contentLength,
|
|
1135
|
+
expectsPayload
|
|
1136
|
+
)
|
|
1137
|
+
} else if (util.isBlobLike(body)) {
|
|
1138
|
+
if (typeof body.stream === 'function') {
|
|
1139
|
+
writeIterable(
|
|
1046
1140
|
abort,
|
|
1047
1141
|
stream,
|
|
1048
|
-
body,
|
|
1142
|
+
body.stream(),
|
|
1049
1143
|
client,
|
|
1050
1144
|
request,
|
|
1051
1145
|
client[kSocket],
|
|
1052
1146
|
contentLength,
|
|
1053
1147
|
expectsPayload
|
|
1054
1148
|
)
|
|
1055
|
-
} else
|
|
1056
|
-
|
|
1057
|
-
writeIterable(
|
|
1058
|
-
abort,
|
|
1059
|
-
stream,
|
|
1060
|
-
body.stream(),
|
|
1061
|
-
client,
|
|
1062
|
-
request,
|
|
1063
|
-
client[kSocket],
|
|
1064
|
-
contentLength,
|
|
1065
|
-
expectsPayload
|
|
1066
|
-
)
|
|
1067
|
-
} else {
|
|
1068
|
-
writeBlob(
|
|
1069
|
-
abort,
|
|
1070
|
-
stream,
|
|
1071
|
-
body,
|
|
1072
|
-
client,
|
|
1073
|
-
request,
|
|
1074
|
-
client[kSocket],
|
|
1075
|
-
contentLength,
|
|
1076
|
-
expectsPayload
|
|
1077
|
-
)
|
|
1078
|
-
}
|
|
1079
|
-
} else if (util.isStream(body)) {
|
|
1080
|
-
writeStream(
|
|
1081
|
-
abort,
|
|
1082
|
-
client[kSocket],
|
|
1083
|
-
expectsPayload,
|
|
1084
|
-
stream,
|
|
1085
|
-
body,
|
|
1086
|
-
client,
|
|
1087
|
-
request,
|
|
1088
|
-
contentLength
|
|
1089
|
-
)
|
|
1090
|
-
} else if (util.isIterable(body)) {
|
|
1091
|
-
writeIterable(
|
|
1149
|
+
} else {
|
|
1150
|
+
writeBlob(
|
|
1092
1151
|
abort,
|
|
1093
1152
|
stream,
|
|
1094
1153
|
body,
|
|
@@ -1098,9 +1157,31 @@ function writeH2 (client, request) {
|
|
|
1098
1157
|
contentLength,
|
|
1099
1158
|
expectsPayload
|
|
1100
1159
|
)
|
|
1101
|
-
} else {
|
|
1102
|
-
assert(false)
|
|
1103
1160
|
}
|
|
1161
|
+
} else if (util.isStream(body)) {
|
|
1162
|
+
writeStream(
|
|
1163
|
+
abort,
|
|
1164
|
+
client[kSocket],
|
|
1165
|
+
expectsPayload,
|
|
1166
|
+
stream,
|
|
1167
|
+
body,
|
|
1168
|
+
client,
|
|
1169
|
+
request,
|
|
1170
|
+
contentLength
|
|
1171
|
+
)
|
|
1172
|
+
} else if (util.isIterable(body)) {
|
|
1173
|
+
writeIterable(
|
|
1174
|
+
abort,
|
|
1175
|
+
stream,
|
|
1176
|
+
body,
|
|
1177
|
+
client,
|
|
1178
|
+
request,
|
|
1179
|
+
client[kSocket],
|
|
1180
|
+
contentLength,
|
|
1181
|
+
expectsPayload
|
|
1182
|
+
)
|
|
1183
|
+
} else {
|
|
1184
|
+
assert(false)
|
|
1104
1185
|
}
|
|
1105
1186
|
}
|
|
1106
1187
|
|
|
@@ -1159,8 +1240,6 @@ function writeStream (abort, socket, expectsPayload, h2stream, body, client, req
|
|
|
1159
1240
|
}
|
|
1160
1241
|
|
|
1161
1242
|
async function writeBlob (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
|
|
1162
|
-
assert(contentLength === body.size, 'blob body must have content length')
|
|
1163
|
-
|
|
1164
1243
|
try {
|
|
1165
1244
|
if (contentLength != null && contentLength !== body.size) {
|
|
1166
1245
|
throw new RequestContentLengthMismatchError()
|