@nxtedition/nxt-undici 4.2.26 → 5.0.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/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 wrapDispatcher(dispatcher) {
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 = dispatcher.compose(
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 wrapDispatcher(dispatcher).dispatch(opts, handler)
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
- // TODO (fix): More argument validation...
95
-
96
- if (typeof url === 'string') {
97
- opts = { url: new URL(url), ...opts }
98
- } else if (url instanceof URL) {
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
  }
@@ -1,16 +1,13 @@
1
- import assert from 'node:assert'
2
1
  import { LRUCache } from 'lru-cache'
3
- import { DecoratorHandler, parseHeaders, parseCacheControl } from '../utils.js'
2
+ import { parseHeaders, parseCacheControl } from '../utils.js'
4
3
 
5
- class CacheHandler extends DecoratorHandler {
4
+ class CacheHandler {
6
5
  #handler
7
6
  #store
8
7
  #key
9
- #value = null
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, rawHeaders, resume, statusMessage, headers)
24
+ return this.#handler.onHeaders(statusCode, null, resume, statusMessage, headers)
28
25
  }
29
26
 
30
27
  // TODO (fix): Support vary header.
@@ -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
- data: {
57
- statusCode,
58
- statusMessage,
59
- rawHeaders,
60
- rawTrailers: null,
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, rawHeaders, resume, statusMessage, headers)
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(rawTrailers) {
78
+ onComplete() {
90
79
  if (this.#value) {
91
- this.#value.data.rawTrailers = rawTrailers
92
- this.#value.size += rawTrailers?.reduce((xs, x) => xs + x.length, 0) ?? 0
93
- this.#store.set(this.#key, this.#value.data, { ttl: this.#value.ttl, size: this.#value.size })
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(rawTrailers)
91
+ return this.#handler.onComplete()
92
+ }
93
+
94
+ onError(err) {
95
+ this.#handler.onError(err)
96
96
  }
97
97
  }
98
98
 
99
- // TODO (fix): Async filesystem cache.
100
- class CacheStore {
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(key, value, opts)
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 CacheStore({ maxSize: 128 * 1024, maxEntrySize: 1024 })
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
- let key = makeKey(opts)
159
- let value = store.get(key)
163
+ const key = makeKey(opts)
164
+ const entry = store.get(key)
160
165
 
161
- if (value == null && opts.method === 'HEAD') {
162
- key = makeKey({ ...opts, method: 'GET' })
163
- value = store.get(key)
166
+ if (!entry) {
167
+ return dispatch(opts, new CacheHandler({ handler, store, key: makeKey(opts) }))
164
168
  }
165
169
 
166
- if (value) {
167
- const { statusCode, statusMessage, rawHeaders, rawTrailers, body } = value
168
- const ac = new AbortController()
169
- const signal = ac.signal
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
- const resume = () => {}
172
- const abort = () => {
173
- ac.abort()
178
+ try {
179
+ handler.onConnect(abort)
180
+ if (aborted) {
181
+ return true
174
182
  }
175
183
 
176
- try {
177
- handler.onConnect(abort)
178
- signal.throwIfAborted()
179
- handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
180
- signal.throwIfAborted()
181
- if (opts.method !== 'HEAD') {
182
- for (const chunk of body) {
183
- const ret = handler.onData(chunk)
184
- signal.throwIfAborted()
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
- return true
198
- } else {
199
- return dispatch(opts, new CacheHandler({ handler, store, key: makeKey(opts) }))
196
+ handler.onComplete()
197
+ } catch (err) {
198
+ handler.onError(err)
200
199
  }
200
+
201
+ return true
201
202
  }
@@ -5,7 +5,7 @@ class Handler extends DecoratorHandler {
5
5
  #opts
6
6
  #logger
7
7
 
8
- #abort = null
8
+ #abort
9
9
  #aborted = false
10
10
  #pos = 0
11
11
  #created = 0
@@ -66,7 +66,7 @@ class Handler extends DecoratorHandler {
66
66
  'upstream request response',
67
67
  )
68
68
 
69
- return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
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(rawTrailers) {
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(rawTrailers)
97
+ return this.#handler.onComplete()
98
98
  }
99
99
 
100
100
  onError(err) {
@@ -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: rawHeaders,
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
- forEachHeader(headers, (key, val) => {
74
+ for (const [key, val] of Object.entries(headers)) {
87
75
  const len = key.length
88
- if (len === 3 && !via && key.toString().toLowerCase() === 'via') {
89
- via = val.toString()
90
- } else if (len === 4 && !host && key.toString().toLowerCase() === 'host') {
91
- host = val.toString()
92
- } else if (len === 9 && !forwarded && key.toString().toLowerCase() === 'forwarded') {
93
- forwarded = val.toString()
94
- } else if (len === 10 && !connection && key.toString().toLowerCase() === 'connection') {
95
- connection = val.toString()
96
- } else if (len === 10 && !authority && key.toString().toLowerCase() === ':authority') {
97
- authority = val.toString()
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
- forEachHeader(headers, (key, val) => {
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
@@ -14,7 +14,7 @@ class Handler extends DecoratorHandler {
14
14
  #reason = null
15
15
  #headersSent = false
16
16
  #count = 0
17
- #location = null
17
+ #location = ''
18
18
  #history = []
19
19
 
20
20
  constructor(opts, { dispatch, handler }) {
@@ -23,7 +23,7 @@ class Handler extends DecoratorHandler {
23
23
  this.#dispatch = dispatch
24
24
  this.#handler = handler
25
25
  this.#opts = opts
26
- this.#maxCount = Number.isFinite(opts.follow) ? opts.follow : opts.follow?.count ?? 0
26
+ this.#maxCount = Number.isFinite(opts.follow) ? opts.follow : (opts.follow?.count ?? 0)
27
27
 
28
28
  this.#handler.onConnect((reason) => {
29
29
  this.#aborted = true
@@ -51,14 +51,14 @@ class Handler extends DecoratorHandler {
51
51
  if (redirectableStatusCodes.indexOf(statusCode) === -1) {
52
52
  assert(!this.#headersSent)
53
53
  this.#headersSent = true
54
- return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusText, headers)
54
+ return this.#handler.onHeaders(statusCode, null, resume, statusText, headers)
55
55
  }
56
56
 
57
57
  if (isDisturbed(this.#opts.body)) {
58
58
  throw new Error(`Disturbed request cannot be redirected.`)
59
59
  }
60
60
 
61
- this.#location = headers.location
61
+ this.#location = typeof headers.location === 'string' ? headers.location : ''
62
62
 
63
63
  if (!this.#location) {
64
64
  throw new Error(`Missing redirection location .`)
@@ -71,7 +71,7 @@ class Handler extends DecoratorHandler {
71
71
  if (!this.#opts.follow(this.#location, this.#count, this.#opts)) {
72
72
  assert(!this.#headersSent)
73
73
  this.#headersSent = true
74
- return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusText, headers)
74
+ return this.#handler.onHeaders(statusCode, null, resume, statusText, headers)
75
75
  }
76
76
  } else {
77
77
  if (this.#count >= this.#maxCount) {