@nxtedition/nxt-undici 4.2.26 → 5.0.1
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/lib/errors.js +218 -0
- package/lib/index.js +35 -21
- package/lib/interceptor/cache.js +71 -70
- package/lib/interceptor/log.js +5 -5
- package/lib/interceptor/proxy.js +17 -31
- package/lib/interceptor/redirect.js +5 -5
- package/lib/interceptor/response-error.js +4 -4
- package/lib/interceptor/response-retry.js +4 -4
- package/lib/interceptor/response-verify.js +13 -12
- package/lib/readable.js +523 -0
- package/lib/request.js +167 -0
- package/package.json +1 -1
package/lib/errors.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
export class UndiciError extends Error {
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message, options)
|
|
6
|
+
this.name = 'UndiciError'
|
|
7
|
+
this.code = 'UND_ERR'
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ConnectTimeoutError extends UndiciError {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message)
|
|
14
|
+
this.name = 'ConnectTimeoutError'
|
|
15
|
+
this.message = message || 'Connect Timeout Error'
|
|
16
|
+
this.code = 'UND_ERR_CONNECT_TIMEOUT'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class HeadersTimeoutError extends UndiciError {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message)
|
|
23
|
+
this.name = 'HeadersTimeoutError'
|
|
24
|
+
this.message = message || 'Headers Timeout Error'
|
|
25
|
+
this.code = 'UND_ERR_HEADERS_TIMEOUT'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class HeadersOverflowError extends UndiciError {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message)
|
|
32
|
+
this.name = 'HeadersOverflowError'
|
|
33
|
+
this.message = message || 'Headers Overflow Error'
|
|
34
|
+
this.code = 'UND_ERR_HEADERS_OVERFLOW'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class BodyTimeoutError extends UndiciError {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message)
|
|
41
|
+
this.name = 'BodyTimeoutError'
|
|
42
|
+
this.message = message || 'Body Timeout Error'
|
|
43
|
+
this.code = 'UND_ERR_BODY_TIMEOUT'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ResponseStatusCodeError extends UndiciError {
|
|
48
|
+
constructor(message, statusCode, headers, body) {
|
|
49
|
+
super(message)
|
|
50
|
+
this.name = 'ResponseStatusCodeError'
|
|
51
|
+
this.message = message || 'Response Status Code Error'
|
|
52
|
+
this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
|
|
53
|
+
this.body = body
|
|
54
|
+
this.status = statusCode
|
|
55
|
+
this.statusCode = statusCode
|
|
56
|
+
this.headers = headers
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class InvalidArgumentError extends UndiciError {
|
|
61
|
+
constructor(message) {
|
|
62
|
+
super(message)
|
|
63
|
+
this.name = 'InvalidArgumentError'
|
|
64
|
+
this.message = message || 'Invalid Argument Error'
|
|
65
|
+
this.code = 'UND_ERR_INVALID_ARG'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class InvalidReturnValueError extends UndiciError {
|
|
70
|
+
constructor(message) {
|
|
71
|
+
super(message)
|
|
72
|
+
this.name = 'InvalidReturnValueError'
|
|
73
|
+
this.message = message || 'Invalid Return Value Error'
|
|
74
|
+
this.code = 'UND_ERR_INVALID_RETURN_VALUE'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class AbortError extends UndiciError {
|
|
79
|
+
constructor(message) {
|
|
80
|
+
super(message)
|
|
81
|
+
this.name = 'AbortError'
|
|
82
|
+
this.message = message || 'The operation was aborted'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class RequestAbortedError extends AbortError {
|
|
87
|
+
constructor(message) {
|
|
88
|
+
super(message)
|
|
89
|
+
this.name = 'AbortError'
|
|
90
|
+
this.message = message || 'Request aborted'
|
|
91
|
+
this.code = 'UND_ERR_ABORTED'
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class InformationalError extends UndiciError {
|
|
96
|
+
constructor(message) {
|
|
97
|
+
super(message)
|
|
98
|
+
this.name = 'InformationalError'
|
|
99
|
+
this.message = message || 'Request information'
|
|
100
|
+
this.code = 'UND_ERR_INFO'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class RequestContentLengthMismatchError extends UndiciError {
|
|
105
|
+
constructor(message) {
|
|
106
|
+
super(message)
|
|
107
|
+
this.name = 'RequestContentLengthMismatchError'
|
|
108
|
+
this.message = message || 'Request body length does not match content-length header'
|
|
109
|
+
this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class ResponseContentLengthMismatchError extends UndiciError {
|
|
114
|
+
constructor(message) {
|
|
115
|
+
super(message)
|
|
116
|
+
this.name = 'ResponseContentLengthMismatchError'
|
|
117
|
+
this.message = message || 'Response body length does not match content-length header'
|
|
118
|
+
this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class ClientDestroyedError extends UndiciError {
|
|
123
|
+
constructor(message) {
|
|
124
|
+
super(message)
|
|
125
|
+
this.name = 'ClientDestroyedError'
|
|
126
|
+
this.message = message || 'The client is destroyed'
|
|
127
|
+
this.code = 'UND_ERR_DESTROYED'
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class ClientClosedError extends UndiciError {
|
|
132
|
+
constructor(message) {
|
|
133
|
+
super(message)
|
|
134
|
+
this.name = 'ClientClosedError'
|
|
135
|
+
this.message = message || 'The client is closed'
|
|
136
|
+
this.code = 'UND_ERR_CLOSED'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export class SocketError extends UndiciError {
|
|
141
|
+
constructor(message, socket) {
|
|
142
|
+
super(message)
|
|
143
|
+
this.name = 'SocketError'
|
|
144
|
+
this.message = message || 'Socket error'
|
|
145
|
+
this.code = 'UND_ERR_SOCKET'
|
|
146
|
+
this.socket = socket
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export class NotSupportedError extends UndiciError {
|
|
151
|
+
constructor(message) {
|
|
152
|
+
super(message)
|
|
153
|
+
this.name = 'NotSupportedError'
|
|
154
|
+
this.message = message || 'Not supported error'
|
|
155
|
+
this.code = 'UND_ERR_NOT_SUPPORTED'
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export class BalancedPoolMissingUpstreamError extends UndiciError {
|
|
160
|
+
constructor(message) {
|
|
161
|
+
super(message)
|
|
162
|
+
this.name = 'MissingUpstreamError'
|
|
163
|
+
this.message = message || 'No upstream has been added to the BalancedPool'
|
|
164
|
+
this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export class HTTPParserError extends Error {
|
|
169
|
+
constructor(message, code, data) {
|
|
170
|
+
super(message)
|
|
171
|
+
this.name = 'HTTPParserError'
|
|
172
|
+
this.code = code ? `HPE_${code}` : undefined
|
|
173
|
+
this.data = data ? data.toString() : undefined
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export class ResponseExceededMaxSizeError extends UndiciError {
|
|
178
|
+
constructor(message) {
|
|
179
|
+
super(message)
|
|
180
|
+
this.name = 'ResponseExceededMaxSizeError'
|
|
181
|
+
this.message = message || 'Response content exceeded max size'
|
|
182
|
+
this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE'
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export class RequestRetryError extends UndiciError {
|
|
187
|
+
constructor(message, code, { headers, data }) {
|
|
188
|
+
super(message)
|
|
189
|
+
this.name = 'RequestRetryError'
|
|
190
|
+
this.message = message || 'Request retry error'
|
|
191
|
+
this.code = 'UND_ERR_REQ_RETRY'
|
|
192
|
+
this.statusCode = code
|
|
193
|
+
this.data = data
|
|
194
|
+
this.headers = headers
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export class ResponseError extends UndiciError {
|
|
199
|
+
constructor(message, code, { headers, body }) {
|
|
200
|
+
super(message)
|
|
201
|
+
this.name = 'ResponseError'
|
|
202
|
+
this.message = message || 'Response error'
|
|
203
|
+
this.code = 'UND_ERR_RESPONSE'
|
|
204
|
+
this.statusCode = code
|
|
205
|
+
this.body = body
|
|
206
|
+
this.headers = headers
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export class SecureProxyConnectionError extends UndiciError {
|
|
211
|
+
constructor(cause, message, options = {}) {
|
|
212
|
+
super(message, { cause, ...options })
|
|
213
|
+
this.name = 'SecureProxyConnectionError'
|
|
214
|
+
this.message = message || 'Secure Proxy Connection failed'
|
|
215
|
+
this.code = 'UND_ERR_PRX_TLS'
|
|
216
|
+
this.cause = cause
|
|
217
|
+
}
|
|
218
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import undici from 'undici'
|
|
2
2
|
import { parseHeaders } from './utils.js'
|
|
3
|
+
import { request as _request } from './request.js'
|
|
3
4
|
|
|
4
5
|
const dispatcherCache = new WeakMap()
|
|
5
6
|
|
|
@@ -23,10 +24,36 @@ function defaultLookup(origin, opts, callback) {
|
|
|
23
24
|
callback(null, Array.isArray(origin) ? origin[Math.floor(Math.random() * origin.length)] : origin)
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
function
|
|
27
|
+
export function compose(...interceptors) {
|
|
28
|
+
let dispatch = interceptors.shift()
|
|
29
|
+
if (typeof dispatch?.dispatch === 'function') {
|
|
30
|
+
dispatch = dispatch.dispatch.bind(dispatch)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const interceptor of interceptors) {
|
|
34
|
+
if (interceptor == null) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof interceptor !== 'function') {
|
|
39
|
+
throw new TypeError(`invalid interceptor, expected function received ${typeof interceptor}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
dispatch = interceptor(dispatch)
|
|
43
|
+
|
|
44
|
+
if (dispatch == null || typeof dispatch !== 'function' || dispatch.length !== 2) {
|
|
45
|
+
throw new TypeError('invalid interceptor')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return dispatch
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function wrapDispatch(dispatcher) {
|
|
27
53
|
let wrappedDispatcher = dispatcherCache.get(dispatcher)
|
|
28
54
|
if (wrappedDispatcher == null) {
|
|
29
|
-
wrappedDispatcher =
|
|
55
|
+
wrappedDispatcher = compose(
|
|
56
|
+
dispatcher,
|
|
30
57
|
interceptors.responseError(),
|
|
31
58
|
interceptors.requestBodyFactory(),
|
|
32
59
|
interceptors.log(),
|
|
@@ -84,26 +111,13 @@ function wrapDispatcher(dispatcher) {
|
|
|
84
111
|
}
|
|
85
112
|
|
|
86
113
|
export function dispatch(dispatcher, opts, handler) {
|
|
87
|
-
return
|
|
114
|
+
return wrapDispatch(dispatcher)(opts, handler)
|
|
88
115
|
}
|
|
89
116
|
|
|
90
|
-
// HACK
|
|
91
|
-
const _request = undici.Dispatcher.prototype.request
|
|
92
|
-
|
|
93
117
|
export async function request(url, opts) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
opts
|
|
98
|
-
|
|
99
|
-
opts = { url, ...opts }
|
|
100
|
-
} else if (typeof url.origin === 'string' && typeof (url.path ?? url.pathname) === 'string') {
|
|
101
|
-
opts = opts ? { ...url, ...opts } : url
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (opts == null && typeof url === 'object' && url != null) {
|
|
105
|
-
opts = url
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return _request.call(await wrapDispatcher(opts.dispatcher ?? undici.getGlobalDispatcher()), opts)
|
|
118
|
+
return _request(
|
|
119
|
+
await wrapDispatch(opts?.dispatch ?? opts?.dispatcher ?? undici.getGlobalDispatcher()),
|
|
120
|
+
url,
|
|
121
|
+
opts,
|
|
122
|
+
)
|
|
109
123
|
}
|
package/lib/interceptor/cache.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
1
|
import { LRUCache } from 'lru-cache'
|
|
3
|
-
import {
|
|
2
|
+
import { parseHeaders, parseCacheControl } from '../utils.js'
|
|
4
3
|
|
|
5
|
-
class CacheHandler
|
|
4
|
+
class CacheHandler {
|
|
6
5
|
#handler
|
|
7
6
|
#store
|
|
8
7
|
#key
|
|
9
|
-
#value
|
|
8
|
+
#value
|
|
10
9
|
|
|
11
10
|
constructor({ key, handler, store }) {
|
|
12
|
-
super(handler)
|
|
13
|
-
|
|
14
11
|
this.#key = key
|
|
15
12
|
this.#handler = handler
|
|
16
13
|
this.#store = store
|
|
@@ -24,7 +21,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
24
21
|
|
|
25
22
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
26
23
|
if (statusCode !== 307) {
|
|
27
|
-
return this.#handler.onHeaders(statusCode,
|
|
24
|
+
return this.#handler.onHeaders(statusCode, null, resume, statusMessage, headers)
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
// TODO (fix): Support vary header.
|
|
@@ -34,7 +31,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
34
31
|
const maxEntrySize = this.#store.maxEntrySize ?? Infinity
|
|
35
32
|
|
|
36
33
|
if (
|
|
37
|
-
contentLength < maxEntrySize &&
|
|
34
|
+
(!contentLength || contentLength < maxEntrySize) &&
|
|
38
35
|
cacheControl &&
|
|
39
36
|
cacheControl.public &&
|
|
40
37
|
!cacheControl.private &&
|
|
@@ -47,29 +44,21 @@ class CacheHandler extends DecoratorHandler {
|
|
|
47
44
|
!cacheControl['proxy-revalidate']
|
|
48
45
|
) {
|
|
49
46
|
const maxAge = cacheControl['s-max-age'] ?? cacheControl['max-age']
|
|
50
|
-
const ttl = cacheControl.immutable
|
|
51
|
-
? 31556952 // 1 year
|
|
52
|
-
: Number(maxAge)
|
|
47
|
+
const ttl = cacheControl.immutable ? 31556952 : Number(maxAge)
|
|
53
48
|
|
|
54
49
|
if (ttl > 0) {
|
|
55
50
|
this.#value = {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
body: [],
|
|
62
|
-
},
|
|
63
|
-
size:
|
|
64
|
-
(rawHeaders?.reduce((xs, x) => xs + x.length, 0) ?? 0) +
|
|
65
|
-
(statusMessage?.length ?? 0) +
|
|
66
|
-
64,
|
|
51
|
+
statusCode,
|
|
52
|
+
statusMessage,
|
|
53
|
+
headers,
|
|
54
|
+
body: [],
|
|
55
|
+
size: 256, // TODO (fix): Measure headers size...
|
|
67
56
|
ttl: ttl * 1e3,
|
|
68
57
|
}
|
|
69
58
|
}
|
|
70
59
|
}
|
|
71
60
|
|
|
72
|
-
return this.#handler.onHeaders(statusCode,
|
|
61
|
+
return this.#handler.onHeaders(statusCode, null, resume, statusMessage, headers)
|
|
73
62
|
}
|
|
74
63
|
|
|
75
64
|
onData(chunk) {
|
|
@@ -86,26 +75,46 @@ class CacheHandler extends DecoratorHandler {
|
|
|
86
75
|
return this.#handler.onData(chunk)
|
|
87
76
|
}
|
|
88
77
|
|
|
89
|
-
onComplete(
|
|
78
|
+
onComplete() {
|
|
90
79
|
if (this.#value) {
|
|
91
|
-
this.#
|
|
92
|
-
|
|
93
|
-
|
|
80
|
+
this.#store.set(
|
|
81
|
+
this.#key,
|
|
82
|
+
{
|
|
83
|
+
statusCode: this.#value.statusCode,
|
|
84
|
+
statusMessage: this.#value.statusMessage,
|
|
85
|
+
headers: this.#value.headers,
|
|
86
|
+
body: Buffer.concat(this.#value.body),
|
|
87
|
+
},
|
|
88
|
+
{ ttl: this.#value.ttl, size: this.#value.size },
|
|
89
|
+
)
|
|
94
90
|
}
|
|
95
|
-
return this.#handler.onComplete(
|
|
91
|
+
return this.#handler.onComplete()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onError(err) {
|
|
95
|
+
this.#handler.onError(err)
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
constructor({ maxSize = 1024 * 1024, maxEntrySize = 128 * 1024 }) {
|
|
99
|
+
class MemoryCacheStore {
|
|
100
|
+
constructor({ maxSize = 1024 * 1024, maxEntrySize = 128 * 1024, maxTTL = 48 * 3600e3 }) {
|
|
102
101
|
this.maxSize = maxSize
|
|
103
102
|
this.maxEntrySize = maxEntrySize
|
|
103
|
+
this.maxTTL = maxTTL
|
|
104
104
|
this.cache = new LRUCache({ maxSize })
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
set(key, value, opts) {
|
|
108
|
-
this.cache.set(
|
|
108
|
+
this.cache.set(
|
|
109
|
+
key,
|
|
110
|
+
value,
|
|
111
|
+
opts
|
|
112
|
+
? {
|
|
113
|
+
ttl: opts.ttl ? Math.min(opts.ttl, this.maxTTL) : undefined,
|
|
114
|
+
size: opts.size,
|
|
115
|
+
}
|
|
116
|
+
: undefined,
|
|
117
|
+
)
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
get(key) {
|
|
@@ -118,14 +127,13 @@ function makeKey(opts) {
|
|
|
118
127
|
return `${opts.origin}:${opts.method}:${opts.path}`
|
|
119
128
|
}
|
|
120
129
|
|
|
121
|
-
const DEFAULT_CACHE_STORE = new
|
|
130
|
+
const DEFAULT_CACHE_STORE = new MemoryCacheStore({ maxSize: 128 * 1024, maxEntrySize: 1024 })
|
|
122
131
|
|
|
123
132
|
export default (opts) => (dispatch) => (opts, handler) => {
|
|
124
133
|
if (!opts.cache || opts.upgrade) {
|
|
125
134
|
return dispatch(opts, handler)
|
|
126
135
|
}
|
|
127
136
|
|
|
128
|
-
// TODO (fix): Cache other methods?
|
|
129
137
|
if (opts.method !== 'GET' && opts.method !== 'HEAD') {
|
|
130
138
|
return dispatch(opts, handler)
|
|
131
139
|
}
|
|
@@ -143,9 +151,6 @@ export default (opts) => (dispatch) => (opts, handler) => {
|
|
|
143
151
|
return dispatch(opts, handler)
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
// TODO (fix): Support body...
|
|
147
|
-
assert(opts.method === 'GET' || opts.method === 'HEAD')
|
|
148
|
-
|
|
149
154
|
// Dump body...
|
|
150
155
|
opts.body?.on('error', () => {}).resume()
|
|
151
156
|
|
|
@@ -155,47 +160,43 @@ export default (opts) => (dispatch) => (opts, handler) => {
|
|
|
155
160
|
throw new Error(`Cache store not provided.`)
|
|
156
161
|
}
|
|
157
162
|
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
const key = makeKey(opts)
|
|
164
|
+
const entry = store.get(key)
|
|
160
165
|
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
value = store.get(key)
|
|
166
|
+
if (!entry) {
|
|
167
|
+
return dispatch(opts, new CacheHandler({ handler, store, key: makeKey(opts) }))
|
|
164
168
|
}
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
const { statusCode, statusMessage, headers, body } = entry
|
|
171
|
+
|
|
172
|
+
let aborted = false
|
|
173
|
+
const abort = () => {
|
|
174
|
+
aborted = true
|
|
175
|
+
}
|
|
176
|
+
const resume = () => {}
|
|
170
177
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
178
|
+
try {
|
|
179
|
+
handler.onConnect(abort)
|
|
180
|
+
if (aborted) {
|
|
181
|
+
return true
|
|
174
182
|
}
|
|
175
183
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (ret === false) {
|
|
186
|
-
// TODO (fix): back pressure...
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
handler.onComplete(rawTrailers)
|
|
190
|
-
} else {
|
|
191
|
-
handler.onComplete([])
|
|
184
|
+
handler.onHeaders(statusCode, null, resume, statusMessage, headers)
|
|
185
|
+
if (aborted) {
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (body.byteKength > 0) {
|
|
190
|
+
handler.onData(body)
|
|
191
|
+
if (aborted) {
|
|
192
|
+
return true
|
|
192
193
|
}
|
|
193
|
-
} catch (err) {
|
|
194
|
-
handler.onError(err)
|
|
195
194
|
}
|
|
196
195
|
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
196
|
+
handler.onComplete()
|
|
197
|
+
} catch (err) {
|
|
198
|
+
handler.onError(err)
|
|
200
199
|
}
|
|
200
|
+
|
|
201
|
+
return true
|
|
201
202
|
}
|
package/lib/interceptor/log.js
CHANGED
|
@@ -5,7 +5,7 @@ class Handler extends DecoratorHandler {
|
|
|
5
5
|
#opts
|
|
6
6
|
#logger
|
|
7
7
|
|
|
8
|
-
#abort
|
|
8
|
+
#abort
|
|
9
9
|
#aborted = false
|
|
10
10
|
#pos = 0
|
|
11
11
|
#created = 0
|
|
@@ -59,14 +59,14 @@ class Handler extends DecoratorHandler {
|
|
|
59
59
|
|
|
60
60
|
this.#logger.debug(
|
|
61
61
|
{
|
|
62
|
-
ureq: { id: this.#opts.id, url: this.#opts.url },
|
|
62
|
+
ureq: { id: this.#opts.id, url: String(this.#opts.url) },
|
|
63
63
|
ures: { statusCode, headers },
|
|
64
64
|
elapsedTime: this.#timing.headers,
|
|
65
65
|
},
|
|
66
66
|
'upstream request response',
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
return this.#handler.onHeaders(statusCode,
|
|
69
|
+
return this.#handler.onHeaders(statusCode, null, resume, statusMessage, headers)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
onData(chunk) {
|
|
@@ -79,7 +79,7 @@ class Handler extends DecoratorHandler {
|
|
|
79
79
|
return this.#handler.onData(chunk)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
onComplete(
|
|
82
|
+
onComplete() {
|
|
83
83
|
this.#timing.end = performance.now() - this.#created
|
|
84
84
|
|
|
85
85
|
this.#logger.debug(
|
|
@@ -94,7 +94,7 @@ class Handler extends DecoratorHandler {
|
|
|
94
94
|
'upstream request completed',
|
|
95
95
|
)
|
|
96
96
|
|
|
97
|
-
return this.#handler.onComplete(
|
|
97
|
+
return this.#handler.onComplete()
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
onError(err) {
|
package/lib/interceptor/proxy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import net from 'node:net'
|
|
2
2
|
import createError from 'http-errors'
|
|
3
|
-
import { DecoratorHandler } from '../utils.js'
|
|
3
|
+
import { DecoratorHandler, parseHeaders } from '../utils.js'
|
|
4
4
|
|
|
5
5
|
class Handler extends DecoratorHandler {
|
|
6
6
|
#handler
|
|
@@ -33,12 +33,12 @@ class Handler extends DecoratorHandler {
|
|
|
33
33
|
)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
onHeaders(statusCode, rawHeaders, resume, statusMessage) {
|
|
36
|
+
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
37
37
|
return this.#handler.onHeaders(
|
|
38
38
|
statusCode,
|
|
39
39
|
reduceHeaders(
|
|
40
40
|
{
|
|
41
|
-
headers
|
|
41
|
+
headers,
|
|
42
42
|
httpVersion: this.#opts.httpVersion ?? this.#opts.req?.httpVersion,
|
|
43
43
|
socket: this.#opts.socket,
|
|
44
44
|
proxyName: this.#opts.name,
|
|
@@ -61,18 +61,6 @@ class Handler extends DecoratorHandler {
|
|
|
61
61
|
const HOP_EXPR =
|
|
62
62
|
/^(te|host|upgrade|trailers|connection|keep-alive|http2-settings|transfer-encoding|proxy-connection|proxy-authenticate|proxy-authorization)$/i
|
|
63
63
|
|
|
64
|
-
function forEachHeader(headers, fn) {
|
|
65
|
-
if (Array.isArray(headers)) {
|
|
66
|
-
for (let n = 0; n < headers.length; n += 2) {
|
|
67
|
-
fn(headers[n + 0], headers[n + 1])
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
for (const [key, val] of Object.entries(headers)) {
|
|
71
|
-
fn(key, val)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
64
|
// Removes hop-by-hop and pseudo headers.
|
|
77
65
|
// Updates via and forwarded headers.
|
|
78
66
|
// Only hop-by-hop headers may be set using the Connection general header.
|
|
@@ -83,33 +71,31 @@ function reduceHeaders({ headers, proxyName, httpVersion, socket }, fn, acc) {
|
|
|
83
71
|
let authority = ''
|
|
84
72
|
let connection = ''
|
|
85
73
|
|
|
86
|
-
|
|
74
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
87
75
|
const len = key.length
|
|
88
|
-
if (len === 3 && !via && key
|
|
89
|
-
via = val
|
|
90
|
-
} else if (len === 4 && !host && key
|
|
91
|
-
host = val
|
|
92
|
-
} else if (len === 9 && !forwarded && key
|
|
93
|
-
forwarded = val
|
|
94
|
-
} else if (len === 10 && !connection && key
|
|
95
|
-
connection = val
|
|
96
|
-
} else if (len === 10 && !authority && key
|
|
97
|
-
authority = val
|
|
76
|
+
if (len === 3 && !via && key === 'via') {
|
|
77
|
+
via = val
|
|
78
|
+
} else if (len === 4 && !host && key === 'host') {
|
|
79
|
+
host = val
|
|
80
|
+
} else if (len === 9 && !forwarded && key === 'forwarded') {
|
|
81
|
+
forwarded = val
|
|
82
|
+
} else if (len === 10 && !connection && key === 'connection') {
|
|
83
|
+
connection = val
|
|
84
|
+
} else if (len === 10 && !authority && key === ':authority') {
|
|
85
|
+
authority = val
|
|
98
86
|
}
|
|
99
|
-
}
|
|
87
|
+
}
|
|
100
88
|
|
|
101
89
|
let remove = []
|
|
102
90
|
if (connection && !HOP_EXPR.test(connection)) {
|
|
103
91
|
remove = connection.split(/,\s*/)
|
|
104
92
|
}
|
|
105
93
|
|
|
106
|
-
|
|
107
|
-
key = key.toString()
|
|
108
|
-
|
|
94
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
109
95
|
if (key.charAt(0) !== ':' && !remove.includes(key) && !HOP_EXPR.test(key)) {
|
|
110
96
|
acc = fn(acc, key, val.toString())
|
|
111
97
|
}
|
|
112
|
-
}
|
|
98
|
+
}
|
|
113
99
|
|
|
114
100
|
if (socket) {
|
|
115
101
|
const forwardedHost = authority || host
|