@fastify/reply-from
Version:
forward your HTTP request to another server, for fastify
309 lines (241 loc) • 9.58 kB
JavaScript
const t = require('node:test')
const Fastify = require('fastify')
const From = require('..')
const http = require('node:http')
// RFC 7230 Section 6.1 - Connection header handling
// A proxy MUST parse the Connection header and remove any headers listed within it
// Helper to make HTTP request with Connection header (undici doesn't allow this)
function makeRequest (port, headers) {
return new Promise((resolve, reject) => {
const req = http.request({
method: 'GET',
hostname: 'localhost',
port,
path: '/',
headers
}, (res) => {
let data = ''
res.on('data', (chunk) => { data += chunk })
res.on('end', () => resolve({ statusCode: res.statusCode, body: data }))
})
req.on('error', reject)
req.end()
})
}
t.test('strips headers listed in Connection header (undici)', async (t) => {
t.plan(4)
const instance = Fastify()
instance.register(From)
t.after(() => instance.close())
const target = http.createServer((req, res) => {
t.assert.ok('request proxied')
t.assert.strictEqual(req.headers['x-custom-header'], undefined, 'X-Custom-Header should be stripped')
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('ok')
})
instance.get('/', (_request, reply) => {
reply.from(`http://localhost:${target.address().port}`)
})
t.after(() => target.close())
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
await new Promise((resolve) => target.listen({ port: 0 }, resolve))
const result = await makeRequest(instance.server.address().port, {
'X-Custom-Header': 'some-value',
Connection: 'X-Custom-Header'
})
t.assert.strictEqual(result.statusCode, 200)
t.assert.strictEqual(result.body, 'ok')
})
t.test('strips multiple headers listed in Connection header (undici)', async (t) => {
t.plan(5)
const instance = Fastify()
instance.register(From)
t.after(() => instance.close())
const target = http.createServer((req, res) => {
t.assert.ok('request proxied')
t.assert.strictEqual(req.headers['x-custom-one'], undefined, 'X-Custom-One should be stripped')
t.assert.strictEqual(req.headers['x-custom-two'], undefined, 'X-Custom-Two should be stripped')
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('ok')
})
instance.get('/', (_request, reply) => {
reply.from(`http://localhost:${target.address().port}`)
})
t.after(() => target.close())
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
await new Promise((resolve) => target.listen({ port: 0 }, resolve))
const result = await makeRequest(instance.server.address().port, {
'X-Custom-One': 'value1',
'X-Custom-Two': 'value2',
Connection: 'X-Custom-One, X-Custom-Two'
})
t.assert.strictEqual(result.statusCode, 200)
t.assert.strictEqual(result.body, 'ok')
})
t.test('preserves headers not listed in Connection header (undici)', async (t) => {
t.plan(5)
const instance = Fastify()
instance.register(From)
t.after(() => instance.close())
const target = http.createServer((req, res) => {
t.assert.ok('request proxied')
t.assert.strictEqual(req.headers['x-keep-header'], 'keep-me', 'X-Keep-Header should be preserved')
t.assert.strictEqual(req.headers['x-strip-header'], undefined, 'X-Strip-Header should be stripped')
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('ok')
})
instance.get('/', (_request, reply) => {
reply.from(`http://localhost:${target.address().port}`)
})
t.after(() => target.close())
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
await new Promise((resolve) => target.listen({ port: 0 }, resolve))
const result = await makeRequest(instance.server.address().port, {
'X-Keep-Header': 'keep-me',
'X-Strip-Header': 'strip-me',
Connection: 'X-Strip-Header'
})
t.assert.strictEqual(result.statusCode, 200)
t.assert.strictEqual(result.body, 'ok')
})
t.test('strips headers listed in Connection header (http)', async (t) => {
t.plan(4)
const instance = Fastify()
instance.register(From, { undici: false })
t.after(() => instance.close())
const target = http.createServer((req, res) => {
t.assert.ok('request proxied')
t.assert.strictEqual(req.headers['x-custom-header'], undefined, 'X-Custom-Header should be stripped')
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('ok')
})
instance.get('/', (_request, reply) => {
reply.from(`http://localhost:${target.address().port}`)
})
t.after(() => target.close())
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
await new Promise((resolve) => target.listen({ port: 0 }, resolve))
const result = await makeRequest(instance.server.address().port, {
'X-Custom-Header': 'some-value',
Connection: 'X-Custom-Header'
})
t.assert.strictEqual(result.statusCode, 200)
t.assert.strictEqual(result.body, 'ok')
})
t.test('strips multiple headers listed in Connection header (http)', async (t) => {
t.plan(5)
const instance = Fastify()
instance.register(From, { undici: false })
t.after(() => instance.close())
const target = http.createServer((req, res) => {
t.assert.ok('request proxied')
t.assert.strictEqual(req.headers['x-custom-one'], undefined, 'X-Custom-One should be stripped')
t.assert.strictEqual(req.headers['x-custom-two'], undefined, 'X-Custom-Two should be stripped')
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('ok')
})
instance.get('/', (_request, reply) => {
reply.from(`http://localhost:${target.address().port}`)
})
t.after(() => target.close())
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
await new Promise((resolve) => target.listen({ port: 0 }, resolve))
const result = await makeRequest(instance.server.address().port, {
'X-Custom-One': 'value1',
'X-Custom-Two': 'value2',
Connection: 'X-Custom-One, X-Custom-Two'
})
t.assert.strictEqual(result.statusCode, 200)
t.assert.strictEqual(result.body, 'ok')
})
t.test('handles Connection header with keep-alive and custom headers (undici)', async (t) => {
t.plan(4)
const instance = Fastify()
instance.register(From)
t.after(() => instance.close())
const target = http.createServer((req, res) => {
t.assert.ok('request proxied')
t.assert.strictEqual(req.headers['x-custom-header'], undefined, 'X-Custom-Header should be stripped')
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('ok')
})
instance.get('/', (_request, reply) => {
reply.from(`http://localhost:${target.address().port}`)
})
t.after(() => target.close())
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
await new Promise((resolve) => target.listen({ port: 0 }, resolve))
const result = await makeRequest(instance.server.address().port, {
'X-Custom-Header': 'some-value',
Connection: 'keep-alive, X-Custom-Header'
})
t.assert.strictEqual(result.statusCode, 200)
t.assert.strictEqual(result.body, 'ok')
})
t.test('does not strip headers added by rewriteRequestHeaders (undici)', async (t) => {
t.plan(4)
const instance = Fastify()
instance.register(From)
t.after(() => instance.close())
let seenForwardedBy
const target = http.createServer((req, res) => {
seenForwardedBy = req.headers['x-forwarded-by']
t.assert.ok('request proxied')
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('ok')
})
instance.get('/', (_request, reply) => {
reply.from(`http://localhost:${target.address().port}`, {
rewriteRequestHeaders: (_request, headers) => {
return { ...headers, 'x-forwarded-by': 'fastify-proxy' }
}
})
})
t.after(() => target.close())
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
await new Promise((resolve) => target.listen({ port: 0 }, resolve))
const result = await makeRequest(instance.server.address().port, {
Connection: 'X-Forwarded-By'
})
t.assert.strictEqual(result.statusCode, 200)
t.assert.strictEqual(result.body, 'ok')
t.assert.strictEqual(seenForwardedBy, 'fastify-proxy', 'X-Forwarded-By should not be stripped')
})
t.test('does not strip headers added by rewriteRequestHeaders (http)', async (t) => {
t.plan(4)
const instance = Fastify()
instance.register(From, { undici: false })
t.after(() => instance.close())
let seenForwardedBy
const target = http.createServer((req, res) => {
seenForwardedBy = req.headers['x-forwarded-by']
t.assert.ok('request proxied')
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('ok')
})
instance.get('/', (_request, reply) => {
reply.from(`http://localhost:${target.address().port}`, {
rewriteRequestHeaders: (_request, headers) => {
return { ...headers, 'x-forwarded-by': 'fastify-proxy' }
}
})
})
t.after(() => target.close())
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
await new Promise((resolve) => target.listen({ port: 0 }, resolve))
const result = await makeRequest(instance.server.address().port, {
Connection: 'X-Forwarded-By'
})
t.assert.strictEqual(result.statusCode, 200)
t.assert.strictEqual(result.body, 'ok')
t.assert.strictEqual(seenForwardedBy, 'fastify-proxy', 'X-Forwarded-By should not be stripped')
})