undici 6.11.1 → 6.13.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 +12 -0
- package/docs/docs/api/Dispatcher.md +6 -0
- package/lib/api/abort-signal.js +4 -1
- package/lib/api/api-connect.js +7 -3
- package/lib/api/api-pipeline.js +6 -4
- package/lib/api/api-request.js +7 -6
- package/lib/api/api-stream.js +7 -7
- package/lib/api/api-upgrade.js +6 -3
- package/lib/api/readable.js +1 -3
- package/lib/core/util.js +41 -8
- package/lib/dispatcher/client-h1.js +63 -68
- package/lib/dispatcher/client-h2.js +172 -142
- package/lib/dispatcher/client.js +7 -13
- package/lib/dispatcher/proxy-agent.js +11 -9
- package/lib/handler/decorator-handler.js +17 -8
- package/lib/mock/mock-interceptor.js +18 -17
- package/lib/mock/mock-utils.js +5 -5
- package/lib/web/fetch/body.js +2 -2
- package/lib/web/fetch/constants.js +2 -2
- package/lib/web/fetch/formdata-parser.js +2 -40
- package/lib/web/fetch/index.js +7 -30
- package/lib/web/fetch/util.js +29 -30
- package/lib/web/websocket/receiver.js +2 -3
- package/lib/web/websocket/util.js +28 -2
- package/package.json +12 -8
- package/types/dispatcher.d.ts +1 -1
- package/types/fetch.d.ts +1 -1
package/README.md
CHANGED
|
@@ -248,6 +248,18 @@ const data = {
|
|
|
248
248
|
await fetch('https://example.com', { body: data, method: 'POST', duplex: 'half' })
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
+
[FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) besides text data and buffers can also utilize streams via [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects:
|
|
252
|
+
|
|
253
|
+
```js
|
|
254
|
+
import { openAsBlob } from 'node:fs'
|
|
255
|
+
|
|
256
|
+
const file = await openAsBlob('./big.csv')
|
|
257
|
+
const body = new FormData()
|
|
258
|
+
body.set('file', file, 'big.csv')
|
|
259
|
+
|
|
260
|
+
await fetch('http://example.com', { method: 'POST', body })
|
|
261
|
+
```
|
|
262
|
+
|
|
251
263
|
#### `request.duplex`
|
|
252
264
|
|
|
253
265
|
- half
|
|
@@ -969,6 +969,12 @@ Parameters:
|
|
|
969
969
|
* **targets** `Array<Dispatcher>`
|
|
970
970
|
* **error** `Error`
|
|
971
971
|
|
|
972
|
+
Emitted when the dispatcher has been disconnected from the origin.
|
|
973
|
+
|
|
974
|
+
> **Note**: For HTTP/2, this event is also emitted when the dispatcher has received the [GOAWAY Frame](https://webconcepts.info/concepts/http2-frame-type/0x7) with an Error with the message `HTTP/2: "GOAWAY" frame received` and the code `UND_ERR_INFO`.
|
|
975
|
+
> Due to nature of the protocol of using binary frames, it is possible that requests gets hanging as a frame can be received between the `HEADER` and `DATA` frames.
|
|
976
|
+
> It is recommended to handle this event and close the dispatcher to create a new HTTP/2 session.
|
|
977
|
+
|
|
972
978
|
### Event: `'connectionError'`
|
|
973
979
|
|
|
974
980
|
Parameters:
|
package/lib/api/abort-signal.js
CHANGED
|
@@ -8,11 +8,14 @@ function abort (self) {
|
|
|
8
8
|
if (self.abort) {
|
|
9
9
|
self.abort(self[kSignal]?.reason)
|
|
10
10
|
} else {
|
|
11
|
-
self.
|
|
11
|
+
self.reason = self[kSignal]?.reason ?? new RequestAbortedError()
|
|
12
12
|
}
|
|
13
|
+
removeSignal(self)
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function addSignal (self, signal) {
|
|
17
|
+
self.reason = null
|
|
18
|
+
|
|
16
19
|
self[kSignal] = null
|
|
17
20
|
self[kListener] = null
|
|
18
21
|
|
package/lib/api/api-connect.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const assert = require('node:assert')
|
|
3
4
|
const { AsyncResource } = require('node:async_hooks')
|
|
4
|
-
const { InvalidArgumentError,
|
|
5
|
+
const { InvalidArgumentError, SocketError } = require('../core/errors')
|
|
5
6
|
const util = require('../core/util')
|
|
6
7
|
const { addSignal, removeSignal } = require('./abort-signal')
|
|
7
8
|
|
|
@@ -32,10 +33,13 @@ class ConnectHandler extends AsyncResource {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
onConnect (abort, context) {
|
|
35
|
-
if (
|
|
36
|
-
|
|
36
|
+
if (this.reason) {
|
|
37
|
+
abort(this.reason)
|
|
38
|
+
return
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
assert(this.callback)
|
|
42
|
+
|
|
39
43
|
this.abort = abort
|
|
40
44
|
this.context = context
|
|
41
45
|
}
|
package/lib/api/api-pipeline.js
CHANGED
|
@@ -147,12 +147,14 @@ class PipelineHandler extends AsyncResource {
|
|
|
147
147
|
onConnect (abort, context) {
|
|
148
148
|
const { ret, res } = this
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
throw new RequestAbortedError()
|
|
150
|
+
if (this.reason) {
|
|
151
|
+
abort(this.reason)
|
|
152
|
+
return
|
|
154
153
|
}
|
|
155
154
|
|
|
155
|
+
assert(!res, 'pipeline cannot be retried')
|
|
156
|
+
assert(!ret.destroyed)
|
|
157
|
+
|
|
156
158
|
this.abort = abort
|
|
157
159
|
this.context = context
|
|
158
160
|
}
|
package/lib/api/api-request.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const assert = require('node:assert')
|
|
3
4
|
const { Readable } = require('./readable')
|
|
4
|
-
const {
|
|
5
|
-
InvalidArgumentError,
|
|
6
|
-
RequestAbortedError
|
|
7
|
-
} = require('../core/errors')
|
|
5
|
+
const { InvalidArgumentError } = require('../core/errors')
|
|
8
6
|
const util = require('../core/util')
|
|
9
7
|
const { getResolveErrorBodyCallback } = require('./util')
|
|
10
8
|
const { AsyncResource } = require('node:async_hooks')
|
|
@@ -69,10 +67,13 @@ class RequestHandler extends AsyncResource {
|
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
onConnect (abort, context) {
|
|
72
|
-
if (
|
|
73
|
-
|
|
70
|
+
if (this.reason) {
|
|
71
|
+
abort(this.reason)
|
|
72
|
+
return
|
|
74
73
|
}
|
|
75
74
|
|
|
75
|
+
assert(this.callback)
|
|
76
|
+
|
|
76
77
|
this.abort = abort
|
|
77
78
|
this.context = context
|
|
78
79
|
}
|
package/lib/api/api-stream.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const assert = require('node:assert')
|
|
3
4
|
const { finished, PassThrough } = require('node:stream')
|
|
4
|
-
const {
|
|
5
|
-
InvalidArgumentError,
|
|
6
|
-
InvalidReturnValueError,
|
|
7
|
-
RequestAbortedError
|
|
8
|
-
} = require('../core/errors')
|
|
5
|
+
const { InvalidArgumentError, InvalidReturnValueError } = require('../core/errors')
|
|
9
6
|
const util = require('../core/util')
|
|
10
7
|
const { getResolveErrorBodyCallback } = require('./util')
|
|
11
8
|
const { AsyncResource } = require('node:async_hooks')
|
|
@@ -70,10 +67,13 @@ class StreamHandler extends AsyncResource {
|
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
onConnect (abort, context) {
|
|
73
|
-
if (
|
|
74
|
-
|
|
70
|
+
if (this.reason) {
|
|
71
|
+
abort(this.reason)
|
|
72
|
+
return
|
|
75
73
|
}
|
|
76
74
|
|
|
75
|
+
assert(this.callback)
|
|
76
|
+
|
|
77
77
|
this.abort = abort
|
|
78
78
|
this.context = context
|
|
79
79
|
}
|
package/lib/api/api-upgrade.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { InvalidArgumentError,
|
|
3
|
+
const { InvalidArgumentError, SocketError } = require('../core/errors')
|
|
4
4
|
const { AsyncResource } = require('node:async_hooks')
|
|
5
5
|
const util = require('../core/util')
|
|
6
6
|
const { addSignal, removeSignal } = require('./abort-signal')
|
|
@@ -34,10 +34,13 @@ class UpgradeHandler extends AsyncResource {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
onConnect (abort, context) {
|
|
37
|
-
if (
|
|
38
|
-
|
|
37
|
+
if (this.reason) {
|
|
38
|
+
abort(this.reason)
|
|
39
|
+
return
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
assert(this.callback)
|
|
43
|
+
|
|
41
44
|
this.abort = abort
|
|
42
45
|
this.context = null
|
|
43
46
|
}
|
package/lib/api/readable.js
CHANGED
|
@@ -63,9 +63,7 @@ class BodyReadable extends Readable {
|
|
|
63
63
|
// tick as it is created, then a user who is waiting for a
|
|
64
64
|
// promise (i.e micro tick) for installing a 'error' listener will
|
|
65
65
|
// never get a chance and will always encounter an unhandled exception.
|
|
66
|
-
|
|
67
|
-
// - micro tick => queueMicrotask(fn)
|
|
68
|
-
queueMicrotask(() => {
|
|
66
|
+
setImmediate(() => {
|
|
69
67
|
callback(err)
|
|
70
68
|
})
|
|
71
69
|
}
|
package/lib/core/util.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
-
const { kDestroyed, kBodyUsed } = require('./symbols')
|
|
4
|
+
const { kDestroyed, kBodyUsed, kListeners } = require('./symbols')
|
|
5
5
|
const { IncomingMessage } = require('node:http')
|
|
6
6
|
const stream = require('node:stream')
|
|
7
7
|
const net = require('node:net')
|
|
@@ -22,13 +22,20 @@ function isStream (obj) {
|
|
|
22
22
|
|
|
23
23
|
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
|
|
24
24
|
function isBlobLike (object) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
if (object === null) {
|
|
26
|
+
return false
|
|
27
|
+
} else if (object instanceof Blob) {
|
|
28
|
+
return true
|
|
29
|
+
} else if (typeof object !== 'object') {
|
|
30
|
+
return false
|
|
31
|
+
} else {
|
|
32
|
+
const sTag = object[Symbol.toStringTag]
|
|
33
|
+
|
|
34
|
+
return (sTag === 'Blob' || sTag === 'File') && (
|
|
35
|
+
('stream' in object && typeof object.stream === 'function') ||
|
|
36
|
+
('arrayBuffer' in object && typeof object.arrayBuffer === 'function')
|
|
37
|
+
)
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
function buildURL (url, queryParams) {
|
|
@@ -534,6 +541,29 @@ function parseRangeHeader (range) {
|
|
|
534
541
|
: null
|
|
535
542
|
}
|
|
536
543
|
|
|
544
|
+
function addListener (obj, name, listener) {
|
|
545
|
+
const listeners = (obj[kListeners] ??= [])
|
|
546
|
+
listeners.push([name, listener])
|
|
547
|
+
obj.on(name, listener)
|
|
548
|
+
return obj
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function removeAllListeners (obj) {
|
|
552
|
+
for (const [name, listener] of obj[kListeners] ?? []) {
|
|
553
|
+
obj.removeListener(name, listener)
|
|
554
|
+
}
|
|
555
|
+
obj[kListeners] = null
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function errorRequest (client, request, err) {
|
|
559
|
+
try {
|
|
560
|
+
request.onError(err)
|
|
561
|
+
assert(request.aborted)
|
|
562
|
+
} catch (err) {
|
|
563
|
+
client.emit('error', err)
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
537
567
|
const kEnumerableProperty = Object.create(null)
|
|
538
568
|
kEnumerableProperty.enumerable = true
|
|
539
569
|
|
|
@@ -556,6 +586,9 @@ module.exports = {
|
|
|
556
586
|
isDestroyed,
|
|
557
587
|
headerNameToString,
|
|
558
588
|
bufferToLowerCasedHeaderName,
|
|
589
|
+
addListener,
|
|
590
|
+
removeAllListeners,
|
|
591
|
+
errorRequest,
|
|
559
592
|
parseRawHeaders,
|
|
560
593
|
parseHeaders,
|
|
561
594
|
parseKeepAliveTimeout,
|
|
@@ -47,7 +47,6 @@ const {
|
|
|
47
47
|
kMaxRequests,
|
|
48
48
|
kCounter,
|
|
49
49
|
kMaxResponseSize,
|
|
50
|
-
kListeners,
|
|
51
50
|
kOnError,
|
|
52
51
|
kResume,
|
|
53
52
|
kHTTPContext
|
|
@@ -56,23 +55,11 @@ const {
|
|
|
56
55
|
const constants = require('../llhttp/constants.js')
|
|
57
56
|
const EMPTY_BUF = Buffer.alloc(0)
|
|
58
57
|
const FastBuffer = Buffer[Symbol.species]
|
|
58
|
+
const addListener = util.addListener
|
|
59
|
+
const removeAllListeners = util.removeAllListeners
|
|
59
60
|
|
|
60
61
|
let extractBody
|
|
61
62
|
|
|
62
|
-
function addListener (obj, name, listener) {
|
|
63
|
-
const listeners = (obj[kListeners] ??= [])
|
|
64
|
-
listeners.push([name, listener])
|
|
65
|
-
obj.on(name, listener)
|
|
66
|
-
return obj
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function removeAllListeners (obj) {
|
|
70
|
-
for (const [name, listener] of obj[kListeners] ?? []) {
|
|
71
|
-
obj.removeListener(name, listener)
|
|
72
|
-
}
|
|
73
|
-
obj[kListeners] = null
|
|
74
|
-
}
|
|
75
|
-
|
|
76
63
|
async function lazyllhttp () {
|
|
77
64
|
const llhttpWasmData = process.env.JEST_WORKER_ID ? require('../llhttp/llhttp-wasm.js') : undefined
|
|
78
65
|
|
|
@@ -719,14 +706,14 @@ async function connectH1 (client, socket) {
|
|
|
719
706
|
const requests = client[kQueue].splice(client[kRunningIdx])
|
|
720
707
|
for (let i = 0; i < requests.length; i++) {
|
|
721
708
|
const request = requests[i]
|
|
722
|
-
errorRequest(client, request, err)
|
|
709
|
+
util.errorRequest(client, request, err)
|
|
723
710
|
}
|
|
724
711
|
} else if (client[kRunning] > 0 && err.code !== 'UND_ERR_INFO') {
|
|
725
712
|
// Fail head of pipeline.
|
|
726
713
|
const request = client[kQueue][client[kRunningIdx]]
|
|
727
714
|
client[kQueue][client[kRunningIdx]++] = null
|
|
728
715
|
|
|
729
|
-
errorRequest(client, request, err)
|
|
716
|
+
util.errorRequest(client, request, err)
|
|
730
717
|
}
|
|
731
718
|
|
|
732
719
|
client[kPendingIdx] = client[kRunningIdx]
|
|
@@ -831,15 +818,6 @@ function resumeH1 (client) {
|
|
|
831
818
|
}
|
|
832
819
|
}
|
|
833
820
|
|
|
834
|
-
function errorRequest (client, request, err) {
|
|
835
|
-
try {
|
|
836
|
-
request.onError(err)
|
|
837
|
-
assert(request.aborted)
|
|
838
|
-
} catch (err) {
|
|
839
|
-
client.emit('error', err)
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
821
|
// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
|
|
844
822
|
function shouldSendContentLength (method) {
|
|
845
823
|
return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT'
|
|
@@ -906,7 +884,7 @@ function writeH1 (client, request) {
|
|
|
906
884
|
// A user agent may send a Content-Length header with 0 value, this should be allowed.
|
|
907
885
|
if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength !== null && request.contentLength !== contentLength) {
|
|
908
886
|
if (client[kStrictContentLength]) {
|
|
909
|
-
errorRequest(client, request, new RequestContentLengthMismatchError())
|
|
887
|
+
util.errorRequest(client, request, new RequestContentLengthMismatchError())
|
|
910
888
|
return false
|
|
911
889
|
}
|
|
912
890
|
|
|
@@ -915,22 +893,24 @@ function writeH1 (client, request) {
|
|
|
915
893
|
|
|
916
894
|
const socket = client[kSocket]
|
|
917
895
|
|
|
918
|
-
|
|
919
|
-
request.
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
}
|
|
896
|
+
const abort = (err) => {
|
|
897
|
+
if (request.aborted || request.completed) {
|
|
898
|
+
return
|
|
899
|
+
}
|
|
923
900
|
|
|
924
|
-
|
|
901
|
+
util.errorRequest(client, request, err || new RequestAbortedError())
|
|
925
902
|
|
|
926
|
-
|
|
927
|
-
|
|
903
|
+
util.destroy(body)
|
|
904
|
+
util.destroy(socket, new InformationalError('aborted'))
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
try {
|
|
908
|
+
request.onConnect(abort)
|
|
928
909
|
} catch (err) {
|
|
929
|
-
errorRequest(client, request, err)
|
|
910
|
+
util.errorRequest(client, request, err)
|
|
930
911
|
}
|
|
931
912
|
|
|
932
913
|
if (request.aborted) {
|
|
933
|
-
util.destroy(body)
|
|
934
914
|
return false
|
|
935
915
|
}
|
|
936
916
|
|
|
@@ -998,35 +978,19 @@ function writeH1 (client, request) {
|
|
|
998
978
|
|
|
999
979
|
/* istanbul ignore else: assertion */
|
|
1000
980
|
if (!body || bodyLength === 0) {
|
|
1001
|
-
|
|
1002
|
-
socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1')
|
|
1003
|
-
} else {
|
|
1004
|
-
assert(contentLength === null, 'no body must not have content length')
|
|
1005
|
-
socket.write(`${header}\r\n`, 'latin1')
|
|
1006
|
-
}
|
|
1007
|
-
request.onRequestSent()
|
|
981
|
+
writeBuffer({ abort, body: null, client, request, socket, contentLength, header, expectsPayload })
|
|
1008
982
|
} else if (util.isBuffer(body)) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
socket.cork()
|
|
1012
|
-
socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1')
|
|
1013
|
-
socket.write(body)
|
|
1014
|
-
socket.uncork()
|
|
1015
|
-
request.onBodySent(body)
|
|
1016
|
-
request.onRequestSent()
|
|
1017
|
-
if (!expectsPayload) {
|
|
1018
|
-
socket[kReset] = true
|
|
1019
|
-
}
|
|
983
|
+
writeBuffer({ abort, body, client, request, socket, contentLength, header, expectsPayload })
|
|
1020
984
|
} else if (util.isBlobLike(body)) {
|
|
1021
985
|
if (typeof body.stream === 'function') {
|
|
1022
|
-
writeIterable({ body: body.stream(), client, request, socket, contentLength, header, expectsPayload })
|
|
986
|
+
writeIterable({ abort, body: body.stream(), client, request, socket, contentLength, header, expectsPayload })
|
|
1023
987
|
} else {
|
|
1024
|
-
writeBlob({ body, client, request, socket, contentLength, header, expectsPayload })
|
|
988
|
+
writeBlob({ abort, body, client, request, socket, contentLength, header, expectsPayload })
|
|
1025
989
|
}
|
|
1026
990
|
} else if (util.isStream(body)) {
|
|
1027
|
-
writeStream({ body, client, request, socket, contentLength, header, expectsPayload })
|
|
991
|
+
writeStream({ abort, body, client, request, socket, contentLength, header, expectsPayload })
|
|
1028
992
|
} else if (util.isIterable(body)) {
|
|
1029
|
-
writeIterable({ body, client, request, socket, contentLength, header, expectsPayload })
|
|
993
|
+
writeIterable({ abort, body, client, request, socket, contentLength, header, expectsPayload })
|
|
1030
994
|
} else {
|
|
1031
995
|
assert(false)
|
|
1032
996
|
}
|
|
@@ -1034,12 +998,12 @@ function writeH1 (client, request) {
|
|
|
1034
998
|
return true
|
|
1035
999
|
}
|
|
1036
1000
|
|
|
1037
|
-
function writeStream ({
|
|
1001
|
+
function writeStream ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
|
|
1038
1002
|
assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
|
|
1039
1003
|
|
|
1040
1004
|
let finished = false
|
|
1041
1005
|
|
|
1042
|
-
const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header })
|
|
1006
|
+
const writer = new AsyncWriter({ abort, socket, request, contentLength, client, expectsPayload, header })
|
|
1043
1007
|
|
|
1044
1008
|
const onData = function (chunk) {
|
|
1045
1009
|
if (finished) {
|
|
@@ -1137,7 +1101,37 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
|
|
|
1137
1101
|
}
|
|
1138
1102
|
}
|
|
1139
1103
|
|
|
1140
|
-
async function
|
|
1104
|
+
async function writeBuffer ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
|
|
1105
|
+
try {
|
|
1106
|
+
if (!body) {
|
|
1107
|
+
if (contentLength === 0) {
|
|
1108
|
+
socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1')
|
|
1109
|
+
} else {
|
|
1110
|
+
assert(contentLength === null, 'no body must not have content length')
|
|
1111
|
+
socket.write(`${header}\r\n`, 'latin1')
|
|
1112
|
+
}
|
|
1113
|
+
} else if (util.isBuffer(body)) {
|
|
1114
|
+
assert(contentLength === body.byteLength, 'buffer body must have content length')
|
|
1115
|
+
|
|
1116
|
+
socket.cork()
|
|
1117
|
+
socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1')
|
|
1118
|
+
socket.write(body)
|
|
1119
|
+
socket.uncork()
|
|
1120
|
+
request.onBodySent(body)
|
|
1121
|
+
|
|
1122
|
+
if (!expectsPayload) {
|
|
1123
|
+
socket[kReset] = true
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
request.onRequestSent()
|
|
1127
|
+
|
|
1128
|
+
client[kResume]()
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
abort(err)
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
async function writeBlob ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
|
|
1141
1135
|
assert(contentLength === body.size, 'blob body must have content length')
|
|
1142
1136
|
|
|
1143
1137
|
try {
|
|
@@ -1161,11 +1155,11 @@ async function writeBlob ({ h2stream, body, client, request, socket, contentLeng
|
|
|
1161
1155
|
|
|
1162
1156
|
client[kResume]()
|
|
1163
1157
|
} catch (err) {
|
|
1164
|
-
|
|
1158
|
+
abort(err)
|
|
1165
1159
|
}
|
|
1166
1160
|
}
|
|
1167
1161
|
|
|
1168
|
-
async function writeIterable ({
|
|
1162
|
+
async function writeIterable ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
|
|
1169
1163
|
assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
|
|
1170
1164
|
|
|
1171
1165
|
let callback = null
|
|
@@ -1191,7 +1185,7 @@ async function writeIterable ({ h2stream, body, client, request, socket, content
|
|
|
1191
1185
|
.on('close', onDrain)
|
|
1192
1186
|
.on('drain', onDrain)
|
|
1193
1187
|
|
|
1194
|
-
const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header })
|
|
1188
|
+
const writer = new AsyncWriter({ abort, socket, request, contentLength, client, expectsPayload, header })
|
|
1195
1189
|
try {
|
|
1196
1190
|
// It's up to the user to somehow abort the async iterable.
|
|
1197
1191
|
for await (const chunk of body) {
|
|
@@ -1215,7 +1209,7 @@ async function writeIterable ({ h2stream, body, client, request, socket, content
|
|
|
1215
1209
|
}
|
|
1216
1210
|
|
|
1217
1211
|
class AsyncWriter {
|
|
1218
|
-
constructor ({ socket, request, contentLength, client, expectsPayload, header }) {
|
|
1212
|
+
constructor ({ abort, socket, request, contentLength, client, expectsPayload, header }) {
|
|
1219
1213
|
this.socket = socket
|
|
1220
1214
|
this.request = request
|
|
1221
1215
|
this.contentLength = contentLength
|
|
@@ -1223,6 +1217,7 @@ class AsyncWriter {
|
|
|
1223
1217
|
this.bytesWritten = 0
|
|
1224
1218
|
this.expectsPayload = expectsPayload
|
|
1225
1219
|
this.header = header
|
|
1220
|
+
this.abort = abort
|
|
1226
1221
|
|
|
1227
1222
|
socket[kWriting] = true
|
|
1228
1223
|
}
|
|
@@ -1338,13 +1333,13 @@ class AsyncWriter {
|
|
|
1338
1333
|
}
|
|
1339
1334
|
|
|
1340
1335
|
destroy (err) {
|
|
1341
|
-
const { socket, client } = this
|
|
1336
|
+
const { socket, client, abort } = this
|
|
1342
1337
|
|
|
1343
1338
|
socket[kWriting] = false
|
|
1344
1339
|
|
|
1345
1340
|
if (err) {
|
|
1346
1341
|
assert(client[kRunning] <= 1, 'pipeline should only contain this request')
|
|
1347
|
-
|
|
1342
|
+
abort(err)
|
|
1348
1343
|
}
|
|
1349
1344
|
}
|
|
1350
1345
|
}
|