Package Exports
- http2-proxy
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (http2-proxy) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
http2-proxy
A simple http/2 & http/1.1 to http/1.1 spec compliant proxy helper for Node.
Features
- Proxies HTTP 2, HTTP 1 and WebSocket.
- Simple and high performance.
- Hop by hop header handling.
- Connection header handling.
- Via header handling.
- Forward header handling.
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()
}
}
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)
})
Use Connect & Helmet
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)
Add x-forwarded Headers
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)
})
Add Response Header
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 || finished) {
return
}
let error = null
let finished = false
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)
}
function setHeaders () {
if (!bytesWritten) {
res.statusCode = proxyRes.statusCode
for (const [ key, value ] of Object.entries(headers)) {
res.setHeader(key, value)
}
}
}
// NOTE: At some point this will be possible
// proxyRes.pipe(res)
proxyRes
.on('data', buf => {
setHeaders()
bytesWritten += buf.length
if (!res.write(buf)) {
proxyRes.pause()
}
})
.on('end', () => {
setHeaders()
// WORKAROUND: https://github.com/nodejs/node/pull/27984
if (!proxyRes.aborted) {
res.end()
// WORKAROUND: https://github.com/nodejs/node/pull/24347
finished = true
}
})
.on('close', () => {
res.off('drain', onDrain)
}))
res.on('drain', onDrain)
function onDrain () {
proxyRes.resume()
}
}
})
} catch (err) {
if (!err.statusCode) {
throw err
}
error = err
if (err.statusCode === 503) {
continue
}
if (req.method === 'HEAD' || req.method === 'GET') {
if (!bytesWritten) {
continue
}
// TODO: Retry range request
}
throw err
}
}
throw error || new createError.ServiceUnavailable()
} catch (err) {
defaultWebHandler(err)
}
}
[async] web (req, res, options[, callback])
req
:http.IncomingMessage
orhttp2.Http2ServerRequest
.res
:http.ServerResponse
orhttp2.Http2ServerResponse
.options
: See Optionscallback(err, req, res)
: Called on completion or error.
See request
[async] ws (req, socket, head, options[, callback])
req
:http.IncomingMessage
.socket
:net.Socket
.head
:Buffer
.options
: See Options.callback(err, req, socket, head)
: Called on completion or error.
See upgrade
options
hostname
: Proxyhttp.request(options)
target hostname.port
: Proxyhttp.request(options)
target port.protocol
: Agent protocol ('http'
or'https'
). Defaults to'http'
.path
: Target pathname. Defaults toreq.originalUrl || req.url
.proxyTimeout
: Proxyhttp.request(options)
timeout.proxyName
: Proxy name used for Via header.timeout
:http.IncomingMessage
orhttp2.Http2ServerRequest
timeout.[async] onReq(req, options[, callback])
: Called before proxy request. If returning a truthy value it will be used as the request.req
:http.IncomingMessage
orhttp2.Http2ServerRequest
options
: Options passed tohttp.request(options)
.callback(err)
: Called on completion or error.
[async] onRes(req, resOrSocket, proxyRes[, callback])
: Called on proxy response. Writing of response must be done inside this method if provided.req
:http.IncomingMessage
orhttp2.Http2ServerRequest
.resOrSocket
: Forweb
http.ServerResponse
orhttp2.Http2ServerResponse
and forws
net.Socket
.proxyRes
:http.ServerResponse
.callback(err)
: Called on completion or error.
Node
These are some existing issues in NodeJS to keep in mind when writing proxy code.
- https://github.com/nodejs/node/issues/27981
- https://github.com/nodejs/node/issues/28001
- https://github.com/nodejs/node/issues/27880
- https://github.com/nodejs/node/issues/24743
- https://github.com/nodejs/node/issues/24742
And some pending PR's:
- https://github.com/nodejs/node/pull/28004
- https://github.com/nodejs/node/pull/27984
- https://github.com/nodejs/node/pull/24347
Some of these are further referenced in the examples.