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.
@@ -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 kRequestStreamOnData = Symbol('request stream on data')
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 failUpgradeStream = this[kRequestStreamOnCloseError]
520
- this[kRequestStreamOnCloseError] = null
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 writeH2 (client, request) {
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 (headers[key] != null) {
558
- headers[key] = Array.isArray(headers[key]) ? (headers[key].push(val), headers[key]) : [headers[key], val]
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 (Array.isArray(val)) {
567
- for (let i = 0; i < val.length; i++) {
568
- if (headers[key]) {
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
- const { hostname, port } = client[kUrl]
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 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)
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[kHTTP2Stream] = true
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[kHTTP2Stream] = true
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
- // Track whether we received a response (headers)
894
- let responseReceived = false
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
- if (request.onResponseData(chunk) === false) {
901
- stream.pause()
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
- 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)
953
+ if (!expectContinue) {
954
+ writeBodyH2.call(stream)
916
955
  }
917
956
 
918
- const releaseRequestStream = () => {
919
- if (request[kRequestStream] === stream) {
920
- detachRequestFromStream(request)
921
- }
957
+ return true
958
+ }
922
959
 
923
- removeRequestStreamListeners()
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
- if (!stream.destroyed && !stream.closed) {
926
- stream.once('error', noop)
927
- }
973
+ function releaseRequestStream (stream) {
974
+ if (stream == null) {
975
+ return
928
976
  }
929
977
 
930
- const onResponse = (headers) => {
931
- stream.off('response', onResponse)
978
+ const state = stream[kRequestStreamState]
979
+ if (state == null) {
980
+ return
981
+ }
932
982
 
933
- const statusCode = headers[HTTP2_HEADER_STATUS]
934
- delete headers[HTTP2_HEADER_STATUS]
935
- request.onResponseStarted()
936
- responseReceived = true
983
+ const { request } = state
937
984
 
938
- // Due to the stream nature, it is possible we face a race condition
939
- // where the stream has been assigned, but the request has been aborted
940
- // the request remains in-flight and headers hasn't been received yet
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
- if (request.onResponseStart(Number(statusCode), headers, stream.resume.bind(stream), '') === false) {
949
- stream.pause()
950
- }
989
+ removeRequestStreamListeners(stream)
951
990
 
952
- stream.on('data', onData)
991
+ if (!stream.destroyed && !stream.closed) {
992
+ stream.once('error', noop)
953
993
  }
994
+ }
954
995
 
955
- const onEnd = () => {
956
- stream.off('end', onEnd)
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
- finalizeRequest()
966
- } else {
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
- stream[kHTTP2Session] = session
974
- stream[kRequestStreamOnData] = onData
975
- stream.once('close', onRequestStreamClose)
1004
+ if (request.onResponseData(chunk) === false) {
1005
+ stream.pause()
1006
+ }
1007
+ }
976
1008
 
977
- const onError = function (err) {
978
- stream.off('error', onError)
1009
+ function onResponse (headers) {
1010
+ const stream = this
1011
+ const state = stream[kRequestStreamState]
1012
+ const { request } = state
979
1013
 
980
- releaseRequestStream()
981
- abort(err)
982
- }
1014
+ stream.off('response', onResponse)
983
1015
 
984
- const onFrameError = (type, code) => {
985
- stream.off('frameError', onFrameError)
1016
+ const statusCode = headers[HTTP2_HEADER_STATUS]
1017
+ delete headers[HTTP2_HEADER_STATUS]
1018
+ request.onResponseStarted()
1019
+ state.responseReceived = true
986
1020
 
987
- releaseRequestStream()
988
- abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
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
- const onAborted = () => {
992
- stream.off('data', onData)
1031
+ if (request.onResponseStart(Number(statusCode), headers, stream.resume.bind(stream), '') === false) {
1032
+ stream.pause()
993
1033
  }
994
1034
 
995
- const onTimeout = () => {
996
- releaseRequestStream()
1035
+ stream.on('data', onData)
1036
+ }
997
1037
 
998
- const err = new InformationalError(`HTTP/2: "stream timeout after ${requestTimeout}"`)
999
- abort(err)
1000
- }
1038
+ function onEnd () {
1039
+ const stream = this
1040
+ const state = stream[kRequestStreamState]
1041
+ const { request } = state
1001
1042
 
1002
- const onTrailers = (trailers) => {
1003
- stream.off('trailers', onTrailers)
1043
+ stream.off('end', onEnd)
1004
1044
 
1005
- if (request.aborted || request.completed) {
1006
- return
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
- releaseRequestStream()
1010
- request.onResponseEnd(trailers)
1011
- finalizeRequest()
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
- 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)
1060
+ function onError (err) {
1061
+ const stream = this
1062
+ const state = stream[kRequestStreamState]
1025
1063
 
1026
- if (!expectContinue) {
1027
- writeBodyH2()
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
- return true
1105
+ releaseRequestStream(stream)
1106
+ request.onResponseEnd(trailers)
1107
+ state.finalizeRequest()
1108
+ }
1031
1109
 
1032
- function writeBodyH2 () {
1033
- if (!body || contentLength === 0) {
1034
- writeBuffer(
1035
- abort,
1036
- stream,
1037
- null,
1038
- client,
1039
- request,
1040
- client[kSocket],
1041
- contentLength,
1042
- expectsPayload
1043
- )
1044
- } else if (util.isBuffer(body)) {
1045
- writeBuffer(
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 if (util.isBlobLike(body)) {
1056
- if (typeof body.stream === 'function') {
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()