http2-proxy
Advanced tools
Comparing version 1.0.2 to 1.1.0-beta.1
153
index.js
@@ -21,2 +21,3 @@ const http2 = require('http2') | ||
HTTP2_HEADER_VIA, | ||
HTTP2_HEADER_STATUS, | ||
// XXX https://github.com/nodejs/node/issues/15337 | ||
@@ -30,4 +31,4 @@ HTTP2_HEADER_FORWARDED = 'forwarded' | ||
}, | ||
web (req, res, options, callback) { | ||
proxy(req, res, null, options, callback) | ||
web (reqOrStream, resOrHeaders, options, callback) { | ||
proxy(reqOrStream, resOrHeaders, null, options, callback) | ||
} | ||
@@ -43,3 +44,10 @@ } | ||
const kOnProxyRes = Symbol('onProxyRes') | ||
const kFinished = Symbol('finished') | ||
const RESPOND_OPTIONS = { | ||
getTrailers: function () { | ||
return this[kProxyRes].trailers | ||
} | ||
} | ||
function proxy (req, res, head, { | ||
@@ -54,4 +62,16 @@ hostname, | ||
}, callback) { | ||
req[kRes] = res | ||
let reqHeaders = req.headers | ||
let reqMethod = req.method | ||
let reqUrl = req.url | ||
if (!reqHeaders) { | ||
reqHeaders = res | ||
reqMethod = reqHeaders[HTTP2_HEADER_METHOD] | ||
reqUrl = reqHeaders[HTTP2_HEADER_PATH] | ||
res = req | ||
} else { | ||
req[kRes] = res | ||
} | ||
res[kReq] = req | ||
@@ -63,2 +83,3 @@ res[kRes] = res | ||
res[kProxySocket] = null | ||
res[kFinished] = false | ||
@@ -75,29 +96,19 @@ assert(typeof callback === 'function' || callback == null) | ||
if (proxyName && reqHeaders[HTTP2_HEADER_VIA]) { | ||
for (const name of reqHeaders[HTTP2_HEADER_VIA].split(',')) { | ||
if (sanitize(name).endsWith(proxyName.toLowerCase())) { | ||
return onFinish.call(res, createError('loop detected', null, 508)) | ||
} | ||
} | ||
} | ||
if (res instanceof net.Socket) { | ||
if (req.method !== 'GET') { | ||
if (reqMethod !== 'GET') { | ||
return onFinish.call(res, createError('method not allowed', null, 405)) | ||
} | ||
if (sanitize(req.headers[HTTP2_HEADER_UPGRADE]) !== 'websocket') { | ||
if (sanitize(reqHeaders[HTTP2_HEADER_UPGRADE]) !== 'websocket') { | ||
return onFinish.call(res, createError('bad request', null, 400)) | ||
} | ||
} | ||
if (req.httpVersion !== '1.1' && req.httpVersion !== '2.0') { | ||
return onFinish.call(res, createError('http version not supported', null, 505)) | ||
} | ||
if (proxyName && req.headers[HTTP2_HEADER_VIA]) { | ||
for (const name of req.headers[HTTP2_HEADER_VIA].split(',')) { | ||
if (sanitize(name).endsWith(proxyName.toLowerCase())) { | ||
return onFinish.call(res, createError('loop detected', null, 508)) | ||
} | ||
} | ||
} | ||
if (timeout) { | ||
req.setTimeout(timeout, onRequestTimeout) | ||
} | ||
if (res instanceof net.Socket) { | ||
if (head && head.length) { | ||
@@ -110,4 +121,8 @@ res.unshift(head) | ||
const headers = getRequestHeaders(req) | ||
if (timeout != null) { | ||
req.setTimeout(timeout, onRequestTimeout) | ||
} | ||
const headers = getRequestHeaders(reqHeaders, req.socket) | ||
if (proxyName) { | ||
@@ -122,6 +137,6 @@ if (headers[HTTP2_HEADER_VIA]) { | ||
const options = { | ||
method: req.method, | ||
method: reqMethod, | ||
hostname, | ||
port, | ||
path: req.url, | ||
path: reqUrl, | ||
headers, | ||
@@ -143,10 +158,17 @@ timeout: proxyTimeout | ||
res | ||
.on('finish', onFinish) | ||
.on('close', onFinish) | ||
.on('error', onFinish) | ||
if (req instanceof http2.Http2Stream) { | ||
req | ||
.on('streamClosed', onFinish) | ||
.on('finish', onFinish) | ||
} else { | ||
req | ||
.on('close', onFinish) | ||
res | ||
.on('finish', onFinish) | ||
.on('close', onFinish) | ||
.on('error', onFinish) | ||
} | ||
req | ||
.on('aborted', onFinish) | ||
.on('close', onFinish) | ||
.on('error', onFinish) | ||
@@ -170,6 +192,8 @@ .pipe(proxyReq) | ||
if (!res[kProxyCallback]) { | ||
if (!res[kFinished]) { | ||
return | ||
} | ||
res[kFinished] = true | ||
if (err) { | ||
@@ -191,3 +215,7 @@ err.statusCode = statusCode || err.statusCode || 500 | ||
} else { | ||
res.writeHead(statusCode) | ||
if (res.respond) { | ||
res.respond({ [HTTP2_HEADER_STATUS]: statusCode }) | ||
} else { | ||
res.writeHead(statusCode) | ||
} | ||
res.end() | ||
@@ -197,3 +225,3 @@ } | ||
if (res[kProxyCallback]) { | ||
res[kProxyCallback].call(res[kProxyReq], err, res[kReq], res) | ||
res[kProxyCallback].call(null, err, res[kReq], res) | ||
res[kProxyCallback] = null | ||
@@ -246,12 +274,23 @@ } | ||
res.statusCode = proxyRes.statusCode | ||
for (const key of Object.keys(proxyRes.headers)) { | ||
res.setHeader(key, proxyRes.headers[key]) | ||
} | ||
if (res.respond) { | ||
proxyRes.headers[HTTP2_HEADER_STATUS] = proxyRes.status | ||
if (this[kOnProxyRes]) { | ||
this[kOnProxyRes](this[kReq], res) | ||
if (this[kOnProxyRes]) { | ||
this[kOnProxyRes](this[kReq], proxyRes.headers) | ||
} | ||
res.respond(proxyRes.headers, RESPOND_OPTIONS) | ||
} else { | ||
res.statusCode = proxyRes.statusCode | ||
for (const key of Object.keys(proxyRes.headers)) { | ||
res.setHeader(key, proxyRes.headers[key]) | ||
} | ||
if (this[kOnProxyRes]) { | ||
this[kOnProxyRes](this[kReq], res) | ||
} | ||
res.writeHead(res.statusCode) | ||
} | ||
res.writeHead(res.statusCode) | ||
proxyRes | ||
@@ -307,3 +346,3 @@ .on('end', onProxyTrailers) | ||
this[kRes].write(head) | ||
res.write(head) | ||
@@ -315,20 +354,18 @@ proxyRes | ||
.on('error', onFinish) | ||
.pipe(this[kRes]) | ||
.pipe(res) | ||
.pipe(proxySocket) | ||
} | ||
function getRequestHeaders (req) { | ||
const host = req.headers[HTTP2_HEADER_AUTHORITY] || req.headers[HTTP2_HEADER_HOST] | ||
const upgrade = req.headers[HTTP2_HEADER_UPGRADE] | ||
const forwarded = req.headers[HTTP2_HEADER_FORWARDED] | ||
function getRequestHeaders (req, reqHeaders, reqSocket) { | ||
const host = reqHeaders[HTTP2_HEADER_AUTHORITY] || reqHeaders[HTTP2_HEADER_HOST] | ||
const upgrade = reqHeaders[HTTP2_HEADER_UPGRADE] | ||
const forwarded = reqHeaders[HTTP2_HEADER_FORWARDED] | ||
const headers = setupHeaders(Object.assign({}, req.headers)) | ||
const headers = setupHeaders(Object.assign({}, reqHeaders)) | ||
if (req.httpVersionMajor === 2) { | ||
// Remove pseudo headers | ||
delete headers[HTTP2_HEADER_AUTHORITY] | ||
delete headers[HTTP2_HEADER_METHOD] | ||
delete headers[HTTP2_HEADER_PATH] | ||
delete headers[HTTP2_HEADER_SCHEME] | ||
} | ||
// Remove pseudo headers | ||
delete headers[HTTP2_HEADER_AUTHORITY] | ||
delete headers[HTTP2_HEADER_METHOD] | ||
delete headers[HTTP2_HEADER_PATH] | ||
delete headers[HTTP2_HEADER_SCHEME] | ||
@@ -340,4 +377,4 @@ if (upgrade) { | ||
headers[HTTP2_HEADER_FORWARDED] = `by=${req.socket.localAddress}` | ||
headers[HTTP2_HEADER_FORWARDED] += `; for=${req.socket.remoteAddress}` | ||
headers[HTTP2_HEADER_FORWARDED] = `by=${reqSocket.localAddress}` | ||
headers[HTTP2_HEADER_FORWARDED] += `; for=${reqSocket.remoteAddress}` | ||
@@ -359,3 +396,3 @@ if (forwarded) { | ||
headers[HTTP2_HEADER_FORWARDED] += `; proto=${req.socket.encrypted ? 'https' : 'http'}` | ||
headers[HTTP2_HEADER_FORWARDED] += `; proto=${reqSocket.encrypted ? 'https' : 'http'}` | ||
@@ -362,0 +399,0 @@ return headers |
{ | ||
"name": "http2-proxy", | ||
"version": "1.0.2", | ||
"version": "1.1.0-beta.1", | ||
"scripts": { | ||
@@ -5,0 +5,0 @@ "dev": "nodemon --inspect=9308 --expose-http2 src", |
@@ -24,3 +24,3 @@ # node-http2-proxy | ||
### Usage | ||
### HTTP/1 API | ||
@@ -37,3 +37,3 @@ You must pass `allowHTTP1: true` to the `http2.createServer` or `http2.createSecureServer` factory methods. | ||
#### Proxy HTTP 1.1/2 and WebSocket | ||
#### Proxy HTTP/2, HTTP/1.1 and WebSocket | ||
@@ -79,3 +79,3 @@ ```js | ||
#### Add x-forwarded headers | ||
#### Add x-forwarded headers | ||
@@ -100,4 +100,2 @@ ```javascript | ||
### API | ||
#### web (req, res, options, [callback]) | ||
@@ -140,4 +138,77 @@ | ||
### HTTP/2 API | ||
If HTTP/1 support is not required use the HTTP/2 `stream` API which has a lower overhead. | ||
```js | ||
import http2 from 'http2' | ||
import proxy from 'http2-proxy' | ||
const server = http2.createServer() | ||
server.listen(8000) | ||
``` | ||
#### Proxy HTTP/2 | ||
```js | ||
server.on('stream', (stream, headers) => { | ||
proxy.web(stream, headers, { | ||
hostname: 'localhost' | ||
port: 9000 | ||
}, err => { | ||
if (err) { | ||
console.error('proxy error', err) | ||
} | ||
}) | ||
}) | ||
``` | ||
#### Add x-forwarded headers | ||
```javascript | ||
server.on('stream', (stream, headers) => { | ||
proxy.web(stream, { headers }, { | ||
hostname: 'localhost' | ||
port: 9000, | ||
onReq: (stream, headers) => { | ||
headers['x-forwarded-for'] = stream.socket.remoteAddress | ||
headers['x-forwarded-proto'] = stream.socket.encrypted ? 'https' : 'http' | ||
headers['x-forwarded-host'] = stream.headers['host'] | ||
} | ||
}, err => { | ||
if (err) { | ||
console.error('proxy error', err) | ||
} | ||
}) | ||
}) | ||
``` | ||
#### web (stream, headers, options, [callback]) | ||
- `stream`: [`http2.Http2Stream`](https://nodejs.org/api/http2.html#http2_class_http2_http2stream) | ||
- `headers`: Request headers object | ||
- `options`: See [Options](#options) | ||
- `callback(err)`: Called on completion or error. Optional | ||
Returns a promise if no callback is provided. | ||
See [`request`](https://nodejs.org/api/http.html#http_event_request) | ||
### Options | ||
- `hostname`: Proxy [`http.request(options)`](https://nodejs.org/api/http.html#http_http_request_options_callback) target hostname | ||
- `port`: Proxy [`http.request(options)`](https://nodejs.org/api/http.html#http_http_request_options_callback) target port | ||
- `proxyTimeout`: Proxy [`http.request(options)`](https://nodejs.org/api/http.html#http_http_request_options_callback) timeout | ||
- `proxyName`: Proxy name used for **Via** header | ||
- `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 | ||
- `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) | ||
- `onRes(req, headers)`: Called before proxy response | ||
- `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) | ||
- `headers`: Response headers object | ||
### License | ||
[MIT](LICENSE) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19683
355
210
2