Socket
Socket
Sign inDemoInstall

http2-proxy

Package Overview
Dependencies
Maintainers
1
Versions
193
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

http2-proxy - npm Package Compare versions

Comparing version 4.2.15 to 5.0.0

CHANGELOG.md

336

index.js

@@ -0,4 +1,4 @@

const net = require('net')
const http = require('http')
const https = require('https')
const url = require('url')

@@ -9,2 +9,3 @@ const CONNECTION = 'connection'

const PROXY_AUTHORIZATION = 'proxy-authorization'
const PROXY_AUTHENTICATE = 'proxy-authenticate'
const PROXY_CONNECTION = 'proxy-connection'

@@ -20,14 +21,4 @@ const TE = 'te'

module.exports = {
ws (req, socket, head, options, callback) {
return proxy.call(this, req, socket, head || null, options, callback)
},
web (req, res, options, callback) {
return proxy.call(this, req, res, undefined, options, callback)
}
}
const kReq = Symbol('req')
const kRes = Symbol('res')
const kSelf = Symbol('self')
const kProxyCallback = Symbol('callback')

@@ -37,10 +28,27 @@ const kProxyReq = Symbol('proxyReq')

const kProxySocket = Symbol('proxySocket')
const kOnProxyRes = Symbol('onProxyRes')
const kHead = Symbol('head')
const kConnected = Symbol('connected')
const kOnRes = Symbol('onRes')
function proxy (req, res, head, options, callback) {
if (typeof options === 'string') {
options = new url.URL(options)
module.exports = proxy
proxy.ws = function ws (req, socket, head, options, callback) {
const promise = compat(ctx, options)
if (!callback) {
return promise
}
promise
.then(() => callback(null, req, socket, head))
.then(err => callback(err, req, socket, head))
}
proxy.web = function web (req, res, options, callback) {
const promise = compat(ctx, options)
if (!callback) {
return promise
}
promise
.then(() => callback(null, req, res))
.then(err => callback(err, req, res))
}
async function compat (ctx, options) {
const {

@@ -58,31 +66,64 @@ hostname,

let promise
if (!callback) {
promise = new Promise((resolve, reject) => {
callback = (err, ...args) => err ? reject(err) : resolve(args)
})
if (timeout != null) {
req.setTimeout(timeout)
}
req[kRes] = res
await proxy(
{ ...ctx, proxyName },
async headers => {
const options = {
method: req.method,
hostname,
port,
path,
headers,
timeout: proxyTimeout
}
res[kReq] = req
res[kRes] = res
res[kSelf] = this
res[kProxyCallback] = callback
res[kProxyReq] = null
res[kProxySocket] = null
res[kHead] = head
res[kOnProxyRes] = onRes
if (onReq) {
return await new Promise((resolve, reject) => {
const promise = onReq(req, options, (err, val) => err ? reject(err) : resolve(val))
if (promise.then) {
promise.then(resolve).catch(reject)
}
})
} else {
let agent
if (protocol == null || /^(http|ws):?$/.test(protocol)) {
agent = http
} else if (/^(http|ws)s:?$/.test(protocol)) {
agent = https
} else {
throw new HttpError(`invalid protocol`, null, 500)
}
return agent.request(options)
}
},
async (proxyRes, headers) => {
proxyRes.headers = headers
return new Promise((resolve, reject) => {
const promise = onRes(req, res, proxyRes, (err, val) => err ? reject(err) : callback(val))
if (promise.then) {
promise.then(resolve).catch(reject)
}
})
}
)
}
const headers = getRequestHeaders(req, { proxyName })
async function proxy ({ req, socket, res = socket, head, proxyName }, onReq, onRes) {
let callback
let promise = new Promise((resolve, reject) => {
callback = err => err ? reject(err) : resolve()
})
const headers = getRequestHeaders(req, proxyName)
if (head !== undefined) {
if (req.method !== 'GET') {
process.nextTick(onComplete.bind(res), new HttpError('method not allowed', null, 405))
return promise
throw new HttpError('only GET request allowed', null, 405)
}
if (sanitize(req.headers[UPGRADE]) !== 'websocket') {
process.nextTick(onComplete.bind(res), new HttpError('bad request', null, 400))
return promise
if (req.headers[UPGRADE] !== 'websocket') {
throw new HttpError('missing upgrade header', null, 400)
}

@@ -100,49 +141,17 @@

if (proxyName) {
if (headers[VIA]) {
headers[VIA] += `,${req.httpVersion} ${proxyName}`
} else {
headers[VIA] = `${req.httpVersion} ${proxyName}`
}
}
const proxyReq = await onReq(headers)
if (timeout != null) {
req.setTimeout(timeout)
}
req[kRes] = res
const reqOptions = {
method: req.method,
hostname,
port,
path,
headers,
timeout: proxyTimeout
}
res[kReq] = req
res[kRes] = res
res[kProxySocket] = null
res[kProxyReq] = proxyReq
res[kProxyRes] = null
res[kProxyCallback] = callback
let proxyReq
try {
if (onReq) {
proxyReq = onReq.call(res[kSelf], req, reqOptions)
}
if (!proxyReq) {
let agent
if (protocol == null || /^(http|ws):?$/.test(protocol)) {
agent = http
} else if (/^(http|ws)s:?$/.test(protocol)) {
agent = https
} else {
throw new HttpError(`invalid protocol`, null, 500)
}
proxyReq = agent.request(reqOptions)
}
} catch (err) {
process.nextTick(onComplete.bind(res), err)
return promise
}
proxyReq[kReq] = req
proxyReq[kRes] = res
res[kProxyReq] = proxyReq
proxyReq[kConnected] = false
proxyReq[kOnRes] = onRes

@@ -158,12 +167,24 @@ res

.on('error', onComplete)
.on('timeout', onRequestTimeout)
.pipe(proxyReq)
.on('error', onProxyError)
.on('timeout', onProxyTimeout)
.on('response', onProxyResponse)
.on('upgrade', onProxyUpgrade)
proxyReq
.on('error', onProxyReqError)
.on('timeout', onProxyReqTimeout)
.on('response', onProxyReqResponse)
.on('upgrade', onProxyReqUpgrade)
deferToConnect.call(proxyReq, onProxyConnect)
return promise
}
function deferToConnect (cb) {
this.once('socket', function (socket) {
if (!socket.connecting) {
cb.call(this)
} else {
socket.once('connect', cb.bind(this))
}
})
}
function onComplete (err) {

@@ -173,5 +194,3 @@ const res = this[kRes]

const callback = res[kProxyCallback]
if (!callback) {
if (!res[kProxyCallback]) {
return

@@ -181,34 +200,27 @@ }

const proxySocket = res[kProxySocket]
const proxyReq = res[kProxyReq]
const proxyRes = res[kProxyRes]
const proxyReq = res[kProxyReq]
const callback = res[kProxyCallback]
res[kProxySocket] = undefined
res[kProxyRes] = undefined
res[kProxyReq] = undefined
res[kSelf] = undefined
res[kHead] = undefined
res[kOnProxyRes] = undefined
res[kProxyCallback] = undefined
res[kProxySocket] = null
res[kProxyReq] = null
res[kProxyRes] = null
res[kProxyCallback] = null
res
.removeListener('close', onComplete)
.removeListener('finish', onComplete)
.removeListener('error', onComplete)
.off('close', onComplete)
.off('finish', onComplete)
.off('error', onComplete)
req
.removeListener('close', onComplete)
.removeListener('aborted', onComplete)
.removeListener('error', onComplete)
.removeListener('timeout', onRequestTimeout)
.off('close', onComplete)
.off('aborted', onComplete)
.off('error', onComplete)
if (proxySocket) {
if (proxySocket.destroy) {
proxySocket.destroy()
}
proxySocket.destroy()
}
if (proxyRes) {
if (proxyRes.destroy) {
proxyRes.destroy()
}
proxyRes.destroy()
}

@@ -224,40 +236,21 @@

if (err) {
err.statusCode = err.statusCode || 500
err.code = err.code || res.code
if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
err.statusCode = 503
} else if (/HPE_INVALID/.test(err.code)) {
err.statusCode = 502
}
}
if (res[kHead] === undefined) {
callback.call(res[kSelf], err, req, res, { proxyReq, proxyRes })
} else {
callback.call(res[kSelf], err, req, res, res[kHead], { proxyReq, proxyRes, proxySocket })
}
callback(err)
}
function onRequestTimeout () {
onComplete.call(this, new HttpError('request timeout', null, 408))
function onProxyConnect () {
this[kConnected] = true
this[kReq].pipe(this)
}
function onProxyError (err) {
err.statusCode = 502
function onProxyReqError (err) {
err.statusCode = this[kConnected] ? 502 : 503
onComplete.call(this, err)
}
function onProxyTimeout () {
onComplete.call(this, new HttpError('gateway timeout', null, 504))
function onProxyReqTimeout () {
onComplete.call(this, new HttpError('proxy timeout', 'ETIMEDOUT', 504))
}
function onProxyAborted () {
onComplete.call(this, new HttpError('response aborted', 'ECONNRESET', 502))
}
function onProxyResponse (proxyRes) {
async function onProxyReqResponse (proxyRes) {
const res = this[kRes]
const req = res[kReq]

@@ -267,11 +260,11 @@ res[kProxyRes] = proxyRes

const headers = setupHeaders(proxyRes.headers)
proxyRes
.on('aborted', onProxyAborted)
.on('error', onComplete)
.on('aborted', onProxyResAborted)
.on('error', onProxyResError)
const headers = setupHeaders(proxyRes.headers)
if (res[kOnProxyRes]) {
if (this[kOnRes]) {
try {
res[kOnProxyRes].call(res[kSelf], req, res, proxyRes, err => onComplete.call(this, err))
await this[kOnRes](proxyRes, headers)
} catch (err) {

@@ -294,3 +287,3 @@ onComplete.call(this, err)

function onProxyUpgrade (proxyRes, proxySocket, proxyHead) {
function onProxyReqUpgrade (proxyRes, proxySocket, proxyHead) {
const res = this[kRes]

@@ -310,4 +303,4 @@

proxySocket
.on('error', onComplete)
.on('close', onProxyAborted)
.on('error', onProxyResError)
.on('close', onProxyResAborted)
.pipe(res)

@@ -317,2 +310,11 @@ .pipe(proxySocket)

function onProxyResError (err) {
err.statusCode = 502
onComplete.call(this, err)
}
function onProxyResAborted () {
onComplete.call(this, new HttpError('proxy aborted', 'ECONNRESET', 502))
}
function createHttpHeader (line, headers) {

@@ -333,3 +335,3 @@ let head = line

function getRequestHeaders (req, { proxyName }) {
function getRequestHeaders (req, proxyName) {
const headers = {}

@@ -342,7 +344,8 @@ for (const [ key, value ] of Object.entries(req.headers)) {

// TODO(fix): <host> [ ":" <port> ] vs <pseudonym>
// See, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Via.
if (proxyName) {
proxyName = sanitize(proxyName)
if (headers[VIA]) {
for (const name of headers[VIA].split(',')) {
if (sanitize(name).endsWith(proxyName)) {
if (name.endsWith(proxyName)) {
throw new HttpError('loop detected', null, 508)

@@ -356,12 +359,23 @@ }

headers[VIA] += `${req.httpVersion} ${proxyName}`
headers[VIA] += `${req.httpVersion} ${req.proxyName}`
}
function printIp (address) {
return /:.*:/.test(address) ? `"[${address}]"` : address
function printIp (address, port) {
const isIPv6 = net.isIPv6(address)
let str = `${address}`
if (isIPv6) {
str = `[${str}]`
}
if (port) {
str = `${str}:${port}`
}
if (isIPv6) {
str = `"${str}"`
}
return str
}
const forwarded = [
`by=${printIp(req.socket.localAddress)}`,
`for=${printIp(req.socket.remoteAddress)}`,
`by=${printIp(req.socket.localAddress, req.socket.localPort)}`,
`for=${printIp(req.socket.remoteAddress, req.socket.remotePort)}`,
`proto=${req.socket.encrypted ? 'https' : 'http'}`,

@@ -387,3 +401,3 @@ `host=${printIp(req.headers[AUTHORITY] || req.headers[HOST] || '')}`

function setupHeaders (headers) {
const connection = sanitize(headers[CONNECTION])
const connection = headers[CONNECTION]

@@ -398,9 +412,11 @@ if (connection && connection !== CONNECTION && connection !== KEEP_ALIVE) {

delete headers[CONNECTION]
delete headers[PROXY_CONNECTION]
delete headers[KEEP_ALIVE]
delete headers[PROXY_AUTHENTICATE]
delete headers[PROXY_AUTHORIZATION]
delete headers[TE]
delete headers[TRAILER]
delete headers[TRANSFER_ENCODING]
delete headers[TE]
delete headers[UPGRADE]
delete headers[PROXY_AUTHORIZATION]
delete headers[PROXY_CONNECTION]
delete headers[TRAILER]
delete headers[HTTP2_SETTINGS]

@@ -411,6 +427,2 @@

function sanitize (name) {
return name ? name.trim().toLowerCase() : ''
}
class HttpError extends Error {

@@ -417,0 +429,0 @@ constructor (msg, code, statusCode) {

{
"name": "http2-proxy",
"version": "4.2.15",
"version": "5.0.0",
"scripts": {

@@ -5,0 +5,0 @@ "dev": "nodemon --inspect=9308 src",

@@ -5,3 +5,3 @@ # node-http2-proxy

### Features
## Features

@@ -15,3 +15,3 @@ - Proxies HTTP 2, HTTP 1 and WebSocket.

### Installation
## Installation

@@ -22,5 +22,5 @@ ```sh

### Notes
## Notes
`http2-proxy` requires at least node **v9.5.0**.
`http2-proxy` requires at least node **v10.0.0**.

@@ -49,3 +49,3 @@ Request & Response errors are emitted to the server object either as `clientError` for http/1 or `streamError` for http/2. See the NodeJS documentation for further details.

### HTTP/1 API
## HTTP/1 API

@@ -71,4 +71,6 @@ You must pass `allowHTTP1: true` to the `http2.createServer` or `http2.createSecureServer` factory methods.

#### Proxy HTTP/2, HTTP/1 and WebSocket
## API
### Proxy HTTP/2, HTTP/1 and WebSocket
```js

@@ -89,3 +91,3 @@ server.on('request', (req, res) => {

#### Use [Connect](https://www.npmjs.com/package/connect) & [Helmet](https://www.npmjs.com/package/helmet)
### Use [Connect](https://www.npmjs.com/package/connect) & [Helmet](https://www.npmjs.com/package/helmet)

@@ -108,3 +110,3 @@ ```js

#### Add x-forwarded headers
### Add x-forwarded headers

@@ -125,3 +127,3 @@ ```js

#### Follow Redirects
### Follow Redirects

@@ -140,3 +142,3 @@ ```js

#### web (req, res, options, [callback])
### web (req, res, options, [callback])

@@ -150,3 +152,3 @@ - `req`: [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) or [`http2.Http2ServerRequest`](https://nodejs.org/api/http2.html#http2_class_http2_http2serverrequest).

#### ws (req, socket, head, options, [callback])
### ws (req, socket, head, options, [callback])

@@ -170,5 +172,6 @@ - `req`: [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage).

- `timeout`: [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) or [`http2.Http2ServerRequest`](https://nodejs.org/api/http2.html#http2_class_http2_http2serverrequest) timeout.
- `onReq(req, options)`: Called before proxy request. If returning a truthy value it will be used as the request.
- `onReq(req, options, callback)`: Called before proxy request. If returning a truthy value it will be used as the request.
- `req`: [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) or [`http2.Http2ServerRequest`](https://nodejs.org/api/http2.html#http2_class_http2_http2serverrequest)
- `options`: Options passed to [`http.request(options)`](https://nodejs.org/api/http.html#http_http_request_options_callback).
- `callback(err)`: Called on completion or error. Optionally a promise can be returned.
- `onRes(req, resOrSocket, proxyRes, callback)`: Called before proxy response.

@@ -178,3 +181,3 @@ - `req`: [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) or [`http2.Http2ServerRequest`](https://nodejs.org/api/http2.html#http2_class_http2_http2serverrequest).

- `proxyRes`: [`http.ServerResponse`](https://nodejs.org/api/http.html#http_class_http_serverresponse).
- `callback(err)`: Called on completion or error..
- `callback(err)`: Called on completion or error. Optionally a promise can be returned.

@@ -181,0 +184,0 @@ ### License

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc