undici 6.6.1 → 6.7.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.
Files changed (114) hide show
  1. package/{README.md → docs/README.md} +19 -15
  2. package/docs/{api → docs/api}/Dispatcher.md +39 -3
  3. package/docs/docs/api/Fetch.md +57 -0
  4. package/docs/{api → docs/api}/ProxyAgent.md +3 -1
  5. package/docs/docs/api/RetryAgent.md +45 -0
  6. package/docs/{api → docs/api}/RetryHandler.md +1 -1
  7. package/docs/{api → docs/api}/api-lifecycle.md +33 -4
  8. package/docs/{best-practices → docs/best-practices}/proxy.md +6 -6
  9. package/index-fetch.js +11 -7
  10. package/index.js +31 -25
  11. package/lib/core/request.js +72 -135
  12. package/lib/core/symbols.js +6 -5
  13. package/lib/core/tree.js +46 -26
  14. package/lib/core/util.js +41 -20
  15. package/lib/{agent.js → dispatcher/agent.js} +4 -4
  16. package/lib/{balanced-pool.js → dispatcher/balanced-pool.js} +3 -3
  17. package/lib/dispatcher/client-h1.js +1339 -0
  18. package/lib/dispatcher/client-h2.js +639 -0
  19. package/lib/dispatcher/client.js +611 -0
  20. package/lib/{dispatcher-base.js → dispatcher/dispatcher-base.js} +2 -2
  21. package/lib/{pool-base.js → dispatcher/pool-base.js} +3 -3
  22. package/lib/{pool-stats.js → dispatcher/pool-stats.js} +1 -1
  23. package/lib/{pool.js → dispatcher/pool.js} +4 -4
  24. package/lib/{proxy-agent.js → dispatcher/proxy-agent.js} +29 -35
  25. package/lib/dispatcher/retry-agent.js +35 -0
  26. package/lib/global.js +1 -1
  27. package/lib/handler/{RetryHandler.js → retry-handler.js} +2 -2
  28. package/lib/interceptor/{redirectInterceptor.js → redirect-interceptor.js} +1 -1
  29. package/lib/mock/mock-agent.js +2 -2
  30. package/lib/mock/mock-client.js +1 -1
  31. package/lib/mock/mock-interceptor.js +2 -2
  32. package/lib/mock/mock-pool.js +1 -1
  33. package/lib/mock/mock-utils.js +6 -4
  34. package/lib/{cache → web/cache}/cache.js +2 -4
  35. package/lib/{cache → web/cache}/cachestorage.js +1 -1
  36. package/lib/web/cache/symbols.js +5 -0
  37. package/lib/{cache → web/cache}/util.js +5 -9
  38. package/lib/{cookies → web/cookies}/parse.js +1 -1
  39. package/lib/{cookies → web/cookies}/util.js +76 -60
  40. package/lib/{eventsource → web/eventsource}/eventsource.js +2 -6
  41. package/lib/{fetch → web/fetch}/body.js +23 -52
  42. package/lib/{fetch/dataURL.js → web/fetch/data-url.js} +2 -0
  43. package/lib/{compat → web/fetch}/dispatcher-weakref.js +1 -1
  44. package/lib/{fetch → web/fetch}/file.js +2 -2
  45. package/lib/{fetch → web/fetch}/formdata.js +6 -67
  46. package/lib/{fetch → web/fetch}/headers.js +99 -71
  47. package/lib/{fetch → web/fetch}/index.js +40 -31
  48. package/lib/{fetch → web/fetch}/request.js +14 -6
  49. package/lib/{fetch → web/fetch}/response.js +3 -3
  50. package/lib/{fetch → web/fetch}/symbols.js +2 -1
  51. package/lib/{fetch → web/fetch}/util.js +142 -48
  52. package/lib/{fetch → web/fetch}/webidl.js +53 -19
  53. package/lib/{fileapi → web/fileapi}/filereader.js +1 -1
  54. package/lib/{fileapi → web/fileapi}/util.js +1 -1
  55. package/lib/{websocket → web/websocket}/connection.js +20 -10
  56. package/lib/{websocket → web/websocket}/constants.js +7 -0
  57. package/lib/{websocket → web/websocket}/events.js +1 -1
  58. package/lib/{websocket → web/websocket}/frame.js +1 -0
  59. package/lib/{websocket → web/websocket}/receiver.js +9 -16
  60. package/lib/{websocket → web/websocket}/util.js +37 -23
  61. package/lib/{websocket → web/websocket}/websocket.js +21 -9
  62. package/package.json +27 -52
  63. package/types/dispatcher.d.ts +1 -1
  64. package/types/fetch.d.ts +20 -21
  65. package/types/index.d.ts +2 -1
  66. package/types/retry-agent.d.ts +11 -0
  67. package/types/webidl.d.ts +6 -1
  68. package/docs/api/Fetch.md +0 -27
  69. package/docs/assets/lifecycle-diagram.png +0 -0
  70. package/lib/cache/symbols.js +0 -5
  71. package/lib/client.js +0 -2295
  72. package/lib/llhttp/llhttp-wasm.js +0 -3
  73. package/lib/llhttp/llhttp.wasm +0 -0
  74. package/lib/llhttp/llhttp_simd.wasm +0 -0
  75. /package/docs/{api → docs/api}/Agent.md +0 -0
  76. /package/docs/{api → docs/api}/BalancedPool.md +0 -0
  77. /package/docs/{api → docs/api}/CacheStorage.md +0 -0
  78. /package/docs/{api → docs/api}/Client.md +0 -0
  79. /package/docs/{api → docs/api}/Connector.md +0 -0
  80. /package/docs/{api → docs/api}/ContentType.md +0 -0
  81. /package/docs/{api → docs/api}/Cookies.md +0 -0
  82. /package/docs/{api → docs/api}/Debug.md +0 -0
  83. /package/docs/{api → docs/api}/DiagnosticsChannel.md +0 -0
  84. /package/docs/{api → docs/api}/DispatchInterceptor.md +0 -0
  85. /package/docs/{api → docs/api}/Errors.md +0 -0
  86. /package/docs/{api → docs/api}/EventSource.md +0 -0
  87. /package/docs/{api → docs/api}/MockAgent.md +0 -0
  88. /package/docs/{api → docs/api}/MockClient.md +0 -0
  89. /package/docs/{api → docs/api}/MockErrors.md +0 -0
  90. /package/docs/{api → docs/api}/MockPool.md +0 -0
  91. /package/docs/{api → docs/api}/Pool.md +0 -0
  92. /package/docs/{api → docs/api}/PoolStats.md +0 -0
  93. /package/docs/{api → docs/api}/RedirectHandler.md +0 -0
  94. /package/docs/{api → docs/api}/Util.md +0 -0
  95. /package/docs/{api → docs/api}/WebSocket.md +0 -0
  96. /package/docs/{best-practices → docs/best-practices}/client-certificate.md +0 -0
  97. /package/docs/{best-practices → docs/best-practices}/mocking-request.md +0 -0
  98. /package/docs/{best-practices → docs/best-practices}/writing-tests.md +0 -0
  99. /package/lib/{dispatcher.js → dispatcher/dispatcher.js} +0 -0
  100. /package/lib/{node → dispatcher}/fixed-queue.js +0 -0
  101. /package/lib/handler/{DecoratorHandler.js → decorator-handler.js} +0 -0
  102. /package/lib/handler/{RedirectHandler.js → redirect-handler.js} +0 -0
  103. /package/lib/{timers.js → util/timers.js} +0 -0
  104. /package/lib/{cookies → web/cookies}/constants.js +0 -0
  105. /package/lib/{cookies → web/cookies}/index.js +0 -0
  106. /package/lib/{eventsource → web/eventsource}/eventsource-stream.js +0 -0
  107. /package/lib/{eventsource → web/eventsource}/util.js +0 -0
  108. /package/lib/{fetch → web/fetch}/LICENSE +0 -0
  109. /package/lib/{fetch → web/fetch}/constants.js +0 -0
  110. /package/lib/{fetch → web/fetch}/global.js +0 -0
  111. /package/lib/{fileapi → web/fileapi}/encoding.js +0 -0
  112. /package/lib/{fileapi → web/fileapi}/progressevent.js +0 -0
  113. /package/lib/{fileapi → web/fileapi}/symbols.js +0 -0
  114. /package/lib/{websocket → web/websocket}/symbols.js +0 -0
@@ -0,0 +1,1339 @@
1
+ 'use strict'
2
+
3
+ /* global WebAssembly */
4
+
5
+ const assert = require('node:assert')
6
+ const util = require('../core/util.js')
7
+ const { channels } = require('../core/diagnostics.js')
8
+ const timers = require('../util/timers.js')
9
+ const {
10
+ RequestContentLengthMismatchError,
11
+ ResponseContentLengthMismatchError,
12
+ RequestAbortedError,
13
+ HeadersTimeoutError,
14
+ HeadersOverflowError,
15
+ SocketError,
16
+ InformationalError,
17
+ BodyTimeoutError,
18
+ HTTPParserError,
19
+ ResponseExceededMaxSizeError
20
+ } = require('../core/errors.js')
21
+ const {
22
+ kUrl,
23
+ kReset,
24
+ kClient,
25
+ kParser,
26
+ kBlocking,
27
+ kRunning,
28
+ kPending,
29
+ kSize,
30
+ kWriting,
31
+ kQueue,
32
+ kNoRef,
33
+ kKeepAliveDefaultTimeout,
34
+ kHostHeader,
35
+ kPendingIdx,
36
+ kRunningIdx,
37
+ kError,
38
+ kPipelining,
39
+ kSocket,
40
+ kKeepAliveTimeoutValue,
41
+ kMaxHeadersSize,
42
+ kKeepAliveMaxTimeout,
43
+ kKeepAliveTimeoutThreshold,
44
+ kHeadersTimeout,
45
+ kBodyTimeout,
46
+ kStrictContentLength,
47
+ kMaxRequests,
48
+ kCounter,
49
+ kMaxResponseSize,
50
+ kListeners,
51
+ kOnError,
52
+ kResume,
53
+ kHTTPContext
54
+ } = require('../core/symbols.js')
55
+
56
+ const constants = require('../llhttp/constants.js')
57
+ const EMPTY_BUF = Buffer.alloc(0)
58
+ const FastBuffer = Buffer[Symbol.species]
59
+
60
+ let extractBody
61
+
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
+ async function lazyllhttp () {
77
+ const mod = await WebAssembly.compile(require('../llhttp/llhttp_simd-wasm.js'))
78
+
79
+ return await WebAssembly.instantiate(mod, {
80
+ env: {
81
+ /* eslint-disable camelcase */
82
+
83
+ wasm_on_url: (p, at, len) => {
84
+ /* istanbul ignore next */
85
+ return 0
86
+ },
87
+ wasm_on_status: (p, at, len) => {
88
+ assert.strictEqual(currentParser.ptr, p)
89
+ const start = at - currentBufferPtr + currentBufferRef.byteOffset
90
+ return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start, len)) || 0
91
+ },
92
+ wasm_on_message_begin: (p) => {
93
+ assert.strictEqual(currentParser.ptr, p)
94
+ return currentParser.onMessageBegin() || 0
95
+ },
96
+ wasm_on_header_field: (p, at, len) => {
97
+ assert.strictEqual(currentParser.ptr, p)
98
+ const start = at - currentBufferPtr + currentBufferRef.byteOffset
99
+ return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start, len)) || 0
100
+ },
101
+ wasm_on_header_value: (p, at, len) => {
102
+ assert.strictEqual(currentParser.ptr, p)
103
+ const start = at - currentBufferPtr + currentBufferRef.byteOffset
104
+ return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start, len)) || 0
105
+ },
106
+ wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => {
107
+ assert.strictEqual(currentParser.ptr, p)
108
+ return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0
109
+ },
110
+ wasm_on_body: (p, at, len) => {
111
+ assert.strictEqual(currentParser.ptr, p)
112
+ const start = at - currentBufferPtr + currentBufferRef.byteOffset
113
+ return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start, len)) || 0
114
+ },
115
+ wasm_on_message_complete: (p) => {
116
+ assert.strictEqual(currentParser.ptr, p)
117
+ return currentParser.onMessageComplete() || 0
118
+ }
119
+
120
+ /* eslint-enable camelcase */
121
+ }
122
+ })
123
+ }
124
+
125
+ let llhttpInstance = null
126
+ let llhttpPromise = lazyllhttp()
127
+ llhttpPromise.catch()
128
+
129
+ let currentParser = null
130
+ let currentBufferRef = null
131
+ let currentBufferSize = 0
132
+ let currentBufferPtr = null
133
+
134
+ const TIMEOUT_HEADERS = 1
135
+ const TIMEOUT_BODY = 2
136
+ const TIMEOUT_IDLE = 3
137
+
138
+ class Parser {
139
+ constructor (client, socket, { exports }) {
140
+ assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0)
141
+
142
+ this.llhttp = exports
143
+ this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE)
144
+ this.client = client
145
+ this.socket = socket
146
+ this.timeout = null
147
+ this.timeoutValue = null
148
+ this.timeoutType = null
149
+ this.statusCode = null
150
+ this.statusText = ''
151
+ this.upgrade = false
152
+ this.headers = []
153
+ this.headersSize = 0
154
+ this.headersMaxSize = client[kMaxHeadersSize]
155
+ this.shouldKeepAlive = false
156
+ this.paused = false
157
+ this.resume = this.resume.bind(this)
158
+
159
+ this.bytesRead = 0
160
+
161
+ this.keepAlive = ''
162
+ this.contentLength = ''
163
+ this.connection = ''
164
+ this.maxResponseSize = client[kMaxResponseSize]
165
+ }
166
+
167
+ setTimeout (value, type) {
168
+ this.timeoutType = type
169
+ if (value !== this.timeoutValue) {
170
+ timers.clearTimeout(this.timeout)
171
+ if (value) {
172
+ this.timeout = timers.setTimeout(onParserTimeout, value, this)
173
+ // istanbul ignore else: only for jest
174
+ if (this.timeout.unref) {
175
+ this.timeout.unref()
176
+ }
177
+ } else {
178
+ this.timeout = null
179
+ }
180
+ this.timeoutValue = value
181
+ } else if (this.timeout) {
182
+ // istanbul ignore else: only for jest
183
+ if (this.timeout.refresh) {
184
+ this.timeout.refresh()
185
+ }
186
+ }
187
+ }
188
+
189
+ resume () {
190
+ if (this.socket.destroyed || !this.paused) {
191
+ return
192
+ }
193
+
194
+ assert(this.ptr != null)
195
+ assert(currentParser == null)
196
+
197
+ this.llhttp.llhttp_resume(this.ptr)
198
+
199
+ assert(this.timeoutType === TIMEOUT_BODY)
200
+ if (this.timeout) {
201
+ // istanbul ignore else: only for jest
202
+ if (this.timeout.refresh) {
203
+ this.timeout.refresh()
204
+ }
205
+ }
206
+
207
+ this.paused = false
208
+ this.execute(this.socket.read() || EMPTY_BUF) // Flush parser.
209
+ this.readMore()
210
+ }
211
+
212
+ readMore () {
213
+ while (!this.paused && this.ptr) {
214
+ const chunk = this.socket.read()
215
+ if (chunk === null) {
216
+ break
217
+ }
218
+ this.execute(chunk)
219
+ }
220
+ }
221
+
222
+ execute (data) {
223
+ assert(this.ptr != null)
224
+ assert(currentParser == null)
225
+ assert(!this.paused)
226
+
227
+ const { socket, llhttp } = this
228
+
229
+ if (data.length > currentBufferSize) {
230
+ if (currentBufferPtr) {
231
+ llhttp.free(currentBufferPtr)
232
+ }
233
+ currentBufferSize = Math.ceil(data.length / 4096) * 4096
234
+ currentBufferPtr = llhttp.malloc(currentBufferSize)
235
+ }
236
+
237
+ new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(data)
238
+
239
+ // Call `execute` on the wasm parser.
240
+ // We pass the `llhttp_parser` pointer address, the pointer address of buffer view data,
241
+ // and finally the length of bytes to parse.
242
+ // The return value is an error code or `constants.ERROR.OK`.
243
+ try {
244
+ let ret
245
+
246
+ try {
247
+ currentBufferRef = data
248
+ currentParser = this
249
+ ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, data.length)
250
+ /* eslint-disable-next-line no-useless-catch */
251
+ } catch (err) {
252
+ /* istanbul ignore next: difficult to make a test case for */
253
+ throw err
254
+ } finally {
255
+ currentParser = null
256
+ currentBufferRef = null
257
+ }
258
+
259
+ const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
260
+
261
+ if (ret === constants.ERROR.PAUSED_UPGRADE) {
262
+ this.onUpgrade(data.slice(offset))
263
+ } else if (ret === constants.ERROR.PAUSED) {
264
+ this.paused = true
265
+ socket.unshift(data.slice(offset))
266
+ } else if (ret !== constants.ERROR.OK) {
267
+ const ptr = llhttp.llhttp_get_error_reason(this.ptr)
268
+ let message = ''
269
+ /* istanbul ignore else: difficult to make a test case for */
270
+ if (ptr) {
271
+ const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
272
+ message =
273
+ 'Response does not match the HTTP/1.1 protocol (' +
274
+ Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
275
+ ')'
276
+ }
277
+ throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
278
+ }
279
+ } catch (err) {
280
+ util.destroy(socket, err)
281
+ }
282
+ }
283
+
284
+ destroy () {
285
+ assert(this.ptr != null)
286
+ assert(currentParser == null)
287
+
288
+ this.llhttp.llhttp_free(this.ptr)
289
+ this.ptr = null
290
+
291
+ timers.clearTimeout(this.timeout)
292
+ this.timeout = null
293
+ this.timeoutValue = null
294
+ this.timeoutType = null
295
+
296
+ this.paused = false
297
+ }
298
+
299
+ onStatus (buf) {
300
+ this.statusText = buf.toString()
301
+ }
302
+
303
+ onMessageBegin () {
304
+ const { socket, client } = this
305
+
306
+ /* istanbul ignore next: difficult to make a test case for */
307
+ if (socket.destroyed) {
308
+ return -1
309
+ }
310
+
311
+ const request = client[kQueue][client[kRunningIdx]]
312
+ if (!request) {
313
+ return -1
314
+ }
315
+ request.onResponseStarted()
316
+ }
317
+
318
+ onHeaderField (buf) {
319
+ const len = this.headers.length
320
+
321
+ if ((len & 1) === 0) {
322
+ this.headers.push(buf)
323
+ } else {
324
+ this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf])
325
+ }
326
+
327
+ this.trackHeader(buf.length)
328
+ }
329
+
330
+ onHeaderValue (buf) {
331
+ let len = this.headers.length
332
+
333
+ if ((len & 1) === 1) {
334
+ this.headers.push(buf)
335
+ len += 1
336
+ } else {
337
+ this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf])
338
+ }
339
+
340
+ const key = this.headers[len - 2]
341
+ if (key.length === 10) {
342
+ const headerName = util.bufferToLowerCasedHeaderName(key)
343
+ if (headerName === 'keep-alive') {
344
+ this.keepAlive += buf.toString()
345
+ } else if (headerName === 'connection') {
346
+ this.connection += buf.toString()
347
+ }
348
+ } else if (key.length === 14 && util.bufferToLowerCasedHeaderName(key) === 'content-length') {
349
+ this.contentLength += buf.toString()
350
+ }
351
+
352
+ this.trackHeader(buf.length)
353
+ }
354
+
355
+ trackHeader (len) {
356
+ this.headersSize += len
357
+ if (this.headersSize >= this.headersMaxSize) {
358
+ util.destroy(this.socket, new HeadersOverflowError())
359
+ }
360
+ }
361
+
362
+ onUpgrade (head) {
363
+ const { upgrade, client, socket, headers, statusCode } = this
364
+
365
+ assert(upgrade)
366
+
367
+ const request = client[kQueue][client[kRunningIdx]]
368
+ assert(request)
369
+
370
+ assert(!socket.destroyed)
371
+ assert(socket === client[kSocket])
372
+ assert(!this.paused)
373
+ assert(request.upgrade || request.method === 'CONNECT')
374
+
375
+ this.statusCode = null
376
+ this.statusText = ''
377
+ this.shouldKeepAlive = null
378
+
379
+ assert(this.headers.length % 2 === 0)
380
+ this.headers = []
381
+ this.headersSize = 0
382
+
383
+ socket.unshift(head)
384
+
385
+ socket[kParser].destroy()
386
+ socket[kParser] = null
387
+
388
+ socket[kClient] = null
389
+ socket[kError] = null
390
+
391
+ removeAllListeners(socket)
392
+
393
+ client[kSocket] = null
394
+ client[kHTTPContext] = null // TODO (fix): This is hacky...
395
+ client[kQueue][client[kRunningIdx]++] = null
396
+ client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
397
+
398
+ try {
399
+ request.onUpgrade(statusCode, headers, socket)
400
+ } catch (err) {
401
+ util.destroy(socket, err)
402
+ }
403
+
404
+ client[kResume]()
405
+ }
406
+
407
+ onHeadersComplete (statusCode, upgrade, shouldKeepAlive) {
408
+ const { client, socket, headers, statusText } = this
409
+
410
+ /* istanbul ignore next: difficult to make a test case for */
411
+ if (socket.destroyed) {
412
+ return -1
413
+ }
414
+
415
+ const request = client[kQueue][client[kRunningIdx]]
416
+
417
+ /* istanbul ignore next: difficult to make a test case for */
418
+ if (!request) {
419
+ return -1
420
+ }
421
+
422
+ assert(!this.upgrade)
423
+ assert(this.statusCode < 200)
424
+
425
+ if (statusCode === 100) {
426
+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
427
+ return -1
428
+ }
429
+
430
+ /* this can only happen if server is misbehaving */
431
+ if (upgrade && !request.upgrade) {
432
+ util.destroy(socket, new SocketError('bad upgrade', util.getSocketInfo(socket)))
433
+ return -1
434
+ }
435
+
436
+ assert.strictEqual(this.timeoutType, TIMEOUT_HEADERS)
437
+
438
+ this.statusCode = statusCode
439
+ this.shouldKeepAlive = (
440
+ shouldKeepAlive ||
441
+ // Override llhttp value which does not allow keepAlive for HEAD.
442
+ (request.method === 'HEAD' && !socket[kReset] && this.connection.toLowerCase() === 'keep-alive')
443
+ )
444
+
445
+ if (this.statusCode >= 200) {
446
+ const bodyTimeout = request.bodyTimeout != null
447
+ ? request.bodyTimeout
448
+ : client[kBodyTimeout]
449
+ this.setTimeout(bodyTimeout, TIMEOUT_BODY)
450
+ } else if (this.timeout) {
451
+ // istanbul ignore else: only for jest
452
+ if (this.timeout.refresh) {
453
+ this.timeout.refresh()
454
+ }
455
+ }
456
+
457
+ if (request.method === 'CONNECT') {
458
+ assert(client[kRunning] === 1)
459
+ this.upgrade = true
460
+ return 2
461
+ }
462
+
463
+ if (upgrade) {
464
+ assert(client[kRunning] === 1)
465
+ this.upgrade = true
466
+ return 2
467
+ }
468
+
469
+ assert(this.headers.length % 2 === 0)
470
+ this.headers = []
471
+ this.headersSize = 0
472
+
473
+ if (this.shouldKeepAlive && client[kPipelining]) {
474
+ const keepAliveTimeout = this.keepAlive ? util.parseKeepAliveTimeout(this.keepAlive) : null
475
+
476
+ if (keepAliveTimeout != null) {
477
+ const timeout = Math.min(
478
+ keepAliveTimeout - client[kKeepAliveTimeoutThreshold],
479
+ client[kKeepAliveMaxTimeout]
480
+ )
481
+ if (timeout <= 0) {
482
+ socket[kReset] = true
483
+ } else {
484
+ client[kKeepAliveTimeoutValue] = timeout
485
+ }
486
+ } else {
487
+ client[kKeepAliveTimeoutValue] = client[kKeepAliveDefaultTimeout]
488
+ }
489
+ } else {
490
+ // Stop more requests from being dispatched.
491
+ socket[kReset] = true
492
+ }
493
+
494
+ const pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false
495
+
496
+ if (request.aborted) {
497
+ return -1
498
+ }
499
+
500
+ if (request.method === 'HEAD') {
501
+ return 1
502
+ }
503
+
504
+ if (statusCode < 200) {
505
+ return 1
506
+ }
507
+
508
+ if (socket[kBlocking]) {
509
+ socket[kBlocking] = false
510
+ client[kResume]()
511
+ }
512
+
513
+ return pause ? constants.ERROR.PAUSED : 0
514
+ }
515
+
516
+ onBody (buf) {
517
+ const { client, socket, statusCode, maxResponseSize } = this
518
+
519
+ if (socket.destroyed) {
520
+ return -1
521
+ }
522
+
523
+ const request = client[kQueue][client[kRunningIdx]]
524
+ assert(request)
525
+
526
+ assert.strictEqual(this.timeoutType, TIMEOUT_BODY)
527
+ if (this.timeout) {
528
+ // istanbul ignore else: only for jest
529
+ if (this.timeout.refresh) {
530
+ this.timeout.refresh()
531
+ }
532
+ }
533
+
534
+ assert(statusCode >= 200)
535
+
536
+ if (maxResponseSize > -1 && this.bytesRead + buf.length > maxResponseSize) {
537
+ util.destroy(socket, new ResponseExceededMaxSizeError())
538
+ return -1
539
+ }
540
+
541
+ this.bytesRead += buf.length
542
+
543
+ if (request.onData(buf) === false) {
544
+ return constants.ERROR.PAUSED
545
+ }
546
+ }
547
+
548
+ onMessageComplete () {
549
+ const { client, socket, statusCode, upgrade, headers, contentLength, bytesRead, shouldKeepAlive } = this
550
+
551
+ if (socket.destroyed && (!statusCode || shouldKeepAlive)) {
552
+ return -1
553
+ }
554
+
555
+ if (upgrade) {
556
+ return
557
+ }
558
+
559
+ const request = client[kQueue][client[kRunningIdx]]
560
+ assert(request)
561
+
562
+ assert(statusCode >= 100)
563
+
564
+ this.statusCode = null
565
+ this.statusText = ''
566
+ this.bytesRead = 0
567
+ this.contentLength = ''
568
+ this.keepAlive = ''
569
+ this.connection = ''
570
+
571
+ assert(this.headers.length % 2 === 0)
572
+ this.headers = []
573
+ this.headersSize = 0
574
+
575
+ if (statusCode < 200) {
576
+ return
577
+ }
578
+
579
+ /* istanbul ignore next: should be handled by llhttp? */
580
+ if (request.method !== 'HEAD' && contentLength && bytesRead !== parseInt(contentLength, 10)) {
581
+ util.destroy(socket, new ResponseContentLengthMismatchError())
582
+ return -1
583
+ }
584
+
585
+ request.onComplete(headers)
586
+
587
+ client[kQueue][client[kRunningIdx]++] = null
588
+
589
+ if (socket[kWriting]) {
590
+ assert.strictEqual(client[kRunning], 0)
591
+ // Response completed before request.
592
+ util.destroy(socket, new InformationalError('reset'))
593
+ return constants.ERROR.PAUSED
594
+ } else if (!shouldKeepAlive) {
595
+ util.destroy(socket, new InformationalError('reset'))
596
+ return constants.ERROR.PAUSED
597
+ } else if (socket[kReset] && client[kRunning] === 0) {
598
+ // Destroy socket once all requests have completed.
599
+ // The request at the tail of the pipeline is the one
600
+ // that requested reset and no further requests should
601
+ // have been queued since then.
602
+ util.destroy(socket, new InformationalError('reset'))
603
+ return constants.ERROR.PAUSED
604
+ } else if (client[kPipelining] == null || client[kPipelining] === 1) {
605
+ // We must wait a full event loop cycle to reuse this socket to make sure
606
+ // that non-spec compliant servers are not closing the connection even if they
607
+ // said they won't.
608
+ setImmediate(() => client[kResume]())
609
+ } else {
610
+ client[kResume]()
611
+ }
612
+ }
613
+ }
614
+
615
+ function onParserTimeout (parser) {
616
+ const { socket, timeoutType, client } = parser
617
+
618
+ /* istanbul ignore else */
619
+ if (timeoutType === TIMEOUT_HEADERS) {
620
+ if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) {
621
+ assert(!parser.paused, 'cannot be paused while waiting for headers')
622
+ util.destroy(socket, new HeadersTimeoutError())
623
+ }
624
+ } else if (timeoutType === TIMEOUT_BODY) {
625
+ if (!parser.paused) {
626
+ util.destroy(socket, new BodyTimeoutError())
627
+ }
628
+ } else if (timeoutType === TIMEOUT_IDLE) {
629
+ assert(client[kRunning] === 0 && client[kKeepAliveTimeoutValue])
630
+ util.destroy(socket, new InformationalError('socket idle timeout'))
631
+ }
632
+ }
633
+
634
+ async function connectH1 (client, socket) {
635
+ client[kSocket] = socket
636
+
637
+ if (!llhttpInstance) {
638
+ llhttpInstance = await llhttpPromise
639
+ llhttpPromise = null
640
+ }
641
+
642
+ socket[kNoRef] = false
643
+ socket[kWriting] = false
644
+ socket[kReset] = false
645
+ socket[kBlocking] = false
646
+ socket[kParser] = new Parser(client, socket, llhttpInstance)
647
+
648
+ addListener(socket, 'error', function (err) {
649
+ const parser = this[kParser]
650
+
651
+ assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
652
+
653
+ // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
654
+ // to the user.
655
+ if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
656
+ // We treat all incoming data so for as a valid response.
657
+ parser.onMessageComplete()
658
+ return
659
+ }
660
+
661
+ this[kError] = err
662
+
663
+ this[kClient][kOnError](err)
664
+ })
665
+ addListener(socket, 'readable', function () {
666
+ const parser = this[kParser]
667
+
668
+ if (parser) {
669
+ parser.readMore()
670
+ }
671
+ })
672
+ addListener(socket, 'end', function () {
673
+ const parser = this[kParser]
674
+
675
+ if (parser.statusCode && !parser.shouldKeepAlive) {
676
+ // We treat all incoming data so far as a valid response.
677
+ parser.onMessageComplete()
678
+ return
679
+ }
680
+
681
+ util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
682
+ })
683
+ addListener(socket, 'close', function () {
684
+ const client = this[kClient]
685
+ const parser = this[kParser]
686
+
687
+ if (parser) {
688
+ if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
689
+ // We treat all incoming data so far as a valid response.
690
+ parser.onMessageComplete()
691
+ }
692
+
693
+ this[kParser].destroy()
694
+ this[kParser] = null
695
+ }
696
+
697
+ const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
698
+
699
+ client[kSocket] = null
700
+ client[kHTTPContext] = null // TODO (fix): This is hacky...
701
+
702
+ if (client.destroyed) {
703
+ assert(client[kPending] === 0)
704
+
705
+ // Fail entire queue.
706
+ const requests = client[kQueue].splice(client[kRunningIdx])
707
+ for (let i = 0; i < requests.length; i++) {
708
+ const request = requests[i]
709
+ errorRequest(client, request, err)
710
+ }
711
+ } else if (client[kRunning] > 0 && err.code !== 'UND_ERR_INFO') {
712
+ // Fail head of pipeline.
713
+ const request = client[kQueue][client[kRunningIdx]]
714
+ client[kQueue][client[kRunningIdx]++] = null
715
+
716
+ errorRequest(client, request, err)
717
+ }
718
+
719
+ client[kPendingIdx] = client[kRunningIdx]
720
+
721
+ assert(client[kRunning] === 0)
722
+
723
+ client.emit('disconnect', client[kUrl], [client], err)
724
+
725
+ client[kResume]()
726
+ })
727
+
728
+ let closed = false
729
+ socket.on('close', () => {
730
+ closed = true
731
+ })
732
+
733
+ return {
734
+ version: 'h1',
735
+ defaultPipelining: 1,
736
+ write (...args) {
737
+ return writeH1(client, ...args)
738
+ },
739
+ resume () {
740
+ resumeH1(client)
741
+ },
742
+ destroy (err, callback) {
743
+ if (closed) {
744
+ queueMicrotask(callback)
745
+ } else {
746
+ socket.destroy(err).on('close', callback)
747
+ }
748
+ },
749
+ get destroyed () {
750
+ return socket.destroyed
751
+ },
752
+ busy (request) {
753
+ if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
754
+ return true
755
+ }
756
+
757
+ if (request) {
758
+ if (client[kRunning] > 0 && !request.idempotent) {
759
+ // Non-idempotent request cannot be retried.
760
+ // Ensure that no other requests are inflight and
761
+ // could cause failure.
762
+ return true
763
+ }
764
+
765
+ if (client[kRunning] > 0 && (request.upgrade || request.method === 'CONNECT')) {
766
+ // Don't dispatch an upgrade until all preceding requests have completed.
767
+ // A misbehaving server might upgrade the connection before all pipelined
768
+ // request has completed.
769
+ return true
770
+ }
771
+
772
+ if (client[kRunning] > 0 && util.bodyLength(request.body) !== 0 &&
773
+ (util.isStream(request.body) || util.isAsyncIterable(request.body) || util.isFormDataLike(request.body))) {
774
+ // Request with stream or iterator body can error while other requests
775
+ // are inflight and indirectly error those as well.
776
+ // Ensure this doesn't happen by waiting for inflight
777
+ // to complete before dispatching.
778
+
779
+ // Request with stream or iterator body cannot be retried.
780
+ // Ensure that no other requests are inflight and
781
+ // could cause failure.
782
+ return true
783
+ }
784
+ }
785
+
786
+ return false
787
+ }
788
+ }
789
+ }
790
+
791
+ function resumeH1 (client) {
792
+ const socket = client[kSocket]
793
+
794
+ if (socket && !socket.destroyed) {
795
+ if (client[kSize] === 0) {
796
+ if (!socket[kNoRef] && socket.unref) {
797
+ socket.unref()
798
+ socket[kNoRef] = true
799
+ }
800
+ } else if (socket[kNoRef] && socket.ref) {
801
+ socket.ref()
802
+ socket[kNoRef] = false
803
+ }
804
+
805
+ if (client[kSize] === 0) {
806
+ if (socket[kParser].timeoutType !== TIMEOUT_IDLE) {
807
+ socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_IDLE)
808
+ }
809
+ } else if (client[kRunning] > 0 && socket[kParser].statusCode < 200) {
810
+ if (socket[kParser].timeoutType !== TIMEOUT_HEADERS) {
811
+ const request = client[kQueue][client[kRunningIdx]]
812
+ const headersTimeout = request.headersTimeout != null
813
+ ? request.headersTimeout
814
+ : client[kHeadersTimeout]
815
+ socket[kParser].setTimeout(headersTimeout, TIMEOUT_HEADERS)
816
+ }
817
+ }
818
+ }
819
+ }
820
+
821
+ function errorRequest (client, request, err) {
822
+ try {
823
+ request.onError(err)
824
+ assert(request.aborted)
825
+ } catch (err) {
826
+ client.emit('error', err)
827
+ }
828
+ }
829
+
830
+ // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
831
+ function shouldSendContentLength (method) {
832
+ return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT'
833
+ }
834
+
835
+ function writeH1 (client, request) {
836
+ const { method, path, host, upgrade, blocking, reset } = request
837
+
838
+ let { body, headers, contentLength } = request
839
+
840
+ // https://tools.ietf.org/html/rfc7231#section-4.3.1
841
+ // https://tools.ietf.org/html/rfc7231#section-4.3.2
842
+ // https://tools.ietf.org/html/rfc7231#section-4.3.5
843
+
844
+ // Sending a payload body on a request that does not
845
+ // expect it can cause undefined behavior on some
846
+ // servers and corrupt connection state. Do not
847
+ // re-use the connection for further requests.
848
+
849
+ const expectsPayload = (
850
+ method === 'PUT' ||
851
+ method === 'POST' ||
852
+ method === 'PATCH'
853
+ )
854
+
855
+ if (util.isFormDataLike(body)) {
856
+ if (!extractBody) {
857
+ extractBody = require('../web/fetch/body.js').extractBody
858
+ }
859
+
860
+ const [bodyStream, contentType] = extractBody(body)
861
+ if (request.contentType == null) {
862
+ headers.push('content-type', contentType)
863
+ }
864
+ body = bodyStream.stream
865
+ contentLength = bodyStream.length
866
+ } else if (util.isBlobLike(body) && request.contentType == null && body.type) {
867
+ headers.push('content-type', body.type)
868
+ }
869
+
870
+ if (body && typeof body.read === 'function') {
871
+ // Try to read EOF in order to get length.
872
+ body.read(0)
873
+ }
874
+
875
+ const bodyLength = util.bodyLength(body)
876
+
877
+ contentLength = bodyLength ?? contentLength
878
+
879
+ if (contentLength === null) {
880
+ contentLength = request.contentLength
881
+ }
882
+
883
+ if (contentLength === 0 && !expectsPayload) {
884
+ // https://tools.ietf.org/html/rfc7230#section-3.3.2
885
+ // A user agent SHOULD NOT send a Content-Length header field when
886
+ // the request message does not contain a payload body and the method
887
+ // semantics do not anticipate such a body.
888
+
889
+ contentLength = null
890
+ }
891
+
892
+ // https://github.com/nodejs/undici/issues/2046
893
+ // A user agent may send a Content-Length header with 0 value, this should be allowed.
894
+ if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength !== null && request.contentLength !== contentLength) {
895
+ if (client[kStrictContentLength]) {
896
+ errorRequest(client, request, new RequestContentLengthMismatchError())
897
+ return false
898
+ }
899
+
900
+ process.emitWarning(new RequestContentLengthMismatchError())
901
+ }
902
+
903
+ const socket = client[kSocket]
904
+
905
+ try {
906
+ request.onConnect((err) => {
907
+ if (request.aborted || request.completed) {
908
+ return
909
+ }
910
+
911
+ errorRequest(client, request, err || new RequestAbortedError())
912
+
913
+ util.destroy(socket, new InformationalError('aborted'))
914
+ })
915
+ } catch (err) {
916
+ errorRequest(client, request, err)
917
+ }
918
+
919
+ if (request.aborted) {
920
+ util.destroy(body)
921
+ return false
922
+ }
923
+
924
+ if (method === 'HEAD') {
925
+ // https://github.com/mcollina/undici/issues/258
926
+ // Close after a HEAD request to interop with misbehaving servers
927
+ // that may send a body in the response.
928
+
929
+ socket[kReset] = true
930
+ }
931
+
932
+ if (upgrade || method === 'CONNECT') {
933
+ // On CONNECT or upgrade, block pipeline from dispatching further
934
+ // requests on this connection.
935
+
936
+ socket[kReset] = true
937
+ }
938
+
939
+ if (reset != null) {
940
+ socket[kReset] = reset
941
+ }
942
+
943
+ if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) {
944
+ socket[kReset] = true
945
+ }
946
+
947
+ if (blocking) {
948
+ socket[kBlocking] = true
949
+ }
950
+
951
+ let header = `${method} ${path} HTTP/1.1\r\n`
952
+
953
+ if (typeof host === 'string') {
954
+ header += `host: ${host}\r\n`
955
+ } else {
956
+ header += client[kHostHeader]
957
+ }
958
+
959
+ if (upgrade) {
960
+ header += `connection: upgrade\r\nupgrade: ${upgrade}\r\n`
961
+ } else if (client[kPipelining] && !socket[kReset]) {
962
+ header += 'connection: keep-alive\r\n'
963
+ } else {
964
+ header += 'connection: close\r\n'
965
+ }
966
+
967
+ if (Array.isArray(headers)) {
968
+ for (let n = 0; n < headers.length; n += 2) {
969
+ const key = headers[n + 0]
970
+ const val = headers[n + 1]
971
+
972
+ if (Array.isArray(val)) {
973
+ for (let i = 0; i < val.length; i++) {
974
+ header += `${key}: ${val[i]}\r\n`
975
+ }
976
+ } else {
977
+ header += `${key}: ${val}\r\n`
978
+ }
979
+ }
980
+ }
981
+
982
+ if (channels.sendHeaders.hasSubscribers) {
983
+ channels.sendHeaders.publish({ request, headers: header, socket })
984
+ }
985
+
986
+ /* istanbul ignore else: assertion */
987
+ if (!body || bodyLength === 0) {
988
+ if (contentLength === 0) {
989
+ socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1')
990
+ } else {
991
+ assert(contentLength === null, 'no body must not have content length')
992
+ socket.write(`${header}\r\n`, 'latin1')
993
+ }
994
+ request.onRequestSent()
995
+ } else if (util.isBuffer(body)) {
996
+ assert(contentLength === body.byteLength, 'buffer body must have content length')
997
+
998
+ socket.cork()
999
+ socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1')
1000
+ socket.write(body)
1001
+ socket.uncork()
1002
+ request.onBodySent(body)
1003
+ request.onRequestSent()
1004
+ if (!expectsPayload) {
1005
+ socket[kReset] = true
1006
+ }
1007
+ } else if (util.isBlobLike(body)) {
1008
+ if (typeof body.stream === 'function') {
1009
+ writeIterable({ body: body.stream(), client, request, socket, contentLength, header, expectsPayload })
1010
+ } else {
1011
+ writeBlob({ body, client, request, socket, contentLength, header, expectsPayload })
1012
+ }
1013
+ } else if (util.isStream(body)) {
1014
+ writeStream({ body, client, request, socket, contentLength, header, expectsPayload })
1015
+ } else if (util.isIterable(body)) {
1016
+ writeIterable({ body, client, request, socket, contentLength, header, expectsPayload })
1017
+ } else {
1018
+ assert(false)
1019
+ }
1020
+
1021
+ return true
1022
+ }
1023
+
1024
+ function writeStream ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
1025
+ assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
1026
+
1027
+ let finished = false
1028
+
1029
+ const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header })
1030
+
1031
+ const onData = function (chunk) {
1032
+ if (finished) {
1033
+ return
1034
+ }
1035
+
1036
+ try {
1037
+ if (!writer.write(chunk) && this.pause) {
1038
+ this.pause()
1039
+ }
1040
+ } catch (err) {
1041
+ util.destroy(this, err)
1042
+ }
1043
+ }
1044
+ const onDrain = function () {
1045
+ if (finished) {
1046
+ return
1047
+ }
1048
+
1049
+ if (body.resume) {
1050
+ body.resume()
1051
+ }
1052
+ }
1053
+ const onClose = function () {
1054
+ // 'close' might be emitted *before* 'error' for
1055
+ // broken streams. Wait a tick to avoid this case.
1056
+ queueMicrotask(() => {
1057
+ // It's only safe to remove 'error' listener after
1058
+ // 'close'.
1059
+ body.removeListener('error', onFinished)
1060
+ })
1061
+
1062
+ if (!finished) {
1063
+ const err = new RequestAbortedError()
1064
+ queueMicrotask(() => onFinished(err))
1065
+ }
1066
+ }
1067
+ const onFinished = function (err) {
1068
+ if (finished) {
1069
+ return
1070
+ }
1071
+
1072
+ finished = true
1073
+
1074
+ assert(socket.destroyed || (socket[kWriting] && client[kRunning] <= 1))
1075
+
1076
+ socket
1077
+ .off('drain', onDrain)
1078
+ .off('error', onFinished)
1079
+
1080
+ body
1081
+ .removeListener('data', onData)
1082
+ .removeListener('end', onFinished)
1083
+ .removeListener('close', onClose)
1084
+
1085
+ if (!err) {
1086
+ try {
1087
+ writer.end()
1088
+ } catch (er) {
1089
+ err = er
1090
+ }
1091
+ }
1092
+
1093
+ writer.destroy(err)
1094
+
1095
+ if (err && (err.code !== 'UND_ERR_INFO' || err.message !== 'reset')) {
1096
+ util.destroy(body, err)
1097
+ } else {
1098
+ util.destroy(body)
1099
+ }
1100
+ }
1101
+
1102
+ body
1103
+ .on('data', onData)
1104
+ .on('end', onFinished)
1105
+ .on('error', onFinished)
1106
+ .on('close', onClose)
1107
+
1108
+ if (body.resume) {
1109
+ body.resume()
1110
+ }
1111
+
1112
+ socket
1113
+ .on('drain', onDrain)
1114
+ .on('error', onFinished)
1115
+
1116
+ if (body.errorEmitted ?? body.errored) {
1117
+ setImmediate(() => onFinished(body.errored))
1118
+ } else if (body.endEmitted ?? body.readableEnded) {
1119
+ setImmediate(() => onFinished(null))
1120
+ }
1121
+
1122
+ if (body.closeEmitted ?? body.closed) {
1123
+ setImmediate(onClose)
1124
+ }
1125
+ }
1126
+
1127
+ async function writeBlob ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
1128
+ assert(contentLength === body.size, 'blob body must have content length')
1129
+
1130
+ try {
1131
+ if (contentLength != null && contentLength !== body.size) {
1132
+ throw new RequestContentLengthMismatchError()
1133
+ }
1134
+
1135
+ const buffer = Buffer.from(await body.arrayBuffer())
1136
+
1137
+ socket.cork()
1138
+ socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1')
1139
+ socket.write(buffer)
1140
+ socket.uncork()
1141
+
1142
+ request.onBodySent(buffer)
1143
+ request.onRequestSent()
1144
+
1145
+ if (!expectsPayload) {
1146
+ socket[kReset] = true
1147
+ }
1148
+
1149
+ client[kResume]()
1150
+ } catch (err) {
1151
+ util.destroy(socket, err)
1152
+ }
1153
+ }
1154
+
1155
+ async function writeIterable ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
1156
+ assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
1157
+
1158
+ let callback = null
1159
+ function onDrain () {
1160
+ if (callback) {
1161
+ const cb = callback
1162
+ callback = null
1163
+ cb()
1164
+ }
1165
+ }
1166
+
1167
+ const waitForDrain = () => new Promise((resolve, reject) => {
1168
+ assert(callback === null)
1169
+
1170
+ if (socket[kError]) {
1171
+ reject(socket[kError])
1172
+ } else {
1173
+ callback = resolve
1174
+ }
1175
+ })
1176
+
1177
+ socket
1178
+ .on('close', onDrain)
1179
+ .on('drain', onDrain)
1180
+
1181
+ const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header })
1182
+ try {
1183
+ // It's up to the user to somehow abort the async iterable.
1184
+ for await (const chunk of body) {
1185
+ if (socket[kError]) {
1186
+ throw socket[kError]
1187
+ }
1188
+
1189
+ if (!writer.write(chunk)) {
1190
+ await waitForDrain()
1191
+ }
1192
+ }
1193
+
1194
+ writer.end()
1195
+ } catch (err) {
1196
+ writer.destroy(err)
1197
+ } finally {
1198
+ socket
1199
+ .off('close', onDrain)
1200
+ .off('drain', onDrain)
1201
+ }
1202
+ }
1203
+
1204
+ class AsyncWriter {
1205
+ constructor ({ socket, request, contentLength, client, expectsPayload, header }) {
1206
+ this.socket = socket
1207
+ this.request = request
1208
+ this.contentLength = contentLength
1209
+ this.client = client
1210
+ this.bytesWritten = 0
1211
+ this.expectsPayload = expectsPayload
1212
+ this.header = header
1213
+
1214
+ socket[kWriting] = true
1215
+ }
1216
+
1217
+ write (chunk) {
1218
+ const { socket, request, contentLength, client, bytesWritten, expectsPayload, header } = this
1219
+
1220
+ if (socket[kError]) {
1221
+ throw socket[kError]
1222
+ }
1223
+
1224
+ if (socket.destroyed) {
1225
+ return false
1226
+ }
1227
+
1228
+ const len = Buffer.byteLength(chunk)
1229
+ if (!len) {
1230
+ return true
1231
+ }
1232
+
1233
+ // We should defer writing chunks.
1234
+ if (contentLength !== null && bytesWritten + len > contentLength) {
1235
+ if (client[kStrictContentLength]) {
1236
+ throw new RequestContentLengthMismatchError()
1237
+ }
1238
+
1239
+ process.emitWarning(new RequestContentLengthMismatchError())
1240
+ }
1241
+
1242
+ socket.cork()
1243
+
1244
+ if (bytesWritten === 0) {
1245
+ if (!expectsPayload) {
1246
+ socket[kReset] = true
1247
+ }
1248
+
1249
+ if (contentLength === null) {
1250
+ socket.write(`${header}transfer-encoding: chunked\r\n`, 'latin1')
1251
+ } else {
1252
+ socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1')
1253
+ }
1254
+ }
1255
+
1256
+ if (contentLength === null) {
1257
+ socket.write(`\r\n${len.toString(16)}\r\n`, 'latin1')
1258
+ }
1259
+
1260
+ this.bytesWritten += len
1261
+
1262
+ const ret = socket.write(chunk)
1263
+
1264
+ socket.uncork()
1265
+
1266
+ request.onBodySent(chunk)
1267
+
1268
+ if (!ret) {
1269
+ if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) {
1270
+ // istanbul ignore else: only for jest
1271
+ if (socket[kParser].timeout.refresh) {
1272
+ socket[kParser].timeout.refresh()
1273
+ }
1274
+ }
1275
+ }
1276
+
1277
+ return ret
1278
+ }
1279
+
1280
+ end () {
1281
+ const { socket, contentLength, client, bytesWritten, expectsPayload, header, request } = this
1282
+ request.onRequestSent()
1283
+
1284
+ socket[kWriting] = false
1285
+
1286
+ if (socket[kError]) {
1287
+ throw socket[kError]
1288
+ }
1289
+
1290
+ if (socket.destroyed) {
1291
+ return
1292
+ }
1293
+
1294
+ if (bytesWritten === 0) {
1295
+ if (expectsPayload) {
1296
+ // https://tools.ietf.org/html/rfc7230#section-3.3.2
1297
+ // A user agent SHOULD send a Content-Length in a request message when
1298
+ // no Transfer-Encoding is sent and the request method defines a meaning
1299
+ // for an enclosed payload body.
1300
+
1301
+ socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1')
1302
+ } else {
1303
+ socket.write(`${header}\r\n`, 'latin1')
1304
+ }
1305
+ } else if (contentLength === null) {
1306
+ socket.write('\r\n0\r\n\r\n', 'latin1')
1307
+ }
1308
+
1309
+ if (contentLength !== null && bytesWritten !== contentLength) {
1310
+ if (client[kStrictContentLength]) {
1311
+ throw new RequestContentLengthMismatchError()
1312
+ } else {
1313
+ process.emitWarning(new RequestContentLengthMismatchError())
1314
+ }
1315
+ }
1316
+
1317
+ if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) {
1318
+ // istanbul ignore else: only for jest
1319
+ if (socket[kParser].timeout.refresh) {
1320
+ socket[kParser].timeout.refresh()
1321
+ }
1322
+ }
1323
+
1324
+ client[kResume]()
1325
+ }
1326
+
1327
+ destroy (err) {
1328
+ const { socket, client } = this
1329
+
1330
+ socket[kWriting] = false
1331
+
1332
+ if (err) {
1333
+ assert(client[kRunning] <= 1, 'pipeline should only contain this request')
1334
+ util.destroy(socket, err)
1335
+ }
1336
+ }
1337
+ }
1338
+
1339
+ module.exports = connectH1