http2-proxy
Advanced tools
Comparing version 0.2.11 to 0.2.12
464
index.js
@@ -31,7 +31,7 @@ const http2 = require('http2') | ||
module.exports = { | ||
ws (req, socket, head, options, onProxyError) { | ||
impl(req, socket, head, options, onProxyError) | ||
ws (req, socket, head, options, callback) { | ||
impl(req, socket, head, options, callback) | ||
}, | ||
web (req, res, options, onProxyError) { | ||
impl(req, res, null, options, onProxyError) | ||
web (req, res, options, callback) { | ||
impl(req, res, null, options, callback) | ||
} | ||
@@ -48,32 +48,10 @@ } | ||
onRes | ||
}, onProxyError) { | ||
let hasError = false | ||
}, callback) { | ||
const errorHandler = ErrorHandler.create(req, resOrSocket, callback) | ||
function onError (err, statusCode = (err && err.statusCode) || 500) { | ||
if (hasError) { | ||
return | ||
} | ||
resOrSocket.on('error', errorHandler) | ||
hasError = true | ||
const incoming = req.stream || req | ||
incoming.on('error', errorHandler) | ||
if (resOrSocket.closed === true || | ||
resOrSocket.headersSent !== false || | ||
!resOrSocket.writeHead | ||
) { | ||
resOrSocket.destroy() | ||
} else { | ||
resOrSocket.writeHead(statusCode) | ||
resOrSocket.end() | ||
} | ||
if (onProxyError) { | ||
onProxyError(err, req, resOrSocket) | ||
} else { | ||
throw err | ||
} | ||
} | ||
(req.stream || req).on('error', onError) | ||
resOrSocket.on('error', onError) | ||
try { | ||
@@ -95,13 +73,12 @@ if (resOrSocket instanceof net.Socket) { | ||
if (proxyName && | ||
req.headers[HTTP2_HEADER_VIA] && | ||
req.headers[HTTP2_HEADER_VIA] | ||
.split(',') | ||
.some(name => sanitize(name).endsWith(proxyName.toLowerCase())) | ||
) { | ||
throw createError('loop detected', null, 508) | ||
if (proxyName && req.headers[HTTP2_HEADER_VIA]) { | ||
for (const name of req.headers[HTTP2_HEADER_VIA].split(',')) { | ||
if (sanitize(name).endsWith(proxyName.toLowerCase())) { | ||
throw createError('loop detected', null, 508) | ||
} | ||
} | ||
} | ||
if (timeout) { | ||
req.setTimeout(timeout, () => onError(createError('request timeout', null, 408))) | ||
req.setTimeout(timeout, errorHandler.requestTimeout) | ||
} | ||
@@ -140,128 +117,23 @@ | ||
return proxy(req, resOrSocket, options, onRes, onError) | ||
return proxy(req, resOrSocket, options, onRes, errorHandler) | ||
} catch (err) { | ||
return onError(err) | ||
return errorHandler(err) | ||
} | ||
} | ||
function proxy (req, resOrSocket, options, onRes, onError) { | ||
function proxy (req, resOrSocket, options, onRes, errorHandler) { | ||
const proxyReq = http.request(options) | ||
const proxyErrorHandler = ProxyErrorHandler.create(req, proxyReq, errorHandler) | ||
let hasError = false | ||
const abort = () => { | ||
if (!proxyReq.aborted) { | ||
proxyReq.abort() | ||
} | ||
} | ||
const callback = err => { | ||
if (hasError) { | ||
return | ||
} | ||
hasError = true | ||
req.removeListener('close', abort) | ||
abort() | ||
onError(err) | ||
} | ||
req.on('close', abort) | ||
req | ||
.pipe(proxyReq) | ||
.on('error', err => { | ||
if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') { | ||
err.statusCode = 503 | ||
callback(err) | ||
} else if (/HPE_INVALID/.test(err.code)) { | ||
err.statusCode = 502 | ||
callback(err) | ||
} else if (err.code === 'ECONNRESET') { | ||
if (!proxyReq.aborted) { | ||
err.statusCode = 502 | ||
callback(err) | ||
} | ||
} else { | ||
callback(err) | ||
} | ||
}) | ||
// NOTE http.ClientRequest emits "socket hang up" error when aborted, i.e. | ||
// there is no need to listen for proxyReq.on('aborted', ...). | ||
.on('timeout', () => callback(createError('gateway timeout', null, 504))) | ||
.on('response', proxyRes => { | ||
try { | ||
proxyRes.on('aborted', () => callback(createError('socket hang up', 'ECONNRESET', 502))) | ||
.on('error', proxyErrorHandler) | ||
// NOTE http.ClientRequest emits "socket hang up" error when aborted | ||
// before having received a response, i.e. there is no need to listen for | ||
// proxyReq.on('aborted', ...). | ||
.on('timeout', proxyErrorHandler.gatewayTimeout) | ||
.on('response', ProxyResponseHandler.create(req, resOrSocket, onRes, proxyErrorHandler)) | ||
if (resOrSocket instanceof net.Socket) { | ||
if (onRes) { | ||
onRes(req, resOrSocket) | ||
} | ||
if (!proxyRes.upgrade) { | ||
resOrSocket.end() | ||
} | ||
} else { | ||
setupHeaders(proxyRes.headers) | ||
resOrSocket.statusCode = proxyRes.statusCode | ||
for (const key of Object.keys(proxyRes.headers)) { | ||
resOrSocket.setHeader(key, proxyRes.headers[key]) | ||
} | ||
if (onRes) { | ||
onRes(req, resOrSocket) | ||
} | ||
resOrSocket.writeHead(resOrSocket.statusCode) | ||
proxyRes.on('end', () => { | ||
resOrSocket.addTrailers(proxyRes.trailers) | ||
}) | ||
proxyRes | ||
.on('error', callback) | ||
.pipe(resOrSocket) | ||
} | ||
} catch (err) { | ||
callback(err) | ||
} | ||
}) | ||
if (resOrSocket instanceof net.Socket) { | ||
proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => { | ||
try { | ||
setupSocket(proxySocket) | ||
if (proxyHead && proxyHead.length) { | ||
proxySocket.unshift(proxyHead) | ||
} | ||
resOrSocket.write( | ||
Object | ||
.keys(proxyRes.headers) | ||
.reduce((head, key) => { | ||
const value = proxyRes.headers[key] | ||
if (!Array.isArray(value)) { | ||
head.push(key + ': ' + value) | ||
return head | ||
} | ||
for (let i = 0; i < value.length; i++) { | ||
head.push(key + ': ' + value[i]) | ||
} | ||
return head | ||
}, ['HTTP/1.1 101 Switching Protocols']) | ||
.join('\r\n') + '\r\n\r\n' | ||
) | ||
proxyRes.on('error', callback) | ||
proxySocket | ||
.on('error', callback) | ||
.pipe(resOrSocket) | ||
.pipe(proxySocket) | ||
} catch (err) { | ||
callback(err) | ||
} | ||
}) | ||
proxyReq.on('upgrade', ProxyUpgradeHandler.create(req, resOrSocket, proxyErrorHandler)) | ||
} | ||
@@ -345,1 +217,283 @@ } | ||
} | ||
class ErrorHandler { | ||
constructor () { | ||
this.hasError = false | ||
this.req = null | ||
this.resOrSocket = null | ||
this.callback = null | ||
this._release = this._release.bind(this) | ||
this._handle = this._handle.bind(this) | ||
this._handle.requestTimeout = this._requestTimeout.bind(this) | ||
} | ||
_requestTimeout () { | ||
this._handle(createError('request timeout', null, 408)) | ||
} | ||
_handle (err, statusCode = err.statusCode || 500) { | ||
if (this.hasError) { | ||
return | ||
} | ||
this.hasError = true | ||
if (this.resOrSocket.closed === true || | ||
this.resOrSocket.headersSent !== false || | ||
!this.resOrSocket.writeHead | ||
) { | ||
this.resOrSocket.destroy() | ||
} else { | ||
this.resOrSocket.writeHead(statusCode) | ||
this.resOrSocket.end() | ||
} | ||
if (this.callback) { | ||
this.callback(err, this.req, this.resOrSocket) | ||
} else { | ||
throw err | ||
} | ||
} | ||
_release () { | ||
this.hasError = false | ||
this.req = null | ||
this.resOrSocket = null | ||
this.callback = null | ||
ErrorHandler.pool.push(this) | ||
} | ||
static create (req, resOrSocket, callback) { | ||
const handler = ErrorHandler.pool.pop() || new ErrorHandler() | ||
handler.hasError = false | ||
handler.req = req | ||
handler.resOrSocket = resOrSocket | ||
handler.callback = callback | ||
handler.req.on('close', handler._release) | ||
return handler._handle | ||
} | ||
} | ||
ErrorHandler.pool = [] | ||
class ProxyErrorHandler { | ||
constructor () { | ||
this.hasError = null | ||
this.req = null | ||
this.proxyReq = null | ||
this.errorHandler = null | ||
this._release = this._release.bind(this) | ||
this._handle = this._handle.bind(this) | ||
this._handle.gatewayTimeout = this._gatewayTimeout.bind(this) | ||
this._handle.socketHangup = this._socketHangup.bind(this) | ||
} | ||
_handle (err) { | ||
if (this.hasError) { | ||
return | ||
} | ||
this.hasError = true | ||
if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') { | ||
err.statusCode = 503 | ||
} else if (/HPE_INVALID/.test(err.code)) { | ||
err.statusCode = 502 | ||
} else if (err.code === 'ECONNRESET') { | ||
if (!this.proxyReq.aborted) { | ||
err.statusCode = 502 | ||
} else { | ||
return | ||
} | ||
} | ||
this._abort() | ||
this.errorHandler(err) | ||
} | ||
_gatewayTimeout () { | ||
this._handle(createError('gateway timeout', null, 504)) | ||
} | ||
_socketHangup () { | ||
this._handle(createError('socket hang up', 'ECONNRESET', 502)) | ||
} | ||
_abort () { | ||
if (!this.proxyReq.aborted) { | ||
this.proxyReq.abort() | ||
} | ||
} | ||
_release () { | ||
this._abort() | ||
this.hasError = null | ||
this.req = null | ||
this.proxyReq = null | ||
this.errorHandler = null | ||
ProxyErrorHandler.pool.push(this) | ||
} | ||
static create (req, proxyReq, errorHandler) { | ||
const handler = ProxyErrorHandler.pool.pop() || new ProxyErrorHandler() | ||
handler.req = req | ||
handler.proxyReq = proxyReq | ||
handler.errorHandler = errorHandler | ||
handler.req.on('close', handler._release) | ||
return handler._handle | ||
} | ||
} | ||
ProxyErrorHandler.pool = [] | ||
class ProxyResponseHandler { | ||
constructor () { | ||
this.req = null | ||
this.resOrSocket = null | ||
this.onRes = null | ||
this.proxyErrorHandler = null | ||
this.proxyRes = null | ||
this._handle = this._handle.bind(this) | ||
this._addTrailers = this._addTrailers.bind(this) | ||
this._release = this._release.bind(this) | ||
} | ||
_addTrailers () { | ||
this.resOrSocket.addTrailers(this.proxyRes.trailers) | ||
} | ||
_handle (proxyRes) { | ||
this.proxyRes = proxyRes | ||
try { | ||
proxyRes.on('aborted', this.proxyErrorHandler.socketHangup) | ||
if (this.resOrSocket instanceof net.Socket) { | ||
if (this.onRes) { | ||
this.onRes(this.req, this.resOrSocket) | ||
} | ||
if (!proxyRes.upgrade) { | ||
this.resOrSocket.end() | ||
} | ||
} else { | ||
setupHeaders(proxyRes.headers) | ||
this.resOrSocket.statusCode = proxyRes.statusCode | ||
for (const key of Object.keys(proxyRes.headers)) { | ||
this.resOrSocket.setHeader(key, proxyRes.headers[key]) | ||
} | ||
if (this.onRes) { | ||
this.onRes(this.req, this.resOrSocket) | ||
} | ||
this.resOrSocket.writeHead(this.resOrSocket.statusCode) | ||
proxyRes.on('end', this._addTrailers) | ||
proxyRes | ||
.on('error', this.proxyErrorHandler) | ||
.pipe(this.resOrSocket) | ||
} | ||
} catch (err) { | ||
this.proxyErrorHandler(err) | ||
} | ||
} | ||
_release () { | ||
this.req = null | ||
this.resOrSocket = null | ||
this.onRes = null | ||
this.proxyErrorHandler = null | ||
this.proxyRes = null | ||
ProxyResponseHandler.pool.push(this) | ||
} | ||
static create (req, resOrSocket, onRes, proxyErrorHandler) { | ||
const handler = ProxyResponseHandler.pool.pop() || new ProxyResponseHandler() | ||
handler.req = req | ||
handler.resOrSocket = resOrSocket | ||
handler.onRes = onRes | ||
handler.proxyErrorHandler = proxyErrorHandler | ||
handler.proxyRes = null | ||
handler.req.on('close', handler._release) | ||
return handler._handle | ||
} | ||
} | ||
ProxyResponseHandler.pool = [] | ||
class ProxyUpgradeHandler { | ||
constructor () { | ||
this.req = null | ||
this.resOrSocket = null | ||
this.proxyErrorHandler = null | ||
this.proxyRes = null | ||
this.proxySocket = null | ||
this._release = this._release.bind(this) | ||
this._handle = this._handle.bind(this) | ||
} | ||
_handle (proxyRes, proxySocket, proxyHead) { | ||
this.proxyRes = proxyRes | ||
this.proxySocket = proxySocket | ||
try { | ||
setupSocket(proxySocket) | ||
if (proxyHead && proxyHead.length) { | ||
proxySocket.unshift(proxyHead) | ||
} | ||
let head = 'HTTP/1.1 101 Switching Protocols' | ||
for (const key of Object.keys(proxyRes.headers)) { | ||
const value = proxyRes.headers[key] | ||
if (!Array.isArray(value)) { | ||
head += '\r\n' + key + ': ' + value | ||
} else { | ||
for (let i = 0; i < value.length; i++) { | ||
head += '\r\n' + key + ': ' + value[i] | ||
} | ||
} | ||
} | ||
head += '\r\n\r\n' | ||
this.resOrSocket.write(head) | ||
proxyRes.on('error', this.proxyErrorHandler) | ||
proxySocket | ||
.on('error', this.proxyErrorHandler) | ||
.pipe(this.resOrSocket) | ||
.pipe(proxySocket) | ||
} catch (err) { | ||
this.proxyErrorHandler(err) | ||
} | ||
} | ||
_release () { | ||
this.proxyRes.destroy() | ||
this.proxySocket.destroy() | ||
this.req = null | ||
this.resOrSocket = null | ||
this.proxyErrorHandler = null | ||
this.proxyRes = null | ||
this.proxySocket = null | ||
ProxyUpgradeHandler.pool.push(this) | ||
} | ||
static create (req, resOrSocket, proxyErrorHandler) { | ||
const handler = ProxyUpgradeHandler.pool.pop() || new ProxyUpgradeHandler() | ||
handler.req = req | ||
handler.resOrSocket = resOrSocket | ||
handler.proxyErrorHandler = proxyErrorHandler | ||
handler.req.on('close', handler._release) | ||
return handler._handle | ||
} | ||
} | ||
ProxyUpgradeHandler.pool = [] |
{ | ||
"name": "http2-proxy", | ||
"version": "0.2.11", | ||
"version": "0.2.12", | ||
"scripts": { | ||
@@ -5,0 +5,0 @@ "dev": "nodemon --inspect=9308 --expose-http2 src", |
# node-http2-proxy | ||
A simple http/2 & http/1.1 to http/1.1 spec compliant proxy helper for Node. | ||
A simple high performance http/2 & http/1.1 to http/1.1 spec compliant proxy helper for Node. | ||
@@ -9,2 +9,3 @@ ### Features | ||
- Simple and easy to follow implementation | ||
- GC friendly with minimal allocations | ||
- [Hop by hop header handling](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) | ||
@@ -11,0 +12,0 @@ - [Connection header handling](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
19386
409
120
4