@suveren/gateway 0.9.0 → 0.10.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 +33 -1
- package/bin/suveren-gateway.js +203 -5
- package/content/integrations/erp.json +184 -0
- package/content/integrations/index.json +2 -1
- package/dist/control-plane/index.mjs +653 -179
- package/dist/mcp-server/http.mjs +466 -259
- package/dist/ui/assets/{index-CIiOCgjJ.js → index-B1qNl7zo.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/lib/as-pairing.mjs +77 -0
- package/lib/config.mjs +48 -0
- package/node_modules/undici/LICENSE +21 -0
- package/node_modules/undici/README.md +741 -0
- package/node_modules/undici/docs/docs/api/Agent.md +84 -0
- package/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
- package/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
- package/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
- package/node_modules/undici/docs/docs/api/Client.md +288 -0
- package/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
- package/node_modules/undici/docs/docs/api/Connector.md +115 -0
- package/node_modules/undici/docs/docs/api/ContentType.md +57 -0
- package/node_modules/undici/docs/docs/api/Cookies.md +128 -0
- package/node_modules/undici/docs/docs/api/Debug.md +62 -0
- package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +317 -0
- package/node_modules/undici/docs/docs/api/Dispatcher.md +1393 -0
- package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
- package/node_modules/undici/docs/docs/api/Errors.md +49 -0
- package/node_modules/undici/docs/docs/api/EventSource.md +45 -0
- package/node_modules/undici/docs/docs/api/Fetch.md +60 -0
- package/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
- package/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
- package/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
- package/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
- package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
- package/node_modules/undici/docs/docs/api/MockClient.md +81 -0
- package/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
- package/node_modules/undici/docs/docs/api/MockPool.md +555 -0
- package/node_modules/undici/docs/docs/api/Pool.md +84 -0
- package/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
- package/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
- package/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
- package/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
- package/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
- package/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
- package/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
- package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +275 -0
- package/node_modules/undici/docs/docs/api/Util.md +25 -0
- package/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
- package/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
- package/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
- package/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
- package/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
- package/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
- package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
- package/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
- package/node_modules/undici/index-fetch.js +65 -0
- package/node_modules/undici/index.d.ts +3 -0
- package/node_modules/undici/index.js +234 -0
- package/node_modules/undici/lib/api/abort-signal.js +59 -0
- package/node_modules/undici/lib/api/api-connect.js +110 -0
- package/node_modules/undici/lib/api/api-pipeline.js +252 -0
- package/node_modules/undici/lib/api/api-request.js +214 -0
- package/node_modules/undici/lib/api/api-stream.js +209 -0
- package/node_modules/undici/lib/api/api-upgrade.js +111 -0
- package/node_modules/undici/lib/api/index.js +7 -0
- package/node_modules/undici/lib/api/readable.js +580 -0
- package/node_modules/undici/lib/cache/memory-cache-store.js +279 -0
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +467 -0
- package/node_modules/undici/lib/core/connect.js +153 -0
- package/node_modules/undici/lib/core/constants.js +143 -0
- package/node_modules/undici/lib/core/diagnostics.js +227 -0
- package/node_modules/undici/lib/core/errors.js +477 -0
- package/node_modules/undici/lib/core/request.js +485 -0
- package/node_modules/undici/lib/core/socks5-client.js +422 -0
- package/node_modules/undici/lib/core/socks5-utils.js +212 -0
- package/node_modules/undici/lib/core/symbols.js +75 -0
- package/node_modules/undici/lib/core/tree.js +160 -0
- package/node_modules/undici/lib/core/util.js +992 -0
- package/node_modules/undici/lib/dispatcher/agent.js +158 -0
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +221 -0
- package/node_modules/undici/lib/dispatcher/client-h1.js +1746 -0
- package/node_modules/undici/lib/dispatcher/client-h2.js +1060 -0
- package/node_modules/undici/lib/dispatcher/client.js +668 -0
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +184 -0
- package/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
- package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
- package/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
- package/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
- package/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
- package/node_modules/undici/lib/dispatcher/pool.js +118 -0
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +319 -0
- package/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
- package/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +260 -0
- package/node_modules/undici/lib/encoding/index.js +33 -0
- package/node_modules/undici/lib/global.js +59 -0
- package/node_modules/undici/lib/handler/cache-handler.js +776 -0
- package/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
- package/node_modules/undici/lib/handler/decorator-handler.js +67 -0
- package/node_modules/undici/lib/handler/deduplication-handler.js +460 -0
- package/node_modules/undici/lib/handler/redirect-handler.js +238 -0
- package/node_modules/undici/lib/handler/retry-handler.js +504 -0
- package/node_modules/undici/lib/handler/unwrap-handler.js +106 -0
- package/node_modules/undici/lib/handler/wrap-handler.js +105 -0
- package/node_modules/undici/lib/interceptor/cache.js +621 -0
- package/node_modules/undici/lib/interceptor/decompress.js +551 -0
- package/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
- package/node_modules/undici/lib/interceptor/dns.js +571 -0
- package/node_modules/undici/lib/interceptor/dump.js +99 -0
- package/node_modules/undici/lib/interceptor/redirect.js +21 -0
- package/node_modules/undici/lib/interceptor/response-error.js +95 -0
- package/node_modules/undici/lib/interceptor/retry.js +19 -0
- package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
- package/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
- package/node_modules/undici/lib/llhttp/constants.js +531 -0
- package/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
- package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
- package/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
- package/node_modules/undici/lib/llhttp/utils.js +12 -0
- package/node_modules/undici/lib/mock/mock-agent.js +232 -0
- package/node_modules/undici/lib/mock/mock-call-history.js +248 -0
- package/node_modules/undici/lib/mock/mock-client.js +68 -0
- package/node_modules/undici/lib/mock/mock-errors.js +29 -0
- package/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
- package/node_modules/undici/lib/mock/mock-pool.js +68 -0
- package/node_modules/undici/lib/mock/mock-symbols.js +32 -0
- package/node_modules/undici/lib/mock/mock-utils.js +486 -0
- package/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
- package/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
- package/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
- package/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
- package/node_modules/undici/lib/util/cache.js +676 -0
- package/node_modules/undici/lib/util/date.js +670 -0
- package/node_modules/undici/lib/util/promise.js +28 -0
- package/node_modules/undici/lib/util/runtime-features.js +124 -0
- package/node_modules/undici/lib/util/stats.js +32 -0
- package/node_modules/undici/lib/util/timers.js +425 -0
- package/node_modules/undici/lib/web/cache/cache.js +864 -0
- package/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
- package/node_modules/undici/lib/web/cache/util.js +45 -0
- package/node_modules/undici/lib/web/cookies/constants.js +12 -0
- package/node_modules/undici/lib/web/cookies/index.js +199 -0
- package/node_modules/undici/lib/web/cookies/parse.js +314 -0
- package/node_modules/undici/lib/web/cookies/util.js +352 -0
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +494 -0
- package/node_modules/undici/lib/web/eventsource/eventsource.js +501 -0
- package/node_modules/undici/lib/web/eventsource/util.js +29 -0
- package/node_modules/undici/lib/web/fetch/LICENSE +21 -0
- package/node_modules/undici/lib/web/fetch/body.js +509 -0
- package/node_modules/undici/lib/web/fetch/constants.js +131 -0
- package/node_modules/undici/lib/web/fetch/data-url.js +596 -0
- package/node_modules/undici/lib/web/fetch/formdata-parser.js +586 -0
- package/node_modules/undici/lib/web/fetch/formdata.js +259 -0
- package/node_modules/undici/lib/web/fetch/global.js +40 -0
- package/node_modules/undici/lib/web/fetch/headers.js +719 -0
- package/node_modules/undici/lib/web/fetch/index.js +2413 -0
- package/node_modules/undici/lib/web/fetch/request.js +1115 -0
- package/node_modules/undici/lib/web/fetch/response.js +641 -0
- package/node_modules/undici/lib/web/fetch/util.js +1522 -0
- package/node_modules/undici/lib/web/infra/index.js +229 -0
- package/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
- package/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
- package/node_modules/undici/lib/web/webidl/index.js +1006 -0
- package/node_modules/undici/lib/web/websocket/connection.js +339 -0
- package/node_modules/undici/lib/web/websocket/constants.js +126 -0
- package/node_modules/undici/lib/web/websocket/events.js +331 -0
- package/node_modules/undici/lib/web/websocket/frame.js +133 -0
- package/node_modules/undici/lib/web/websocket/permessage-deflate.js +105 -0
- package/node_modules/undici/lib/web/websocket/receiver.js +507 -0
- package/node_modules/undici/lib/web/websocket/sender.js +109 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +496 -0
- package/node_modules/undici/lib/web/websocket/util.js +347 -0
- package/node_modules/undici/lib/web/websocket/websocket.js +758 -0
- package/node_modules/undici/package.json +152 -0
- package/node_modules/undici/scripts/strip-comments.js +10 -0
- package/node_modules/undici/types/README.md +6 -0
- package/node_modules/undici/types/agent.d.ts +32 -0
- package/node_modules/undici/types/api.d.ts +43 -0
- package/node_modules/undici/types/balanced-pool.d.ts +30 -0
- package/node_modules/undici/types/cache-interceptor.d.ts +179 -0
- package/node_modules/undici/types/cache.d.ts +36 -0
- package/node_modules/undici/types/client-stats.d.ts +15 -0
- package/node_modules/undici/types/client.d.ts +139 -0
- package/node_modules/undici/types/connector.d.ts +36 -0
- package/node_modules/undici/types/content-type.d.ts +21 -0
- package/node_modules/undici/types/cookies.d.ts +30 -0
- package/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
- package/node_modules/undici/types/dispatcher.d.ts +275 -0
- package/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
- package/node_modules/undici/types/errors.d.ts +177 -0
- package/node_modules/undici/types/eventsource.d.ts +66 -0
- package/node_modules/undici/types/fetch.d.ts +231 -0
- package/node_modules/undici/types/formdata.d.ts +114 -0
- package/node_modules/undici/types/global-dispatcher.d.ts +9 -0
- package/node_modules/undici/types/global-origin.d.ts +7 -0
- package/node_modules/undici/types/h2c-client.d.ts +73 -0
- package/node_modules/undici/types/handlers.d.ts +14 -0
- package/node_modules/undici/types/header.d.ts +160 -0
- package/node_modules/undici/types/index.d.ts +91 -0
- package/node_modules/undici/types/interceptors.d.ts +82 -0
- package/node_modules/undici/types/mock-agent.d.ts +68 -0
- package/node_modules/undici/types/mock-call-history.d.ts +111 -0
- package/node_modules/undici/types/mock-client.d.ts +27 -0
- package/node_modules/undici/types/mock-errors.d.ts +12 -0
- package/node_modules/undici/types/mock-interceptor.d.ts +94 -0
- package/node_modules/undici/types/mock-pool.d.ts +27 -0
- package/node_modules/undici/types/patch.d.ts +29 -0
- package/node_modules/undici/types/pool-stats.d.ts +19 -0
- package/node_modules/undici/types/pool.d.ts +41 -0
- package/node_modules/undici/types/proxy-agent.d.ts +29 -0
- package/node_modules/undici/types/readable.d.ts +68 -0
- package/node_modules/undici/types/retry-agent.d.ts +8 -0
- package/node_modules/undici/types/retry-handler.d.ts +125 -0
- package/node_modules/undici/types/round-robin-pool.d.ts +41 -0
- package/node_modules/undici/types/snapshot-agent.d.ts +109 -0
- package/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
- package/node_modules/undici/types/util.d.ts +18 -0
- package/node_modules/undici/types/utility.d.ts +7 -0
- package/node_modules/undici/types/webidl.d.ts +347 -0
- package/node_modules/undici/types/websocket.d.ts +188 -0
- package/package.json +4 -2
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const assert = require('node:assert')
|
|
3
|
+
|
|
4
|
+
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
|
|
5
|
+
const { RequestRetryError } = require('../core/errors')
|
|
6
|
+
const WrapHandler = require('./wrap-handler')
|
|
7
|
+
const {
|
|
8
|
+
isDisturbed,
|
|
9
|
+
parseRangeHeader,
|
|
10
|
+
wrapRequestBody
|
|
11
|
+
} = require('../core/util')
|
|
12
|
+
|
|
13
|
+
function calculateRetryAfterHeader (retryAfter) {
|
|
14
|
+
const retryTime = new Date(retryAfter).getTime()
|
|
15
|
+
return isNaN(retryTime) ? 0 : retryTime - Date.now()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
|
|
19
|
+
const contentLength = headers['content-length']
|
|
20
|
+
if (contentLength == null) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const length = Number(contentLength)
|
|
29
|
+
const expectedLength = range.end - range.start + 1
|
|
30
|
+
if (!Number.isFinite(length) || length !== expectedLength) {
|
|
31
|
+
throw new RequestRetryError('Content-Length mismatch', statusCode, {
|
|
32
|
+
headers,
|
|
33
|
+
data: { count: retryCount }
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A stable controller handed to the downstream handler for the lifetime of the
|
|
39
|
+
// request. Each transparent retry/resume is a separate dispatch with its own
|
|
40
|
+
// connection controller. The proxy always forwards to the active connection
|
|
41
|
+
// while preserving a downstream pause across controller replacement.
|
|
42
|
+
class RetryController {
|
|
43
|
+
#paused = false
|
|
44
|
+
#target = null
|
|
45
|
+
|
|
46
|
+
set target (target) {
|
|
47
|
+
this.#target = target
|
|
48
|
+
if (this.#paused) {
|
|
49
|
+
target?.pause()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get target () { return this.#target }
|
|
54
|
+
|
|
55
|
+
pause () {
|
|
56
|
+
this.#paused = true
|
|
57
|
+
this.#target?.pause()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resume () {
|
|
61
|
+
this.#paused = false
|
|
62
|
+
this.#target?.resume()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
abort (reason) {
|
|
66
|
+
this.#target?.abort(reason)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get paused () { return this.#paused || (this.#target?.paused ?? false) }
|
|
70
|
+
get aborted () { return this.#target?.aborted ?? false }
|
|
71
|
+
get reason () { return this.#target?.reason ?? null }
|
|
72
|
+
get rawHeaders () { return this.#target?.rawHeaders ?? null }
|
|
73
|
+
set rawHeaders (value) {
|
|
74
|
+
if (this.#target) {
|
|
75
|
+
this.#target.rawHeaders = value
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get rawTrailers () { return this.#target?.rawTrailers ?? null }
|
|
80
|
+
set rawTrailers (value) {
|
|
81
|
+
if (this.#target) {
|
|
82
|
+
this.#target.rawTrailers = value
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class RetryHandler {
|
|
88
|
+
constructor (opts, { dispatch, handler }) {
|
|
89
|
+
const { retryOptions, ...dispatchOpts } = opts
|
|
90
|
+
const {
|
|
91
|
+
// Retry scoped
|
|
92
|
+
retry: retryFn,
|
|
93
|
+
maxRetries,
|
|
94
|
+
maxTimeout,
|
|
95
|
+
minTimeout,
|
|
96
|
+
timeoutFactor,
|
|
97
|
+
// Response scoped
|
|
98
|
+
methods,
|
|
99
|
+
errorCodes,
|
|
100
|
+
retryAfter,
|
|
101
|
+
statusCodes,
|
|
102
|
+
throwOnError
|
|
103
|
+
} = retryOptions ?? {}
|
|
104
|
+
|
|
105
|
+
this.error = null
|
|
106
|
+
this.dispatch = dispatch
|
|
107
|
+
this.handler = WrapHandler.wrap(handler)
|
|
108
|
+
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
|
|
109
|
+
this.retryOpts = {
|
|
110
|
+
throwOnError: throwOnError ?? true,
|
|
111
|
+
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
|
|
112
|
+
retryAfter: retryAfter ?? true,
|
|
113
|
+
maxTimeout: maxTimeout ?? 30 * 1000, // 30s,
|
|
114
|
+
minTimeout: minTimeout ?? 500, // .5s
|
|
115
|
+
timeoutFactor: timeoutFactor ?? 2,
|
|
116
|
+
maxRetries: maxRetries ?? 5,
|
|
117
|
+
// What errors we should retry
|
|
118
|
+
methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'],
|
|
119
|
+
// Indicates which errors to retry
|
|
120
|
+
statusCodes: statusCodes ?? [500, 502, 503, 504, 429],
|
|
121
|
+
// List of errors to retry
|
|
122
|
+
errorCodes: errorCodes ?? [
|
|
123
|
+
'ECONNRESET',
|
|
124
|
+
'ECONNREFUSED',
|
|
125
|
+
'ENOTFOUND',
|
|
126
|
+
'ENETDOWN',
|
|
127
|
+
'ENETUNREACH',
|
|
128
|
+
'EHOSTDOWN',
|
|
129
|
+
'EHOSTUNREACH',
|
|
130
|
+
'EPIPE',
|
|
131
|
+
'UND_ERR_SOCKET'
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.retryCount = 0
|
|
136
|
+
this.retryCountCheckpoint = 0
|
|
137
|
+
this.headersSent = false
|
|
138
|
+
this.start = 0
|
|
139
|
+
this.end = null
|
|
140
|
+
this.etag = null
|
|
141
|
+
this.controllerProxy = new RetryController()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
onResponseStartWithRetry (controller, statusCode, headers, statusMessage, err) {
|
|
145
|
+
if (this.retryOpts.throwOnError) {
|
|
146
|
+
// Preserve old behavior for status codes that are not eligible for retry
|
|
147
|
+
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
148
|
+
if (this.headersSent) {
|
|
149
|
+
// The downstream handler already received the response from an
|
|
150
|
+
// earlier attempt. Forwarding this response would replace the
|
|
151
|
+
// downstream body and leave the original body pending forever.
|
|
152
|
+
this.handler.onResponseError?.(this.controllerProxy, err)
|
|
153
|
+
} else {
|
|
154
|
+
this.headersSent = true
|
|
155
|
+
this.checkpointResponseEnd(headers)
|
|
156
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
this.error = err
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isDisturbed(this.opts.body)) {
|
|
166
|
+
this.headersSent = true
|
|
167
|
+
this.checkpointResponseEnd(headers)
|
|
168
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function shouldRetry (passedErr) {
|
|
173
|
+
if (passedErr) {
|
|
174
|
+
if (this.headersSent) {
|
|
175
|
+
// The downstream handler already received the response from an
|
|
176
|
+
// earlier attempt. Forwarding this response would replace the
|
|
177
|
+
// downstream body and leave the original body pending forever.
|
|
178
|
+
this.handler.onResponseError?.(this.controllerProxy, passedErr)
|
|
179
|
+
} else {
|
|
180
|
+
this.headersSent = true
|
|
181
|
+
this.checkpointResponseEnd(headers)
|
|
182
|
+
this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
|
|
183
|
+
}
|
|
184
|
+
controller.resume()
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.error = err
|
|
189
|
+
controller.resume()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
controller.pause()
|
|
193
|
+
this.retryOpts.retry(
|
|
194
|
+
err,
|
|
195
|
+
{
|
|
196
|
+
state: { counter: this.retryCount },
|
|
197
|
+
opts: { retryOptions: this.retryOpts, ...this.opts }
|
|
198
|
+
},
|
|
199
|
+
shouldRetry.bind(this)
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
checkpointResponseEnd (headers) {
|
|
204
|
+
if (this.end == null && this.opts.method !== 'HEAD') {
|
|
205
|
+
const contentLength = headers['content-length']
|
|
206
|
+
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
|
207
|
+
|
|
208
|
+
assert(
|
|
209
|
+
this.end == null || Number.isFinite(this.end),
|
|
210
|
+
'invalid content-length'
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
this.resume = this.end != null
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
onRequestStart (controller, context) {
|
|
218
|
+
this.controllerProxy.target = controller
|
|
219
|
+
if (!this.headersSent) {
|
|
220
|
+
this.handler.onRequestStart?.(this.controllerProxy, context)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
onRequestUpgrade (_controller, statusCode, headers, socket) {
|
|
225
|
+
this.handler.onRequestUpgrade?.(this.controllerProxy, statusCode, headers, socket)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
|
|
229
|
+
const { statusCode, code, headers } = err
|
|
230
|
+
const { method, retryOptions } = opts
|
|
231
|
+
const {
|
|
232
|
+
maxRetries,
|
|
233
|
+
minTimeout,
|
|
234
|
+
maxTimeout,
|
|
235
|
+
timeoutFactor,
|
|
236
|
+
statusCodes,
|
|
237
|
+
errorCodes,
|
|
238
|
+
methods
|
|
239
|
+
} = retryOptions
|
|
240
|
+
const { counter } = state
|
|
241
|
+
|
|
242
|
+
// Any code that is not a Undici's originated and allowed to retry
|
|
243
|
+
if (code && code !== 'UND_ERR_REQ_RETRY' && !errorCodes.includes(code)) {
|
|
244
|
+
cb(err)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// If a set of method are provided and the current method is not in the list
|
|
249
|
+
if (Array.isArray(methods) && !methods.includes(method)) {
|
|
250
|
+
cb(err)
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// If a set of status code are provided and the current status code is not in the list
|
|
255
|
+
if (
|
|
256
|
+
statusCode != null &&
|
|
257
|
+
Array.isArray(statusCodes) &&
|
|
258
|
+
!statusCodes.includes(statusCode)
|
|
259
|
+
) {
|
|
260
|
+
cb(err)
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// If we reached the max number of retries
|
|
265
|
+
if (counter > maxRetries) {
|
|
266
|
+
cb(err)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let retryAfterHeader = headers?.['retry-after']
|
|
271
|
+
if (retryAfterHeader) {
|
|
272
|
+
retryAfterHeader = Number(retryAfterHeader)
|
|
273
|
+
retryAfterHeader = Number.isNaN(retryAfterHeader)
|
|
274
|
+
? calculateRetryAfterHeader(headers['retry-after'])
|
|
275
|
+
: retryAfterHeader * 1e3 // Retry-After is in seconds
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const retryTimeout =
|
|
279
|
+
retryAfterHeader > 0
|
|
280
|
+
? Math.min(retryAfterHeader, maxTimeout)
|
|
281
|
+
: Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
|
|
282
|
+
|
|
283
|
+
setTimeout(() => cb(null), retryTimeout)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
287
|
+
this.error = null
|
|
288
|
+
this.retryCount += 1
|
|
289
|
+
|
|
290
|
+
if (statusCode >= 300) {
|
|
291
|
+
const err = new RequestRetryError('Request failed', statusCode, {
|
|
292
|
+
headers,
|
|
293
|
+
data: {
|
|
294
|
+
count: this.retryCount
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
this.onResponseStartWithRetry(controller, statusCode, headers, statusMessage, err)
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Checkpoint for resume from where we left it
|
|
303
|
+
if (this.headersSent) {
|
|
304
|
+
// Only Partial Content 206 supposed to provide Content-Range,
|
|
305
|
+
// any other status code that partially consumed the payload
|
|
306
|
+
// should not be retried because it would result in downstream
|
|
307
|
+
// wrongly concatenate multiple responses.
|
|
308
|
+
if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
|
|
309
|
+
throw new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
|
|
310
|
+
headers,
|
|
311
|
+
data: { count: this.retryCount }
|
|
312
|
+
})
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const contentRange = parseRangeHeader(headers['content-range'])
|
|
316
|
+
// If no content range
|
|
317
|
+
if (!contentRange) {
|
|
318
|
+
// We always throw here as we want to indicate that we entred unexpected path
|
|
319
|
+
throw new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
320
|
+
headers,
|
|
321
|
+
data: { count: this.retryCount }
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Let's start with a weak etag check
|
|
326
|
+
if (this.etag != null && this.etag !== headers.etag) {
|
|
327
|
+
// We always throw here as we want to indicate that we entred unexpected path
|
|
328
|
+
throw new RequestRetryError('ETag mismatch', statusCode, {
|
|
329
|
+
headers,
|
|
330
|
+
data: { count: this.retryCount }
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)
|
|
335
|
+
|
|
336
|
+
const { start, size, end = size ? size - 1 : null } = contentRange
|
|
337
|
+
|
|
338
|
+
if (this.start !== start || (this.end != null && this.end !== end)) {
|
|
339
|
+
throw new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
340
|
+
headers,
|
|
341
|
+
data: { count: this.retryCount }
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (this.end == null) {
|
|
349
|
+
if (statusCode === 206) {
|
|
350
|
+
// First time we receive 206
|
|
351
|
+
const range = parseRangeHeader(headers['content-range'])
|
|
352
|
+
|
|
353
|
+
if (range == null) {
|
|
354
|
+
this.headersSent = true
|
|
355
|
+
this.handler.onResponseStart?.(
|
|
356
|
+
this.controllerProxy,
|
|
357
|
+
statusCode,
|
|
358
|
+
headers,
|
|
359
|
+
statusMessage
|
|
360
|
+
)
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
validatePartialResponseContentLength(headers, range, statusCode, this.retryCount)
|
|
365
|
+
|
|
366
|
+
const { start, size, end = size ? size - 1 : null } = range
|
|
367
|
+
assert(
|
|
368
|
+
start != null && Number.isFinite(start),
|
|
369
|
+
'content-range mismatch'
|
|
370
|
+
)
|
|
371
|
+
assert(end != null && Number.isFinite(end), 'invalid content-length')
|
|
372
|
+
|
|
373
|
+
this.start = start
|
|
374
|
+
this.end = end
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// We make our best to checkpoint the body for further range headers
|
|
378
|
+
if (this.end == null) {
|
|
379
|
+
const contentLength = headers['content-length']
|
|
380
|
+
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
assert(Number.isFinite(this.start))
|
|
384
|
+
assert(
|
|
385
|
+
this.end == null || Number.isFinite(this.end),
|
|
386
|
+
'invalid content-length'
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
this.resume = true
|
|
390
|
+
this.etag = headers.etag != null ? headers.etag : null
|
|
391
|
+
|
|
392
|
+
// Weak etags are not useful for comparison nor cache
|
|
393
|
+
// for instance not safe to assume if the response is byte-per-byte
|
|
394
|
+
// equal
|
|
395
|
+
if (
|
|
396
|
+
this.etag != null &&
|
|
397
|
+
this.etag[0] === 'W' &&
|
|
398
|
+
this.etag[1] === '/'
|
|
399
|
+
) {
|
|
400
|
+
this.etag = null
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
this.headersSent = true
|
|
404
|
+
this.handler.onResponseStart?.(
|
|
405
|
+
this.controllerProxy,
|
|
406
|
+
statusCode,
|
|
407
|
+
headers,
|
|
408
|
+
statusMessage
|
|
409
|
+
)
|
|
410
|
+
} else {
|
|
411
|
+
throw new RequestRetryError('Request failed', statusCode, {
|
|
412
|
+
headers,
|
|
413
|
+
data: { count: this.retryCount }
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
onResponseData (_controller, chunk) {
|
|
419
|
+
if (this.error) {
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.start += chunk.length
|
|
424
|
+
|
|
425
|
+
this.handler.onResponseData?.(this.controllerProxy, chunk)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
onResponseEnd (_controller, trailers) {
|
|
429
|
+
if (this.error && this.retryOpts.throwOnError) {
|
|
430
|
+
throw this.error
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!this.error) {
|
|
434
|
+
this.retryCount = 0
|
|
435
|
+
return this.handler.onResponseEnd?.(this.controllerProxy, trailers)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
this.retry()
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
retry () {
|
|
442
|
+
if (this.start !== 0) {
|
|
443
|
+
const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
|
|
444
|
+
|
|
445
|
+
// Weak etag check - weak etags will make comparison algorithms never match
|
|
446
|
+
if (this.etag != null) {
|
|
447
|
+
headers['if-match'] = this.etag
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
this.opts = {
|
|
451
|
+
...this.opts,
|
|
452
|
+
headers: {
|
|
453
|
+
...this.opts.headers,
|
|
454
|
+
...headers
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
this.retryCountCheckpoint = this.retryCount
|
|
461
|
+
this.dispatch(this.opts, this)
|
|
462
|
+
} catch (err) {
|
|
463
|
+
this.handler.onResponseError?.(this.controllerProxy, err)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
onResponseError (controller, err) {
|
|
468
|
+
if (controller?.aborted || isDisturbed(this.opts.body) || (this.headersSent && !this.resume)) {
|
|
469
|
+
this.handler.onResponseError?.(this.controllerProxy, err)
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function shouldRetry (returnedErr) {
|
|
474
|
+
if (!returnedErr) {
|
|
475
|
+
this.retry()
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
this.handler?.onResponseError?.(this.controllerProxy, returnedErr)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// We reconcile in case of a mix between network errors
|
|
483
|
+
// and server error response
|
|
484
|
+
if (this.retryCount - this.retryCountCheckpoint > 0) {
|
|
485
|
+
// We count the difference between the last checkpoint and the current retry count
|
|
486
|
+
this.retryCount =
|
|
487
|
+
this.retryCountCheckpoint +
|
|
488
|
+
(this.retryCount - this.retryCountCheckpoint)
|
|
489
|
+
} else {
|
|
490
|
+
this.retryCount += 1
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
this.retryOpts.retry(
|
|
494
|
+
err,
|
|
495
|
+
{
|
|
496
|
+
state: { counter: this.retryCount },
|
|
497
|
+
opts: { retryOptions: this.retryOpts, ...this.opts }
|
|
498
|
+
},
|
|
499
|
+
shouldRetry.bind(this)
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
module.exports = RetryHandler
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { parseHeaders } = require('../core/util')
|
|
4
|
+
const { InvalidArgumentError } = require('../core/errors')
|
|
5
|
+
|
|
6
|
+
const kResume = Symbol('resume')
|
|
7
|
+
|
|
8
|
+
class UnwrapController {
|
|
9
|
+
#paused = false
|
|
10
|
+
#reason = null
|
|
11
|
+
#aborted = false
|
|
12
|
+
#abort
|
|
13
|
+
|
|
14
|
+
[kResume] = null
|
|
15
|
+
|
|
16
|
+
rawHeaders = null
|
|
17
|
+
rawTrailers = null
|
|
18
|
+
|
|
19
|
+
constructor (abort) {
|
|
20
|
+
this.#abort = abort
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pause () {
|
|
24
|
+
this.#paused = true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
resume () {
|
|
28
|
+
if (this.#paused) {
|
|
29
|
+
this.#paused = false
|
|
30
|
+
this[kResume]?.()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
abort (reason) {
|
|
35
|
+
if (!this.#aborted) {
|
|
36
|
+
this.#aborted = true
|
|
37
|
+
this.#reason = reason
|
|
38
|
+
this.#abort(reason)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get aborted () {
|
|
43
|
+
return this.#aborted
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get reason () {
|
|
47
|
+
return this.#reason
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get paused () {
|
|
51
|
+
return this.#paused
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = class UnwrapHandler {
|
|
56
|
+
#handler
|
|
57
|
+
#controller
|
|
58
|
+
|
|
59
|
+
constructor (handler) {
|
|
60
|
+
this.#handler = handler
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static unwrap (handler) {
|
|
64
|
+
// TODO (fix): More checks...
|
|
65
|
+
return !handler.onRequestStart ? handler : new UnwrapHandler(handler)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onConnect (abort, context) {
|
|
69
|
+
this.#controller = new UnwrapController(abort)
|
|
70
|
+
this.#handler.onRequestStart?.(this.#controller, context)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
onResponseStarted () {
|
|
74
|
+
return this.#handler.onResponseStarted?.()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onUpgrade (statusCode, rawHeaders, socket) {
|
|
78
|
+
this.#controller.rawHeaders = rawHeaders
|
|
79
|
+
this.#handler.onRequestUpgrade?.(this.#controller, statusCode, parseHeaders(rawHeaders), socket)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
83
|
+
this.#controller[kResume] = resume
|
|
84
|
+
this.#controller.rawHeaders = rawHeaders
|
|
85
|
+
this.#handler.onResponseStart?.(this.#controller, statusCode, parseHeaders(rawHeaders), statusMessage)
|
|
86
|
+
return !this.#controller.paused
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onData (data) {
|
|
90
|
+
this.#handler.onResponseData?.(this.#controller, data)
|
|
91
|
+
return !this.#controller.paused
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onComplete (rawTrailers) {
|
|
95
|
+
this.#controller.rawTrailers = rawTrailers
|
|
96
|
+
this.#handler.onResponseEnd?.(this.#controller, parseHeaders(rawTrailers))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onError (err) {
|
|
100
|
+
if (!this.#handler.onResponseError) {
|
|
101
|
+
throw new InvalidArgumentError('invalid onError method')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.#handler.onResponseError?.(this.#controller, err)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { InvalidArgumentError } = require('../core/errors')
|
|
4
|
+
|
|
5
|
+
module.exports = class WrapHandler {
|
|
6
|
+
#handler
|
|
7
|
+
|
|
8
|
+
constructor (handler) {
|
|
9
|
+
this.#handler = handler
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static wrap (handler) {
|
|
13
|
+
// TODO (fix): More checks...
|
|
14
|
+
return handler.onRequestStart ? handler : new WrapHandler(handler)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Unwrap Interface
|
|
18
|
+
|
|
19
|
+
onConnect (abort, context) {
|
|
20
|
+
return this.#handler.onConnect?.(abort, context)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onResponseStarted () {
|
|
24
|
+
return this.#handler.onResponseStarted?.()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
28
|
+
return this.#handler.onHeaders?.(statusCode, rawHeaders, resume, statusMessage)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onUpgrade (statusCode, rawHeaders, socket) {
|
|
32
|
+
return this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onData (data) {
|
|
36
|
+
return this.#handler.onData?.(data)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onComplete (trailers) {
|
|
40
|
+
return this.#handler.onComplete?.(trailers)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onError (err) {
|
|
44
|
+
if (!this.#handler.onError) {
|
|
45
|
+
throw err
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return this.#handler.onError?.(err)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Wrap Interface
|
|
52
|
+
|
|
53
|
+
onRequestStart (controller, context) {
|
|
54
|
+
this.#handler.onConnect?.((reason) => controller.abort(reason), context)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
58
|
+
const rawHeaders = []
|
|
59
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
60
|
+
rawHeaders.push(Buffer.from(key, 'latin1'), toRawHeaderValue(val))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
67
|
+
const rawHeaders = []
|
|
68
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
69
|
+
rawHeaders.push(Buffer.from(key, 'latin1'), toRawHeaderValue(val))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (this.#handler.onHeaders?.(statusCode, rawHeaders, () => controller.resume(), statusMessage) === false) {
|
|
73
|
+
controller.pause()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onResponseData (controller, data) {
|
|
78
|
+
if (this.#handler.onData?.(data) === false) {
|
|
79
|
+
controller.pause()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onResponseEnd (controller, trailers) {
|
|
84
|
+
const rawTrailers = []
|
|
85
|
+
for (const [key, val] of Object.entries(trailers)) {
|
|
86
|
+
rawTrailers.push(Buffer.from(key, 'latin1'), toRawHeaderValue(val))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.#handler.onComplete?.(rawTrailers)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
onResponseError (controller, err) {
|
|
93
|
+
if (!this.#handler.onError) {
|
|
94
|
+
throw new InvalidArgumentError('invalid onError method')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.#handler.onError?.(err)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toRawHeaderValue (value) {
|
|
102
|
+
return Array.isArray(value)
|
|
103
|
+
? value.map((item) => Buffer.from(item, 'latin1'))
|
|
104
|
+
: Buffer.from(value, 'latin1')
|
|
105
|
+
}
|