Skip to content

Commit c2f0068

Browse files
Add support for passing iterable objects as headers (#2708)
* Update tests with iterable object cases * Add support for iterable object headers * Update tests with cases of malformed headers * Add check for malformed headers * Update lib/core/request.js Co-authored-by: Mert Can Altın <[email protected]> * Update lib/core/request.js Co-authored-by: Mert Can Altın <[email protected]> * Fix code after unverified improvement broke functionality --------- Co-authored-by: Mert Can Altın <[email protected]>
1 parent c4adfdd commit c2f0068

File tree

2 files changed

+147
-5
lines changed

2 files changed

+147
-5
lines changed

lib/core/request.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,19 @@ class Request {
164164
processHeader(this, headers[i], headers[i + 1])
165165
}
166166
} else if (headers && typeof headers === 'object') {
167-
const keys = Object.keys(headers)
168-
for (let i = 0; i < keys.length; i++) {
169-
const key = keys[i]
170-
processHeader(this, key, headers[key])
167+
if (headers[Symbol.iterator]) {
168+
for (const header of headers) {
169+
if (!Array.isArray(header) || header.length !== 2) {
170+
throw new InvalidArgumentError('headers must be in key-value pair format')
171+
}
172+
const [key, value] = header
173+
processHeader(this, key, value)
174+
}
175+
} else {
176+
const keys = Object.keys(headers)
177+
for (const key of keys) {
178+
processHeader(this, key, headers[key])
179+
}
171180
}
172181
} else if (headers != null) {
173182
throw new InvalidArgumentError('headers must be an object or an array')

test/request.js

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const { createServer } = require('node:http')
44
const { test } = require('tap')
5-
const { request } = require('..')
5+
const { request, errors } = require('..')
66

77
test('no-slash/one-slash pathname should be included in req.path', async (t) => {
88
const pathServer = createServer((req, res) => {
@@ -246,3 +246,136 @@ test('DispatchOptions#reset', scope => {
246246
})
247247
})
248248
})
249+
250+
test('Should include headers from iterable objects', scope => {
251+
scope.plan(4)
252+
253+
scope.test('Should include headers built with Headers global object', async t => {
254+
const server = createServer((req, res) => {
255+
t.equal('GET', req.method)
256+
t.equal(`localhost:${server.address().port}`, req.headers.host)
257+
t.equal(req.headers.hello, 'world')
258+
res.statusCode = 200
259+
res.end('hello')
260+
})
261+
262+
const headers = new Headers()
263+
headers.set('hello', 'world')
264+
265+
t.plan(3)
266+
267+
t.teardown(server.close.bind(server))
268+
269+
await new Promise((resolve, reject) => {
270+
server.listen(0, (err) => {
271+
if (err != null) reject(err)
272+
else resolve()
273+
})
274+
})
275+
276+
await request({
277+
method: 'GET',
278+
origin: `http://localhost:${server.address().port}`,
279+
reset: true,
280+
headers
281+
})
282+
})
283+
284+
scope.test('Should include headers built with Map', async t => {
285+
const server = createServer((req, res) => {
286+
t.equal('GET', req.method)
287+
t.equal(`localhost:${server.address().port}`, req.headers.host)
288+
t.equal(req.headers.hello, 'world')
289+
res.statusCode = 200
290+
res.end('hello')
291+
})
292+
293+
const headers = new Map()
294+
headers.set('hello', 'world')
295+
296+
t.plan(3)
297+
298+
t.teardown(server.close.bind(server))
299+
300+
await new Promise((resolve, reject) => {
301+
server.listen(0, (err) => {
302+
if (err != null) reject(err)
303+
else resolve()
304+
})
305+
})
306+
307+
await request({
308+
method: 'GET',
309+
origin: `http://localhost:${server.address().port}`,
310+
reset: true,
311+
headers
312+
})
313+
})
314+
315+
scope.test('Should include headers built with custom iterable object', async t => {
316+
const server = createServer((req, res) => {
317+
t.equal('GET', req.method)
318+
t.equal(`localhost:${server.address().port}`, req.headers.host)
319+
t.equal(req.headers.hello, 'world')
320+
res.statusCode = 200
321+
res.end('hello')
322+
})
323+
324+
const headers = {
325+
* [Symbol.iterator] () {
326+
yield ['hello', 'world']
327+
}
328+
}
329+
330+
t.plan(3)
331+
332+
t.teardown(server.close.bind(server))
333+
334+
await new Promise((resolve, reject) => {
335+
server.listen(0, (err) => {
336+
if (err != null) reject(err)
337+
else resolve()
338+
})
339+
})
340+
341+
await request({
342+
method: 'GET',
343+
origin: `http://localhost:${server.address().port}`,
344+
reset: true,
345+
headers
346+
})
347+
})
348+
349+
scope.test('Should throw error if headers iterable object does not yield key-value pairs', async t => {
350+
const server = createServer((req, res) => {
351+
res.end('hello')
352+
})
353+
354+
const headers = {
355+
* [Symbol.iterator] () {
356+
yield 'Bad formatted header'
357+
}
358+
}
359+
360+
t.plan(2)
361+
362+
t.teardown(server.close.bind(server))
363+
364+
await new Promise((resolve, reject) => {
365+
server.listen(0, (err) => {
366+
if (err != null) reject(err)
367+
else resolve()
368+
})
369+
})
370+
371+
await request({
372+
method: 'GET',
373+
origin: `http://localhost:${server.address().port}`,
374+
reset: true,
375+
headers
376+
}).catch((err) => {
377+
t.type(err, errors.InvalidArgumentError)
378+
t.equal(err.message, 'headers must be in key-value pair format')
379+
})
380+
})
381+
})

0 commit comments

Comments
 (0)