node-http2-proxy
A simple http/2 & http/1.1 to http/1.1 spec compliant proxy helper for Node.
Features
Installation
$ npm install http2-proxy
Notes
http2-proxy
requires at least node v10.0.0.
Fully async/await compatible and all callback based usage is optional and discouraged.
During 503 it is safe to assume that the request never made it to the upstream server. This makes it safe to retry non idempotent methods.
Use a final and/or error handler since errored responses won't be cleaned up automatically. This makes it possible to perform retries.
const finalhandler = require('finalhandler')
const defaultWebHandler = (err, req, res) => {
if (err) {
console.error('proxy error', err)
finalhandler(req, res)(err)
}
}
const defaultWSHandler = (err, req, socket, head) => {
if (err) {
console.error('proxy error', err)
socket.destroy()
}
}
Node
These are some existing issues in NodeJS to keep in mind when writing proxy code.
And some pending PR's:
Some of these are further referenced in the examples.
HTTP/1 API
You must pass allowHTTP1: true
to the http2.createServer
or http2.createSecureServer
factory methods.
import http2 from 'http2'
import proxy from 'http2-proxy'
const server = http2.createServer({ allowHTTP1: true })
server.listen(8000)
You can also use http-proxy2
with the old http
&& https
API's.
import http from 'http'
const server = http.createServer()
server.listen(8000)
API
Proxy HTTP/2, HTTP/1 and WebSocket
server.on('request', (req, res) => {
proxy.web(req, res, {
hostname: 'localhost'
port: 9000
}, defaultWebHandler)
})
server.on('upgrade', (req, socket, head) => {
proxy.ws(req, socket, head, {
hostname: 'localhost'
port: 9000
}, defaultWsHandler)
})
const app = connect()
app.use(helmet())
app.use((req, res, next) => proxy
.web(req, res, {
hostname: 'localhost'
port: 9000
}, err => {
if (err) {
next(err)
}
})
)
server.on('request', app)
server.on('request', (req, res) => {
proxy.web(req, res, {
hostname: 'localhost'
port: 9000,
onReq: (req, { headers }) => {
headers['x-forwarded-for'] = req.socket.remoteAddress
headers['x-forwarded-proto'] = req.socket.encrypted ? 'https' : 'http'
headers['x-forwarded-host'] = req.headers['host']
}
}, defaultWebHandler)
})
Follow Redirects
const http = require('follow-redirects').http
server.on('request', (req, res) => {
proxy.web(req, res, {
hostname: 'localhost'
port: 9000,
onReq: (req, options) => http.request(options)
}, defaultWebHandler)
})
server.on('request', (req, res) => {
proxy.web(req, res, {
hostname: 'localhost'
port: 9000,
onReq: (req, options) => http.request(options),
onRes: (req, res, proxyRes) => {
res.setHeader('x-powered-by', 'http2-proxy')
res.writeHead(proxyRes.statusCode, proxyRes.headers)
proxyRes.pipe(res)
}
}, defaultWebHandler)
})
Try Multiple Upstream Servers (Advanced)
const http = require('http')
const proxy = require('http2-proxy')
const createError = require('http-errors')
server.on('request', async (req, res) => {
try {
res.statusCode = null
for await (const { port, timeout, hostname } of upstream) {
if (req.aborted) {
return
}
let bytesWritten = 0
try {
return await proxy.web(req, res, {
port,
timeout,
hostname,
onRes: async (req, res, proxyRes) => {
if (proxyRes.statusCode >= 500) {
throw createError(proxyRes.statusCode, proxyRes.message)
}
if (!res.statusCode) {
res.statusCode = proxyRes.statusCode
for (const [ key, value ] of Object.entries(headers)) {
res.setHeader(key, value)
}
}
function onClose () {
res.off('drain', onDrain)
}
function onDrain () {
proxyRes.resume()
}
proxyRes
.on('data', buf => {
bytesWritten += buf.length
if (!res.write(buf)) {
proxyRes.pause()
}
})
.on('end', () => {
if (proxyRes.aborted) {
return
}
res.end()
res.finished = true
})
.on('close', onClose)
res.on('drain', onDrain)
}
})
} catch (err) {
if (res.finished) {
throw err
}
if (err.statusCode === 503) {
continue
}
if (req.method === 'HEAD' || req.method === 'GET') {
if (bytesWritten === 0) {
continue
} else {
}
}
throw err
}
}
throw new createError.ServiceUnavailable()
} catch (err) {
defaultWebHandler(err)
}
}
[async] web (req, res, options[, callback])
See request
[async] ws (req, socket, head, options[, callback])
See upgrade
options
hostname
: Proxy http.request(options)
target hostname.port
: Proxy http.request(options)
target port.protocol
: Agent protocol ('http'
or 'https'
). Defaults to 'http'
.path
: Target pathname. Defaults to req.originalUrl || req.url
.proxyTimeout
: Proxy http.request(options)
timeout.proxyName
: Proxy name used for Via header.timeout
: http.IncomingMessage
or http2.Http2ServerRequest
timeout.[async] onReq(req, options[, callback])
: Called before proxy request. If returning a truthy value it will be used as the request.
[async] onRes(req, resOrSocket, proxyRes[, callback])
: Called on proxy response. Writing of response must be done inside this method if provided.
License
MIT