Skip to content

Commit 09cb8e7

Browse files
climba03003SimenB
andauthored
fix: undici headers (#289)
* fix: undici headers * test: fix multiple listen * test: provide undici option * test: use keepAliveMaxTimeout 10 * chore: update comment Co-authored-by: Simen Bekkhus <[email protected]> --------- Co-authored-by: Simen Bekkhus <[email protected]>
1 parent b30a4fe commit 09cb8e7

3 files changed

Lines changed: 85 additions & 2 deletions

File tree

lib/request.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const querystring = require('querystring')
55
const eos = require('end-of-stream')
66
const pump = require('pump')
77
const undici = require('undici')
8-
const { stripHttp1ConnectionHeaders } = require('./utils')
8+
const { patchUndiciHeaders, stripHttp1ConnectionHeaders } = require('./utils')
99
const http2 = require('http2')
1010

1111
const {
@@ -158,7 +158,7 @@ function buildRequest (opts) {
158158
// using delete, otherwise it will render as an empty string
159159
delete res.headers['transfer-encoding']
160160

161-
done(null, { statusCode: res.statusCode, headers: res.headers, stream: res.body })
161+
done(null, { statusCode: res.statusCode, headers: patchUndiciHeaders(res.headers), stream: res.body })
162162
})
163163
}
164164

lib/utils.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,31 @@ function filterPseudoHeaders (headers) {
1212
return dest
1313
}
1414

15+
// http requires the header to be encoded with latin1
16+
// undici will convert the latin1 buffer to utf8
17+
// https://github.com/nodejs/undici/blob/2b260c997ad4efe4ed2064b264b4b546a59e7a67/lib/core/util.js#L216-L229
18+
// after chaining, the header will be serialised using wrong encoding
19+
// Buffer.from('', 'latin1').toString('utf8') applied
20+
//
21+
// in order to persist the encoding, always encode it
22+
// back to latin1
23+
function patchUndiciHeaders (headers) {
24+
const headersKeys = Object.keys(headers)
25+
const dist = {}
26+
27+
let header
28+
let i
29+
30+
for (i = 0; i < headersKeys.length; i++) {
31+
header = headersKeys[i]
32+
if (header.charCodeAt(0) !== 58) { // fast path for indexOf(':') === 0
33+
dist[header] = Buffer.from(headers[header]).toString('latin1')
34+
}
35+
}
36+
37+
return dist
38+
}
39+
1540
function copyHeaders (headers, reply) {
1641
const headersKeys = Object.keys(headers)
1742

@@ -74,6 +99,7 @@ function buildURL (source, reqBase) {
7499
}
75100

76101
module.exports = {
102+
patchUndiciHeaders,
77103
copyHeaders,
78104
stripHttp1ConnectionHeaders,
79105
filterPseudoHeaders,

test/undici-chaining.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict'
2+
3+
const t = require('tap')
4+
const Fastify = require('fastify')
5+
const get = require('simple-get').concat
6+
const From = require('..')
7+
8+
const header = 'attachment; filename="år.pdf"'
9+
10+
t.plan(6)
11+
12+
const instance = Fastify()
13+
t.teardown(instance.close.bind(instance))
14+
const proxy1 = Fastify()
15+
t.teardown(proxy1.close.bind(proxy1))
16+
const proxy2 = Fastify()
17+
t.teardown(proxy2.close.bind(proxy2))
18+
19+
instance.get('/', (request, reply) => {
20+
reply.header('content-disposition', header).send('OK')
21+
})
22+
23+
proxy1.register(From, {
24+
undici: {
25+
keepAliveMaxTimeout: 10
26+
}
27+
})
28+
proxy1.get('/', (request, reply) => {
29+
return reply.from(`http://localhost:${instance.server.address().port}`)
30+
})
31+
32+
proxy2.register(From, {
33+
undici: {
34+
keepAliveMaxTimeout: 10
35+
}
36+
})
37+
proxy2.get('/', (request, reply) => {
38+
return reply.from(`http://localhost:${proxy1.server.address().port}`)
39+
})
40+
41+
instance.listen({ port: 0 }, err => {
42+
t.error(err)
43+
44+
proxy1.listen({ port: 0 }, err => {
45+
t.error(err)
46+
47+
proxy2.listen({ port: 0 }, err => {
48+
t.error(err)
49+
50+
get(`http://localhost:${proxy2.server.address().port}`, (err, res, data) => {
51+
t.error(err)
52+
t.equal(res.statusCode, 200)
53+
t.equal(data.toString(), 'OK')
54+
})
55+
})
56+
})
57+
})

0 commit comments

Comments
 (0)