Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Fix 'connection' header, copying of headers, and support Windows named pipes #294

Closed
wants to merge 2 commits into from

4 participants

@gilad61

This pull request is to replace #260 (#260)

It contains the same changes + TESTS:
1. Set the connection header only if not already set by the target server.
2. Copy response headers case-insensitively

In addition it contains another fix:
support unix domain sockets and windows named pipes (socketPath) - This was transparent in node 0.6.x by using port for named pipe, but in node 0.8.x it should be passed explicitly.

yosefd and others added some commits
@yosefd yosefd - support unix donain sockets and windows named pipes (socketPath) on…
… node 0.8.x. On node 0.6.x the support was opaque via port, but on the new node, socketPath should be set explicitely.

- avoid adding multiple headers with the same name
- fix bug in setting connection
3438e54
@gilad61 gilad61 Add tests for headers bug fixes 876157f
@mmalecki

Doing a copy of headers on every request isn't a good thing to do - I'm worried about memory usage. Can you explain exactly why is it needed (an example would be appreciated)? Test case doesn't tell me much.

@gilad61

If response headers are copied using the res.writeHead() function, then the copy is case sensitive. It means that eventually we may have two headers with same name but different casing, for example: x-My-Header: "a", x-my-header: "b", and then it is unpredictable which value will be taken by the client, and doesn't meet HTTP standard.
HTTP headers are case insensitive, so the copy needs to be done with res.setHeader(), which overrides existing headers case-insensitively.

I don't see a difference in mem usage as in one case we copy all the headers at once, and in another case we copy them one by one.
I would even say the opposite - if you use the current implementation you'll use more memory.
For example:
If I have "myheader: x", and then I get a request with "MYHEADER: y", then I'll end up with two headers. And same thing if I get another header "MyHeader: z", and "MyHeAdEr: a" and so on...
But - if I copy them with setHeader() then the headers will be overridden and I will have only one header with the same name.

@indexzero
Owner

@gilad61 @mmalecki This is a subtle but good fix; we should be setting header names consistently. res._renderHeaders() is still case sensitive even in the latest stable versions of node:

@indexzero
Owner

I spoke with @isaacs, this behavior is not going to change even in 0.10.0, but likely in 0.12.0. So going to pull this in if all the tests pass.

@indexzero
Owner

Cherry-picked. Thanks

@indexzero indexzero closed this
@indexzero indexzero referenced this pull request
Closed

Unix socket support #104

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 29, 2012
  1. @yosefd

    - support unix donain sockets and windows named pipes (socketPath) on…

    yosefd authored
    … node 0.8.x. On node 0.6.x the support was opaque via port, but on the new node, socketPath should be set explicitely.
    
    - avoid adding multiple headers with the same name
    - fix bug in setting connection
Commits on Jul 31, 2012
  1. @gilad61
This page is out of date. Refresh to see the latest.
View
22 lib/node-http-proxy/http-proxy.js
@@ -209,13 +209,14 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) {
//
// Setup outgoing proxy with relevant properties.
//
- outgoing.host = this.target.host;
- outgoing.hostname = this.target.hostname;
- outgoing.port = this.target.port;
- outgoing.agent = this.target.agent;
- outgoing.method = req.method;
- outgoing.path = req.url;
- outgoing.headers = req.headers;
+ outgoing.host = this.target.host;
+ outgoing.hostname = this.target.hostname;
+ outgoing.port = this.target.port;
+ outgoing.socketPath = this.target.socketPath;
+ outgoing.agent = this.target.agent;
+ outgoing.method = req.method;
+ outgoing.path = req.url;
+ outgoing.headers = req.headers;
//
// If the changeOrigin option is specified, change the
@@ -234,7 +235,7 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) {
//
// Process the `reverseProxy` `response` when it's received.
//
- if (response.headers.connection) {
+ if (!response.headers.connection) {
if (req.headers.connection) { response.headers.connection = req.headers.connection }
else { response.headers.connection = 'close' }
}
@@ -254,7 +255,10 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) {
}
// Set the headers of the client response
- res.writeHead(response.statusCode, response.headers);
+ Object.keys(response.headers).forEach(function(key){
+ res.setHeader(key, response.headers[key]);
+ });
+ res.writeHead(response.statusCode);
// If `response.statusCode === 304`: No 'data' event and no 'end'
if (response.statusCode === 304) {
View
11 lib/node-http-proxy/routing-proxy.js
@@ -85,11 +85,12 @@ RoutingProxy.prototype.add = function (options) {
//
// TODO: Consume properties in `options` related to the `ProxyTable`.
//
- options.target = options.target || {};
- options.target.host = options.target.host || options.host;
- options.target.port = options.target.port || options.port;
- options.target.https = this.target && this.target.https ||
- options.target && options.target.https;
+ options.target = options.target || {};
+ options.target.host = options.target.host || options.host;
+ options.target.port = options.target.port || options.port;
+ options.target.socketPath = options.target.socketPath || options.socketPath;
+ options.target.https = this.target && this.target.https ||
+ options.target && options.target.https;
//
// Setup options to pass-thru to the new `HttpProxy` instance
View
11 test/helpers/http.js
@@ -61,6 +61,12 @@ exports.createServer = function (options, callback) {
});
}
+ if (options.outputHeaders){
+ Object.keys(options.outputHeaders).forEach(function(header){
+ res.setHeader(header, options.outputHeaders[header]);
+ });
+ }
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write(options.output || 'hello proxy');
res.end();
@@ -113,6 +119,11 @@ exports.createProxyServer = function (options, callback) {
function requestHandler(req, res) {
var buffer = httpProxy.buffer(req);
+ if (options.outputHeaders){
+ Object.keys(options.outputHeaders).forEach(function(header){
+ res.setHeader(header, options.outputHeaders[header]);
+ });
+ }
setTimeout(function () {
//
// Setup options dynamically for `RoutingProxy.prototype.proxyRequest`
View
36 test/http/http-test.js
@@ -42,11 +42,41 @@ vows.describe(helpers.describe()).addBatch({
"and headers": macros.http.assertProxied({
request: { headers: { host: 'unknown.com' } }
}),
+ "and request close connection header": macros.http.assertProxied({
+ request: { headers: { connection: "close" } },
+ outputHeaders: { connection: "close" }
+ }),
+ "and request keep alive connection header": macros.http.assertProxied({
+ request: { headers: { connection: "keep-alive" } },
+ outputHeaders: { connection: "keep-alive" }
+ }),
+ "and response close connection header": macros.http.assertProxied({
+ request: { headers: { connection: "" } }, // Must explicitly set to "" because otherwise node will automatically add a "connection: keep-alive" header
+ targetHeaders: { connection: "close" },
+ outputHeaders: { connection: "close" }
+ }),
+ "and response keep-alive connection header": macros.http.assertProxied({
+ request: { headers: { connection: "" } }, // Must explicitly set to "" because otherwise node will automatically add a "connection: keep-alive" header
+ targetHeaders: { connection: "keep-alive" },
+ outputHeaders: { connection: "keep-alive" }
+ }),
+ "and no connection header": macros.http.assertProxied({
+ request: { headers: { connection: "" } }, // Must explicitly set to "" because otherwise node will automatically add a "connection: keep-alive" header
+ outputHeaders: { connection: "keep-alive" }
+ }),
"and forwarding enabled": macros.http.assertForwardProxied()
},
- "and latency": macros.http.assertProxied({
- latency: 2000
- })
+ "and latency": {
+ "and no headers": macros.http.assertProxied({
+ latency: 2000
+ }),
+ "and response headers": macros.http.assertProxied({
+ targetHeaders: { "x-testheader": "target" },
+ proxyHeaders: { "X-TestHeader": "proxy" },
+ outputHeaders: { "x-testheader": "target" },
+ latency: 1000
+ })
+ }
},
"With a no valid target server": {
"and no latency": macros.http.assertInvalidProxy(),
View
21 test/macros/http.js
@@ -31,6 +31,12 @@ exports.assertRequest = function (options) {
},
"should succeed": function (err, res, body) {
assert.isNull(err);
+ if (options.assert.headers) {
+ Object.keys(options.assert.headers).forEach(function(header){
+ assert.equal(res.headers[header], options.assert.headers[header]);
+ });
+ }
+
if (options.assert.body) {
assert.equal(body, options.assert.body);
}
@@ -56,10 +62,14 @@ exports.assertRequest = function (options) {
exports.assertProxied = function (options) {
options = options || {};
- var ports = options.ports || helpers.nextPortPair,
- output = options.output || 'hello world from ' + ports.target,
- protocol = helpers.protocols.proxy,
- req = options.request || {};
+ var ports = options.ports || helpers.nextPortPair,
+ output = options.output || 'hello world from ' + ports.target,
+ outputHeaders = options.outputHeaders,
+ targetHeaders = options.targetHeaders,
+ proxyHeaders = options.proxyHeaders,
+ protocol = helpers.protocols.proxy,
+ req = options.request || {};
+
req.uri = req.uri || protocol + '://127.0.0.1:' + ports.proxy;
@@ -72,12 +82,14 @@ exports.assertProxied = function (options) {
helpers.http.createServerPair({
target: {
output: output,
+ outputHeaders: targetHeaders,
port: ports.target,
headers: req.headers
},
proxy: {
latency: options.latency,
port: ports.proxy,
+ outputHeaders: proxyHeaders,
proxy: {
forward: options.forward,
target: {
@@ -92,6 +104,7 @@ exports.assertProxied = function (options) {
"the proxy request": exports.assertRequest({
request: req,
assert: {
+ headers: outputHeaders,
body: output
}
})
Something went wrong with that request. Please try again.