Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maintenance #1251

Merged
merged 25 commits into from Apr 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f611880
[dist] make tests work reliably, add package-lock.json
jcrugzz Apr 19, 2018
f6c396f
[wip] proper tests and reporting
jcrugzz Apr 19, 2018
d2d9f2b
[dist][test] codecov config
jcrugzz Apr 19, 2018
66d1337
[fix] move badges
jcrugzz Apr 19, 2018
5be846b
Adding ability to set cookie path
Feb 21, 2018
98b6469
Forgot 'i' flag when changing from regex shorthand to string.
Feb 21, 2018
7370b06
Updating docs and adding more tests.
Feb 21, 2018
58df2a4
Removing unnecessary check since this is a private API
Feb 21, 2018
b5b7a83
[fix] slightly more tolerant
jcrugzz Apr 19, 2018
4269f88
Added timeout option to docs
jlaamanen Jan 31, 2018
528a688
Update common.js
AydinChavez Jan 23, 2018
e9fff45
feat: 添加response自处理参数
Dec 7, 2017
2f6f2e0
[dist] document the feature
jcrugzz Apr 19, 2018
52d8fa1
[test] for override method feature
jcrugzz Apr 19, 2018
48f372b
fix small typos in README
Oct 30, 2017
93e48b1
Add use case for proxy to HTTPS using a PKCS12 client certificate
Oct 16, 2017
c0a5022
Add detail about "buffer" option
jonhunter1977 Sep 11, 2017
2399f85
Add followRedirects option
Aug 17, 2017
54dd4f4
Include websocket non-upgrade response
Tigge Jun 20, 2017
f9e99e1
[dist] update package-lock.json
jcrugzz Apr 19, 2018
b0a4489
Fix "Can't set headers after they are sent" errors
thiagobustamante May 26, 2017
74c07c9
issue #953: stop using writeHead
jfurler Jun 14, 2017
ad6bbd7
add support for modify response
guoxiangyang Jun 7, 2017
ebfee65
[test] add test for selfHandleRequest and remove modifyResponse as se…
jcrugzz Apr 20, 2018
ca9d4ce
[dist] doc updates
jcrugzz Apr 20, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -6,3 +6,5 @@ notes
primus-proxy.js
tes.js
npm-debug.log
.nyc_output
coverage
15 changes: 6 additions & 9 deletions .travis.yml
@@ -1,18 +1,15 @@
sudo: false
language: node_js
node_js:
- "0.10"
- "0.12"
- "4.2"
- "4"
- "6"

before_install:
- travis_retry npm install -g npm@2.14.5
- travis_retry npm install

- "8"
script:
- npm test

after_success:
- bash <(curl -s https://codecov.io/bash)
matrix:
fast_finish: true
notifications:
email:
- travis@nodejitsu.com
Expand Down
100 changes: 87 additions & 13 deletions README.md
Expand Up @@ -2,15 +2,7 @@
<img src="https://raw.github.com/nodejitsu/node-http-proxy/master/doc/logo.png"/>
</p>

node-http-proxy
=======

<p align="left">
<a href="https://travis-ci.org/nodejitsu/node-http-proxy" target="_blank">
<img src="https://travis-ci.org/nodejitsu/node-http-proxy.png"/></a>&nbsp;&nbsp;
<a href="https://coveralls.io/r/nodejitsu/node-http-proxy" target="_blank">
<img src="https://coveralls.io/repos/nodejitsu/node-http-proxy/badge.png"/></a>
</p>
# node-http-proxy [![Build Status](https://travis-ci.org/nodejitsu/node-http-proxy.svg?branch=master)](https://travis-ci.org/nodejitsu/node-http-proxy) [![codecov](https://codecov.io/gh/nodejitsu/node-http-proxy/branch/master/graph/badge.svg)](https://codecov.io/gh/nodejitsu/node-http-proxy)

`node-http-proxy` is an HTTP programmable proxying library that supports
websockets. It is suitable for implementing components such as reverse
Expand Down Expand Up @@ -125,7 +117,7 @@ http.createServer(function (req, res) {
**[Back to top](#table-of-contents)**

#### Setup a stand-alone proxy server with custom server logic
This example show how you can proxy a request using your own HTTP server
This example shows how you can proxy a request using your own HTTP server
and also you can put your own logic to handle the request.

```js
Expand Down Expand Up @@ -237,7 +229,7 @@ http.createServer(function (req, res) {
**[Back to top](#table-of-contents)**

#### Using HTTPS
You can activate the validation of a secure SSL certificate to the target connection (avoid self signed certs), just set `secure: true` in the options.
You can activate the validation of a secure SSL certificate to the target connection (avoid self-signed certs), just set `secure: true` in the options.

##### HTTPS -> HTTP

Expand Down Expand Up @@ -273,6 +265,24 @@ httpProxy.createServer({
}).listen(443);
```

##### HTTP -> HTTPS (using a PKCS12 client certificate)

```js
//
// Create an HTTP proxy server with an HTTPS target
//
httpProxy.createProxyServer({
target: {
protocol: 'https:',
host: 'my-domain-name',
port: 443,
pfx: fs.readFileSync('path/to/certificate.p12'),
passphrase: 'password',
},
changeOrigin: true,
}).listen(8000);
```

**[Back to top](#table-of-contents)**

#### Proxying WebSockets
Expand Down Expand Up @@ -341,7 +351,7 @@ proxyServer.listen(8015);
* **cookieDomainRewrite**: rewrites domain of `set-cookie` headers. Possible values:
* `false` (default): disable cookie rewriting
* String: new domain, for example `cookieDomainRewrite: "new.domain"`. To remove the domain, use `cookieDomainRewrite: ""`.
* Object: mapping of domains to new domains, use `"*"` to match all domains.
* Object: mapping of domains to new domains, use `"*"` to match all domains.
For example keep one domain unchanged, rewrite one domain and remove other domains:
```
cookieDomainRewrite: {
Expand All @@ -350,8 +360,41 @@ proxyServer.listen(8015);
"*": ""
}
```
* **cookiePathRewrite**: rewrites path of `set-cookie` headers. Possible values:
* `false` (default): disable cookie rewriting
* String: new path, for example `cookiePathRewrite: "/newPath/"`. To remove the path, use `cookiePathRewrite: ""`. To set path to root use `cookiePathRewrite: "/"`.
* Object: mapping of paths to new paths, use `"*"` to match all paths.
For example, to keep one path unchanged, rewrite one path and remove other paths:
```
cookiePathRewrite: {
"/unchanged.path/": "/unchanged.path/",
"/old.path/": "/new.path/",
"*": ""
}
```
* **headers**: object with extra headers to be added to target requests.
* **proxyTimeout**: timeout (in millis) when proxy receives no response from target
* **proxyTimeout**: timeout (in millis) for outgoing proxy requests
* **timeout**: timeout (in millis) for incoming requests
* **followRedirects**: true/false, Default: false - specify whether you want to follow redirects
* **selfHandleResponse** true/false, if set to true, none of the webOutgoing passes are called and it's your responsibility to appropriately return the response by listening and acting on the `proxyRes` event
* **buffer**: stream of data to send as the request body. Maybe you have some middleware that consumes the request stream before proxying it on e.g. If you read the body of a request into a field called 'req.rawbody' you could restream this field in the buffer option:

```
'use strict';

const streamify = require('stream-array');
const HttpProxy = require('http-proxy');
const proxy = new HttpProxy();

module.exports = (req, res, next) => {

proxy.web(req, res, {
target: 'http://localhost:4003/',
buffer: streamify(req.rawBody)
}, next);

};
```

**NOTE:**
`options.ws` and `options.ssl` are optional.
Expand All @@ -362,6 +405,7 @@ If you are using the `proxyServer.listen` method, the following options are also
* **ssl**: object to be passed to https.createServer()
* **ws**: true/false, if you want to proxy websockets


**[Back to top](#table-of-contents)**

### Listening for proxy events
Expand Down Expand Up @@ -442,6 +486,36 @@ proxy.close();

### Miscellaneous

If you want to handle your own response after receiving the `proxyRes`, you can do
so with `selfHandleResponse`. As you can see below, if you use this option, you
are able to intercept and read the `proxyRes` but you must also make sure to
reply to the `res` itself otherwise the original client will never receive any
data.

### Modify response

```js

var option = {
target: target,
selfHandleResponse : true
};
proxy.on('proxyRes', function (proxyRes, req, res) {
var body = new Buffer('');
proxyRes.on('data', function (data) {
body = Buffer.concat([body, data]);
});
proxyRes.on('end', function () {
body = body.toString();
console.log("res from proxied server:", body);
res.end("my response to cli");
});
});
proxy.web(req, res, option);


```

#### ProxyTable API

A proxy table API is available through this add-on [module](https://github.com/donasaur/http-proxy-rules), which lets you define a set of rules to translate matching routes to target routes that the reverse proxy will talk to.
Expand Down
10 changes: 10 additions & 0 deletions codecov.yml
@@ -0,0 +1,10 @@
coverage:
parsers:
javascript:
enable_partials: yes
status:
project:
default:
target: "70%"
patch:
enabled: false
29 changes: 14 additions & 15 deletions lib/http-proxy/common.js
Expand Up @@ -4,8 +4,7 @@ var common = exports,
required = require('requires-port');

var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i,
isSSL = /^https|wss/,
cookieDomainRegex = /(;\s*domain=)([^;]+)/i;
isSSL = /^https|wss/;

/**
* Simple Regex for testing if protocol is https
Expand Down Expand Up @@ -40,7 +39,7 @@ common.setupOutgoing = function(outgoing, options, req, forward) {
function(e) { outgoing[e] = options[forward || 'target'][e]; }
);

outgoing.method = req.method;
outgoing.method = options.method || req.method;
outgoing.headers = extend({}, req.headers);

if (options.headers){
Expand Down Expand Up @@ -211,27 +210,27 @@ common.urlJoin = function() {
*
* @api private
*/
common.rewriteCookieDomain = function rewriteCookieDomain(header, config) {
common.rewriteCookieProperty = function rewriteCookieProperty(header, config, property) {
if (Array.isArray(header)) {
return header.map(function (headerElement) {
return rewriteCookieDomain(headerElement, config);
return rewriteCookieProperty(headerElement, config, property);
});
}
return header.replace(cookieDomainRegex, function(match, prefix, previousDomain) {
var newDomain;
if (previousDomain in config) {
newDomain = config[previousDomain];
return header.replace(new RegExp("(;\\s*" + property + "=)([^;]+)", 'i'), function(match, prefix, previousValue) {
var newValue;
if (previousValue in config) {
newValue = config[previousValue];
} else if ('*' in config) {
newDomain = config['*'];
newValue = config['*'];
} else {
//no match, return previous domain
//no match, return previous value
return match;
}
if (newDomain) {
//replace domain
return prefix + newDomain;
if (newValue) {
//replace value
return prefix + newValue;
} else {
//remove domain
//remove value
return '';
}
});
Expand Down
38 changes: 25 additions & 13 deletions lib/http-proxy/passes/web-incoming.js
@@ -1,12 +1,15 @@
var http = require('http'),
https = require('https'),
var httpNative = require('http'),
httpsNative = require('https'),
web_o = require('./web-outgoing'),
common = require('../common');
common = require('../common'),
followRedirects = require('follow-redirects');

web_o = Object.keys(web_o).map(function(pass) {
return web_o[pass];
});

var nativeAgents = { http: httpNative, https: httpsNative };

/*!
* Array of passes.
*
Expand Down Expand Up @@ -99,6 +102,10 @@ module.exports = {
// And we begin!
server.emit('start', req, res, options.target || options.forward);

var agents = options.followRedirects ? followRedirects : nativeAgents;
var http = agents.http;
var https = agents.https;

if(options.forward) {
// If forward enable, so just pipe the request
var forwardReq = (options.forward.protocol === 'https:' ? https : http).request(
Expand Down Expand Up @@ -162,19 +169,24 @@ module.exports = {

proxyReq.on('response', function(proxyRes) {
if(server) { server.emit('proxyRes', proxyRes, req, res); }
for(var i=0; i < web_o.length; i++) {
if(web_o[i](req, res, proxyRes, options)) { break; }
}

// Allow us to listen when the proxy has completed
proxyRes.on('end', function () {
server.emit('end', req, res, proxyRes);
});
if(!res.headersSent && !options.selfHandleResponse) {
for(var i=0; i < web_o.length; i++) {
if(web_o[i](req, res, proxyRes, options)) { break; }
}
}

proxyRes.pipe(res);
if (!res.finished) {
// Allow us to listen when the proxy has completed
proxyRes.on('end', function () {
if (server) server.emit('end', req, res, proxyRes);
});
// We pipe to the response unless its expected to be handled by the user
if (!options.selfHandleResponse) proxyRes.pipe(res);
} else {
if (server) server.emit('end', req, res, proxyRes);
}
});

//proxyReq.end();
}

};
15 changes: 12 additions & 3 deletions lib/http-proxy/passes/web-outgoing.js
Expand Up @@ -84,12 +84,16 @@ module.exports = { // <--
*/
writeHeaders: function writeHeaders(req, res, proxyRes, options) {
var rewriteCookieDomainConfig = options.cookieDomainRewrite,
rewriteCookiePathConfig = options.cookiePathRewrite,
preserveHeaderKeyCase = options.preserveHeaderKeyCase,
rawHeaderKeyMap,
setHeader = function(key, header) {
if (header == undefined) return;
if (rewriteCookieDomainConfig && key.toLowerCase() === 'set-cookie') {
header = common.rewriteCookieDomain(header, rewriteCookieDomainConfig);
header = common.rewriteCookieProperty(header, rewriteCookieDomainConfig, 'domain');
}
if (rewriteCookiePathConfig && key.toLowerCase() === 'set-cookie') {
header = common.rewriteCookieProperty(header, rewriteCookiePathConfig, 'path');
}
res.setHeader(String(key).trim(), header);
};
Expand All @@ -98,6 +102,10 @@ module.exports = { // <--
rewriteCookieDomainConfig = { '*': rewriteCookieDomainConfig };
}

if (typeof rewriteCookiePathConfig === 'string') { //also test for ''
rewriteCookiePathConfig = { '*': rewriteCookiePathConfig };
}

// message.rawHeaders is added in: v0.11.6
// https://nodejs.org/api/http.html#http_message_rawheaders
if (preserveHeaderKeyCase && proxyRes.rawHeaders != undefined) {
Expand Down Expand Up @@ -129,9 +137,10 @@ module.exports = { // <--
writeStatusCode: function writeStatusCode(req, res, proxyRes) {
// From Node.js docs: response.writeHead(statusCode[, statusMessage][, headers])
if(proxyRes.statusMessage) {
res.writeHead(proxyRes.statusCode, proxyRes.statusMessage);
res.statusCode = proxyRes.statusCode;
res.statusMessage = proxyRes.statusMessage;
} else {
res.writeHead(proxyRes.statusCode);
res.statusCode = proxyRes.statusCode;
}
}

Expand Down