Skip to content

Commit dcd44a5

Browse files
authored
fix: remove broken build request hack (#2874)
1 parent 53df078 commit dcd44a5

File tree

9 files changed

+79
-118
lines changed

9 files changed

+79
-118
lines changed

lib/core/request.js

Lines changed: 36 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const {
55
NotSupportedError
66
} = require('./errors')
77
const assert = require('node:assert')
8-
const { kHTTP2BuildRequest, kHTTP2CopyHeaders, kHTTP1BuildRequest } = require('./symbols')
98
const util = require('./util')
109
const { channels } = require('./diagnostics.js')
1110
const { headerNameLowerCasedRecord } = require('./constants')
@@ -149,7 +148,7 @@ class Request {
149148

150149
this.contentType = null
151150

152-
this.headers = ''
151+
this.headers = []
153152

154153
// Only for H2
155154
this.expectContinue = expectContinue != null ? expectContinue : false
@@ -310,78 +309,10 @@ class Request {
310309
}
311310
}
312311

313-
// TODO: adjust to support H2
314312
addHeader (key, value) {
315313
processHeader(this, key, value)
316314
return this
317315
}
318-
319-
static [kHTTP1BuildRequest] (origin, opts, handler) {
320-
// TODO: Migrate header parsing here, to make Requests
321-
// HTTP agnostic
322-
return new Request(origin, opts, handler)
323-
}
324-
325-
static [kHTTP2BuildRequest] (origin, opts, handler) {
326-
const headers = opts.headers
327-
opts = { ...opts, headers: null }
328-
329-
const request = new Request(origin, opts, handler)
330-
331-
request.headers = {}
332-
333-
if (Array.isArray(headers)) {
334-
if (headers.length % 2 !== 0) {
335-
throw new InvalidArgumentError('headers array must be even')
336-
}
337-
for (let i = 0; i < headers.length; i += 2) {
338-
processHeader(request, headers[i], headers[i + 1], true)
339-
}
340-
} else if (headers && typeof headers === 'object') {
341-
const keys = Object.keys(headers)
342-
for (let i = 0; i < keys.length; i++) {
343-
const key = keys[i]
344-
processHeader(request, key, headers[key], true)
345-
}
346-
} else if (headers != null) {
347-
throw new InvalidArgumentError('headers must be an object or an array')
348-
}
349-
350-
return request
351-
}
352-
353-
static [kHTTP2CopyHeaders] (raw) {
354-
const rawHeaders = raw.split('\r\n')
355-
const headers = {}
356-
357-
for (const header of rawHeaders) {
358-
const [key, value] = header.split(': ')
359-
360-
if (value == null || value.length === 0) continue
361-
362-
if (headers[key]) {
363-
headers[key] += `,${value}`
364-
} else {
365-
headers[key] = value
366-
}
367-
}
368-
369-
return headers
370-
}
371-
}
372-
373-
function processHeaderValue (key, val, skipAppend) {
374-
if (val && typeof val === 'object') {
375-
throw new InvalidArgumentError(`invalid ${key} header`)
376-
}
377-
378-
val = val != null ? `${val}` : ''
379-
380-
if (headerCharRegex.exec(val) !== null) {
381-
throw new InvalidArgumentError(`invalid ${key} header`)
382-
}
383-
384-
return skipAppend ? val : `${key}: ${val}\r\n`
385316
}
386317

387318
function processHeader (request, key, val, skipAppend = false) {
@@ -400,10 +331,39 @@ function processHeader (request, key, val, skipAppend = false) {
400331
}
401332
}
402333

403-
if (request.host === null && headerName === 'host') {
334+
if (Array.isArray(val)) {
335+
const arr = []
336+
for (let i = 0; i < val.length; i++) {
337+
if (typeof val[i] === 'string') {
338+
if (headerCharRegex.exec(val[i]) !== null) {
339+
throw new InvalidArgumentError(`invalid ${key} header`)
340+
}
341+
arr.push(val[i])
342+
} else if (val[i] === null) {
343+
arr.push('')
344+
} else if (typeof val[i] === 'object') {
345+
throw new InvalidArgumentError(`invalid ${key} header`)
346+
} else {
347+
arr.push(`${val[i]}`)
348+
}
349+
}
350+
val = arr
351+
} else if (typeof val === 'string') {
404352
if (headerCharRegex.exec(val) !== null) {
405353
throw new InvalidArgumentError(`invalid ${key} header`)
406354
}
355+
} else if (val === null) {
356+
val = ''
357+
} else if (typeof val === 'object') {
358+
throw new InvalidArgumentError(`invalid ${key} header`)
359+
} else {
360+
val = `${val}`
361+
}
362+
363+
if (request.host === null && headerName === 'host') {
364+
if (typeof val !== 'string') {
365+
throw new InvalidArgumentError('invalid host header')
366+
}
407367
// Consumed by Client
408368
request.host = val
409369
} else if (request.contentLength === null && headerName === 'content-length') {
@@ -413,35 +373,22 @@ function processHeader (request, key, val, skipAppend = false) {
413373
}
414374
} else if (request.contentType === null && headerName === 'content-type') {
415375
request.contentType = val
416-
if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend)
417-
else request.headers += processHeaderValue(key, val)
376+
request.headers.push(key, val)
418377
} else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') {
419378
throw new InvalidArgumentError(`invalid ${headerName} header`)
420379
} else if (headerName === 'connection') {
421380
const value = typeof val === 'string' ? val.toLowerCase() : null
422381
if (value !== 'close' && value !== 'keep-alive') {
423382
throw new InvalidArgumentError('invalid connection header')
424-
} else if (value === 'close') {
383+
}
384+
385+
if (value === 'close') {
425386
request.reset = true
426387
}
427388
} else if (headerName === 'expect') {
428389
throw new NotSupportedError('expect header not supported')
429-
} else if (Array.isArray(val)) {
430-
for (let i = 0; i < val.length; i++) {
431-
if (skipAppend) {
432-
if (request.headers[key]) {
433-
request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}`
434-
} else {
435-
request.headers[key] = processHeaderValue(key, val[i], skipAppend)
436-
}
437-
} else {
438-
request.headers += processHeaderValue(key, val[i])
439-
}
440-
}
441-
} else if (skipAppend) {
442-
request.headers[key] = processHeaderValue(key, val, skipAppend)
443390
} else {
444-
request.headers += processHeaderValue(key, val)
391+
request.headers.push(key, val)
445392
}
446393
}
447394

lib/core/symbols.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ module.exports = {
5656
kMaxResponseSize: Symbol('max response size'),
5757
kHTTP2Session: Symbol('http2Session'),
5858
kHTTP2SessionState: Symbol('http2Session state'),
59-
kHTTP2BuildRequest: Symbol('http2 build request'),
60-
kHTTP1BuildRequest: Symbol('http1 build request'),
61-
kHTTP2CopyHeaders: Symbol('http2 copy headers'),
6259
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
6360
kConstruct: Symbol('constructable'),
6461
kListeners: Symbol('listeners'),

lib/core/util.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ kEnumerableProperty.enumerable = true
516516
module.exports = {
517517
kEnumerableProperty,
518518
nop,
519+
519520
isDisturbed,
520521
isErrored,
521522
isReadable,

lib/dispatcher/client-h1.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -817,12 +817,12 @@ function writeH1 (client, request) {
817817

818818
const [bodyStream, contentType] = extractBody(body)
819819
if (request.contentType == null) {
820-
headers += `content-type: ${contentType}\r\n`
820+
headers.push('content-type', contentType)
821821
}
822822
body = bodyStream.stream
823823
contentLength = bodyStream.length
824824
} else if (util.isBlobLike(body) && request.contentType == null && body.type) {
825-
headers += `content-type: ${body.type}\r\n`
825+
headers.push('content-type', body.type)
826826
}
827827

828828
if (body && typeof body.read === 'function') {
@@ -922,8 +922,19 @@ function writeH1 (client, request) {
922922
header += 'connection: close\r\n'
923923
}
924924

925-
if (headers) {
926-
header += headers
925+
if (Array.isArray(headers)) {
926+
for (let n = 0; n < headers.length; n += 2) {
927+
const key = headers[n + 0]
928+
const val = headers[n + 1]
929+
930+
if (Array.isArray(val)) {
931+
for (let i = 0; i < val.length; i++) {
932+
header += `${key}: ${val[i]}\r\n`
933+
}
934+
} else {
935+
header += `${key}: ${val}\r\n`
936+
}
937+
}
927938
}
928939

929940
if (channels.sendHeaders.hasSubscribers) {

lib/dispatcher/client-h2.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
const assert = require('node:assert')
44
const { pipeline } = require('node:stream')
55
const util = require('../core/util.js')
6-
const Request = require('../core/request.js')
76
const {
87
RequestContentLengthMismatchError,
98
RequestAbortedError,
@@ -26,7 +25,6 @@ const {
2625
// HTTP2
2726
kMaxConcurrentStreams,
2827
kHTTP2Session,
29-
kHTTP2CopyHeaders,
3028
kResume
3129
} = require('../core/symbols.js')
3230

@@ -215,10 +213,6 @@ function writeH2 (client, request) {
215213
const session = client[kHTTP2Session]
216214
const { body, method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request
217215

218-
let headers
219-
if (typeof reqHeaders === 'string') headers = Request[kHTTP2CopyHeaders](reqHeaders.trim())
220-
else headers = reqHeaders
221-
222216
if (upgrade) {
223217
errorRequest(client, request, new Error('Upgrade not supported for H2'))
224218
return false
@@ -228,6 +222,24 @@ function writeH2 (client, request) {
228222
return false
229223
}
230224

225+
const headers = {}
226+
for (let n = 0; n < reqHeaders.length; n += 2) {
227+
const key = reqHeaders[n + 0]
228+
const val = reqHeaders[n + 1]
229+
230+
if (Array.isArray(val)) {
231+
for (let i = 0; i < val.length; i++) {
232+
if (headers[key]) {
233+
headers[key] += `,${val[i]}`
234+
} else {
235+
headers[key] = val[i]
236+
}
237+
}
238+
} else {
239+
headers[key] = val
240+
}
241+
}
242+
231243
/** @type {import('node:http2').ClientHttp2Stream} */
232244
let stream
233245

lib/dispatcher/client.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ const {
6060
kHTTPContext,
6161
// HTTP2
6262
kMaxConcurrentStreams,
63-
kHTTP2BuildRequest,
64-
kHTTP1BuildRequest,
6563
kResume
6664
} = require('../core/symbols.js')
6765
const connectH1 = require('./client-h1.js')
@@ -296,12 +294,7 @@ class Client extends DispatcherBase {
296294

297295
[kDispatch] (opts, handler) {
298296
const origin = opts.origin || this[kUrl].origin
299-
300-
// TODO (fix): Why do these need to be
301-
// TODO (fix): This can happen before connect...
302-
const request = this[kHTTPContext]?.version === 'h2'
303-
? Request[kHTTP2BuildRequest](origin, opts, handler)
304-
: Request[kHTTP1BuildRequest](origin, opts, handler)
297+
const request = new Request(origin, opts, handler)
305298

306299
this[kQueue].push(request)
307300
if (this[kResuming]) {

test/node-test/diagnostics-channel/get.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ test('Diagnostics channel - get', (t) => {
3232
assert.equal(request.completed, false)
3333
assert.equal(request.method, 'GET')
3434
assert.equal(request.path, '/')
35-
assert.equal(request.headers, 'bar: bar\r\n')
35+
assert.deepStrictEqual(request.headers, ['bar', 'bar'])
3636
request.addHeader('hello', 'world')
37-
assert.equal(request.headers, 'bar: bar\r\nhello: world\r\n')
37+
assert.deepStrictEqual(request.headers, ['bar', 'bar', 'hello', 'world'])
3838
})
3939

4040
let _connector
@@ -81,7 +81,7 @@ test('Diagnostics channel - get', (t) => {
8181
'hello: world'
8282
]
8383

84-
assert.equal(headers, expectedHeaders.join('\r\n') + '\r\n')
84+
assert.deepStrictEqual(headers, expectedHeaders.join('\r\n') + '\r\n')
8585
})
8686

8787
diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => {

test/node-test/diagnostics-channel/post-stream.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ test('Diagnostics channel - post stream', (t) => {
3333
assert.equal(request.completed, false)
3434
assert.equal(request.method, 'POST')
3535
assert.equal(request.path, '/')
36-
assert.equal(request.headers, 'bar: bar\r\n')
36+
assert.deepStrictEqual(request.headers, ['bar', 'bar'])
3737
request.addHeader('hello', 'world')
38-
assert.equal(request.headers, 'bar: bar\r\nhello: world\r\n')
38+
assert.deepStrictEqual(request.headers, ['bar', 'bar', 'hello', 'world'])
3939
assert.deepStrictEqual(request.body, body)
4040
})
4141

test/node-test/diagnostics-channel/post.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ test('Diagnostics channel - post', (t) => {
3131
assert.equal(request.completed, false)
3232
assert.equal(request.method, 'POST')
3333
assert.equal(request.path, '/')
34-
assert.equal(request.headers, 'bar: bar\r\n')
34+
assert.deepStrictEqual(request.headers, ['bar', 'bar'])
3535
request.addHeader('hello', 'world')
36-
assert.equal(request.headers, 'bar: bar\r\nhello: world\r\n')
36+
assert.deepStrictEqual(request.headers, ['bar', 'bar', 'hello', 'world'])
3737
assert.deepStrictEqual(request.body, Buffer.from('hello world'))
3838
})
3939

@@ -81,7 +81,7 @@ test('Diagnostics channel - post', (t) => {
8181
'hello: world'
8282
]
8383

84-
assert.equal(headers, expectedHeaders.join('\r\n') + '\r\n')
84+
assert.deepStrictEqual(headers, expectedHeaders.join('\r\n') + '\r\n')
8585
})
8686

8787
diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => {

0 commit comments

Comments
 (0)