Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 55 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# express-http-proxy [![NPM version](https://badge.fury.io/js/express-http-proxy.svg)](http://badge.fury.io/js/express-http-proxy) [![Build Status](https://travis-ci.org/villadora/express-http-proxy.svg?branch=master)](https://travis-ci.org/villadora/express-http-proxy)
# express-http-proxy [![NPM version](https://badge.fury.io/js/express-http-proxy.svg)](http://badge.fury.io/js/express-http-proxy) [![Build Status](https://travis-ci.org/villadora/express-http-proxy.svg?branch=master)](https://travis-ci.org/villadora/express-http-proxy)


Express middleware to proxy request to another host and pass response back to original caller.
Expand All @@ -24,6 +24,51 @@ var app = require('express')();
app.use('/proxy', proxy('www.google.com'));
```

### 30k view

The proxy middleware:
* proxies request to your server to an arbitrary server, and
* provide hooks to decorate and filter requests to the proxy target, and
* provide hooks you to decorate and filter proxy responses before returning them to the client.
Copy link

Copilot AI May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in README: change 'provide hooks you to decorate' to 'provide hooks to decorate' for improved clarity.

Suggested change
* provide hooks you to decorate and filter proxy responses before returning them to the client.
* provide hooks to decorate and filter proxy responses before returning them to the client.

Copilot uses AI. Check for mistakes.

```

Client Express App Proxy Middleware Target Server
| | | |
| HTTP Request | | |
|-------------------------->| | |
| | Request | |
| |--------------------------->| |
| | | +------------------------+ |
| | | | Request Preprocessing | |
| | | | 1. filter requests | |
| | | | 2. resolve proxy host | |
| | | | 3. decorate proxy opts | |
| | | | 4. decorate proxy req | |
| | | | 5. resolve req path | |
| | | +------------------------+ |
| | | Forwarded Request |
| | |---------------------------->|
| | | |
| | | Response with Headers |
| | |<----------------------------|
| | | |
| | | +------------------------+ |
| | | | Response Processing | |
| | | | 1. skip to next? | |
| | | | 2. copy proxy headers | |
| | | | 3. decorate headers | |
| | | | 4. decorate response | |
| | | +------------------------+ |
| | | |
| | Modified Response | |
| |<---------------------------| |
| Final Response | | |
|<--------------------------| | |
| | | |

```

### Streaming

Proxy requests and user responses are piped/streamed/chunked by default.
Expand Down Expand Up @@ -74,7 +119,7 @@ function selectProxyHost() {
app.use('/', proxy(selectProxyHost));
```

Notie: Host is only the host name. Any params after in url will be ignored. For ``http://google.com/myPath`, ``myPath`` will be ignored because the host name is ``google.com``.
Notie: Host is only the host name. Any params after in url will be ignored. For ``http://google.com/myPath`, ``myPath`` will be ignored because the host name is ``google.com``.
See ``proxyReqPathResolver`` for more detailed path information.


Expand Down Expand Up @@ -172,17 +217,17 @@ Promise form:

```js
app.use(proxy('localhost:12346', {
filter: function (req, res) {
return new Promise(function (resolve) {
filter: function (req, res) {
return new Promise(function (resolve) {
resolve(req.method === 'GET');
});
});
}
}));
```

Note that in the previous example, `resolve(false)` will execute the happy path
for filter here (skipping the rest of the proxy, and calling `next()`).
`reject()` will also skip the rest of proxy and call `next()`.
`reject()` will also skip the rest of proxy and call `next()`.

#### userResDecorator (was: intercept) (supports Promise)

Expand Down Expand Up @@ -276,8 +321,8 @@ first request.

When a `userResHeaderDecorator` is defined, the return of this method will replace (rather than be merged on to) the headers for `userRes`.

> Note that by default, headers from the PROXY response CLOBBER all headers that may have previously been set on the userResponse.
> Authors have the option of constructing any combination of proxyRes and userRes headers in the `userResHeaderDecorator`.
> Note that by default, headers from the PROXY response CLOBBER all headers that may have previously been set on the userResponse.
> Authors have the option of constructing any combination of proxyRes and userRes headers in the `userResHeaderDecorator`.
> Check the tests for this method for examples.


Expand Down Expand Up @@ -608,9 +653,9 @@ app.use('/', proxy('internalhost.example.com', {
| 1.6.0 | Do gzip and gunzip aysyncronously. Test and documentation improvements, dependency updates. |
| 1.5.1 | Fixes bug in stringifying debug messages. |
| 1.5.0 | Fixes bug in `filter` signature. Fix bug in skipToNextHandler, add expressHttpProxy value to user res when skipped. Add tests for host as ip address. |
| 1.4.0 | DEPRECATED. Critical bug in the `filter` api.|
| 1.4.0 | DEPRECATED. Critical bug in the `filter` api.|
| 1.3.0 | DEPRECATED. Critical bug in the `filter` api. `filter` now supports Promises. Update linter to eslint. |
| 1.2.0 | Auto-stream when no decorations are made to req/res. Improved docs, fixes issues in maybeSkipToNexthandler, allow authors to manage error handling. |
| 1.2.0 | Auto-stream when no decorations are made to req/res. Improved docs, fixes issues in maybeSkipToNexthandler, allow authors to manage error handling. |
| 1.1.0 | Add step to allow response headers to be modified.
| 1.0.7 | Update dependencies. Improve docs on promise rejection. Fix promise rejection on body limit. Improve debug output. |
| 1.0.6 | Fixes preserveHostHdr not working, skip userResDecorator on 304, add maybeSkipToNext, test improvements and cleanup. |
Expand Down
145 changes: 71 additions & 74 deletions test/decorateUserResHeaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,11 @@ var assert = require('assert');
var express = require('express');
var request = require('supertest');
var proxy = require('../');
var proxyTarget = require('./support/proxyTarget');
var TIMEOUT = require('./constants');

describe('when userResHeaderDecorator is defined', function () {
this.timeout(TIMEOUT.STANDARD);

describe('userResHeaderDecorator', function () {
var app;
var serverReference;

afterEach(function () {
serverReference.close();
});
var serverReference;

beforeEach(function () {
app = express();
Expand All @@ -32,80 +25,84 @@ describe('when userResHeaderDecorator is defined', function () {
serverReference.close();
});

it('can delete a header', function (done) {
app.use('/proxy', proxy('http://127.0.0.1:12345', {
userResHeaderDecorator: function (headers /*, userReq, userRes, proxyReq, proxyRes */) {
delete headers['x-my-secret-header'];
return headers;
}
}));
describe('header modification', function () {
it('can remove headers from the response', function (done) {
app.use('/proxy', proxy('http://127.0.0.1:12345', {
userResHeaderDecorator: function (headers) {
delete headers['x-my-secret-header'];
return headers;
}
}));

app.use(function (req, res) {
res.sendStatus(200);
request(app)
.get('/proxy')
.expect(function (res) {
assert(Object.keys(res.headers).indexOf('x-my-not-so-secret-header') > -1);
assert(Object.keys(res.headers).indexOf('x-my-secret-header') === -1);
})
.end(done);
});

request(app)
.get('/proxy')
.expect(function (res) {
assert(Object.keys(res.headers).indexOf('x-my-not-so-secret-header') > -1);
assert(Object.keys(res.headers).indexOf('x-my-secret-header') === -1);
})
.end(done);
});

it('provides an interface for updating headers', function (done) {
app.use('/proxy', proxy('http://127.0.0.1:12345', {
userResHeaderDecorator: function (headers /*, userReq, userRes, proxyReq, proxyRes */) {
headers.boltedonheader = 'franky';
return headers;
}
}));
it('can add new headers to the response', function (done) {
app.use('/proxy', proxy('http://127.0.0.1:12345', {
userResHeaderDecorator: function (headers) {
headers.boltedonheader = 'franky';
return headers;
}
}));

app.use(function (req, res) {
res.sendStatus(200);
request(app)
.get('/proxy')
.expect(function (res) {
assert(res.headers.boltedonheader === 'franky');
})
.end(done);
});

request(app)
.get('/proxy')
.expect(function (res) {
assert(res.headers.boltedonheader === 'franky');
})
.end(done);
});

it('author has option to copy proxyResponse headers to userResponse', function (done) {
app.use('/proxy', proxy('http://127.0.0.1:12345', {
userResHeaderDecorator: function (headers, userReq) { // proxyReq
// Copy specific headers from the proxy request to the user response
//
// We can copy them to new name
if (userReq.headers['x-custom-header']) {
headers['x-proxied-custom-header'] = userReq.headers['x-custom-header'];
}
if (userReq.headers['x-user-agent']) {
headers['x-proxied-user-agent'] = userReq.headers['x-user-agent'];
describe('header proxying', function () {
it('can copy request headers to response with new names', function (done) {
app.use('/proxy', proxy('http://127.0.0.1:12345', {
userResHeaderDecorator: function (headers, userReq) {
if (userReq.headers['x-custom-header']) {
headers['x-proxied-custom-header'] = userReq.headers['x-custom-header'];
}
if (userReq.headers['x-user-agent']) {
headers['x-proxied-user-agent'] = userReq.headers['x-user-agent'];
}
return headers;
}
}));

// We can copy them to the same name
headers['x-copied-header-1'] = userReq.headers['x-copied-header-1'];
headers['x-copied-header-2'] = userReq.headers['x-copied-header-2'];
return headers;
}
}));
request(app)
.get('/proxy')
.set('x-custom-header', 'custom-value')
.set('x-user-agent', 'test-agent')
.expect(function (res) {
assert.equal(res.headers['x-proxied-custom-header'], 'custom-value');
assert.equal(res.headers['x-proxied-user-agent'], 'test-agent');
})
.end(done);
});

request(app)
.get('/proxy')
.set('x-custom-header', 'custom-value')
.set('x-user-agent', 'test-agent')
.set('x-copied-header-1', 'value1')
.set('x-copied-header-2', 'value2')
.expect(function (res) {
// Verify the original headers were proxied to the response
assert.equal(res.headers['x-proxied-custom-header'], 'custom-value');
assert.equal(res.headers['x-proxied-user-agent'], 'test-agent');
assert.equal(res.headers['x-copied-header-1'], 'value1');
assert.equal(res.headers['x-copied-header-2'], 'value2');
})
.end(done);
it('can copy request headers to response with same names', function (done) {
app.use('/proxy', proxy('http://127.0.0.1:12345', {
userResHeaderDecorator: function (headers, userReq) {
headers['x-copied-header-1'] = userReq.headers['x-copied-header-1'];
headers['x-copied-header-2'] = userReq.headers['x-copied-header-2'];
return headers;
}
}));

request(app)
.get('/proxy')
.set('x-copied-header-1', 'value1')
.set('x-copied-header-2', 'value2')
.expect(function (res) {
assert.equal(res.headers['x-copied-header-1'], 'value1');
assert.equal(res.headers['x-copied-header-2'], 'value2');
})
.end(done);
});
});
});
Loading
Loading