Skip to content

Commit

Permalink
feat(proxy): allow contextless proxy configuration (chimurai#70)
Browse files Browse the repository at this point in the history
* feat(proxy): allow contextless proxy configuration

* fix(connect mounting): use connect's `path` configuration to mount proxy

improve old fix: chimurai#17
  • Loading branch information
chimurai committed Apr 18, 2016
1 parent aa89e1f commit 1199174
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 67 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [v0.x.x](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.x.x)
- feat(proxy): support proxy creation without context.
- fix(connect mounting): use connect's `path` configuration to mount proxy.

## [v0.13.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.13.0)
- feat(context): custom context matcher; when simple `path` matching is not sufficient.

Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ $ npm install --save-dev http-proxy-middleware
## Core concept

Configure the proxy middleware.

```javascript
var proxy = require('http-proxy-middleware');

Expand Down Expand Up @@ -150,6 +151,19 @@ proxy('http://www.example.org:8000/api', {changeOrigin:true});
// proxy('/api', {target: 'http://www.example.org:8000', changeOrigin: true});
```

### app.use(path, proxy)

If you want to use the server's `app.use` `path` parameter to match requests;
Create and mount the proxy without the http-proxy-middleware `context` parameter:
```javascript
app.use('/api', proxy({target:'http://www.example.org', changeOrigin:true}));
```

`app.use` documentation:
* express: http://expressjs.com/en/4x/api.html#app.use
* connect: https://github.com/senchalabs/connect#mount-middleware

## WebSocket

```javascript
Expand Down Expand Up @@ -227,6 +241,9 @@ var server = app.listen(3000);
}
```

### Events
Subscribe to [http-proxy events](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events):

* **option.onError**: function, subscribe to http-proxy's `error` event for custom error handling.
```javascript
function onError(err, req, res) {
Expand Down Expand Up @@ -280,7 +297,9 @@ var server = app.listen(3000);
* (DEPRECATED) **option.proxyHost**: Use `option.changeOrigin = true` instead.
The following options are provided by the underlying [http-proxy](https://www.npmjs.com/package/http-proxy).
### http-proxy options
The following options are provided by the underlying [http-proxy](https://github.com/nodejitsu/node-http-proxy#options).
* **option.target**: url string to be parsed with the url module
* **option.forward**: url string to be parsed with the url module
Expand Down
4 changes: 1 addition & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ var httpProxyMiddleware = function(context, opts) {

function middleware(req, res, next) {
// https://github.com/chimurai/http-proxy-middleware/issues/17
if (req.baseUrl) {
req.url = req.originalUrl;
}
req.url = req.originalUrl;

if (contextMatcher.match(config.context, req.url, req)) {
var activeProxyOptions = prepareProxyRequest(req);
Expand Down
43 changes: 37 additions & 6 deletions lib/config-factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ function createConfig(context, opts) {
options: {}
};

var useShortHand = isShortHand(context);

if (useShortHand) {
// app.use('/api', proxy({target:'http://localhost:9000'}));
if (isContexless(context, opts)) {
config.context = '/';
config.options = _.assign(config.options, context);
}
// app.use('/api', proxy('http://localhost:9000'));
// app.use(proxy('http://localhost:9000/api'));
else if (isStringShortHand(context)) {
var oUrl = url.parse(context);
var target = [oUrl.protocol, '//', oUrl.host].join('');

Expand All @@ -25,7 +30,7 @@ function createConfig(context, opts) {
if (oUrl.protocol === 'ws:' || oUrl.protocol === 'wss:') {
config.options.ws = true;
}

// app.use('/api', proxy({target:'http://localhost:9000'}));
} else {
config.context = context;
config.options = _.assign(config.options, opts);
Expand All @@ -41,14 +46,40 @@ function createConfig(context, opts) {
config.options = mapLegacyProxyHostOption(config.options);

return config;
};
}

function isShortHand(context) {
/**
* Checks if a String only target/config is provided.
* This can be just the host or with the optional path.
*
* @example
* app.use('/api', proxy('http://localhost:9000'));
app.use(proxy('http://localhost:9000/api'));
*
* @param {String} context [description]
* @return {Boolean} [description]
*/
function isStringShortHand(context) {
if (_.isString(context)) {
return (url.parse(context).host) ? true : false;
}
}

/**
* Checks if a Object only config is provided, without a context.
* In this case the all paths will be proxied.
*
* @example
* app.use('/api', proxy({target:'http://localhost:9000'}));
*
* @param {Object} context [description]
* @param {*} opts [description]
* @return {Boolean} [description]
*/
function isContexless(context, opts) {
return (_.isPlainObject(context) && _.isEmpty(opts));
}

function mapLegacyProxyHostOption(options) {
// set options.headers.host when option.proxyHost is provided
if (options.proxyHost) {
Expand Down
20 changes: 20 additions & 0 deletions recipes/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,23 @@ var apiProxy = proxy('/api', {target: 'http://localhost:3000'});
// | |
// context options
```

## Alternative configuration

The proxy behavior of the following examples are **exactly** the same; Just different ways to configure it.

```javascript
app.use(proxy('/api', {target: 'http://localhost:3000', changeOrigin:true}));
```

```javascript
app.use(proxy('http://localhost:3000/api', {changeOrigin:true}));
```

```javascript
app.use('/api', proxy('http://localhost:3000', {changeOrigin:true}));
```

```javascript
app.use('/api', proxy({target: 'http://localhost:3000', changeOrigin:true}));
```
129 changes: 73 additions & 56 deletions test/config-factory.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,111 +3,128 @@ var configFactory = require('../lib/config-factory');

describe('configFactory', function() {
var result;
var createConfig = configFactory.createConfig;

describe('createConfig()', function() {

describe('classic api', function() {
describe('classic config', function() {
var context = '/api';
var options = {target: 'http://www.example.org'};

beforeEach(function() {
result = configFactory.createConfig(context, options);
result = createConfig(context, options);
});

it('should return on config object', function() {
it('should return config object', function() {
expect(result).to.have.all.keys('context', 'options');
});

it('should return on config object with context', function() {
it('should return config object with context', function() {
expect(result.context).to.equal(context);
});

it('should return on config object with options', function() {
it('should return config object with options', function() {
expect(result.options).to.deep.equal(options);
});
});

describe('shorthand api', function() {
beforeEach(function() {
result = configFactory.createConfig('http://www.example.org:8000/api');
});
describe('shorthand String', function() {
describe('shorthand String config', function() {
beforeEach(function() {
result = createConfig('http://www.example.org:8000/api');
});

it('should return on config object', function() {
expect(result).to.have.all.keys('context', 'options');
});
it('should return config object', function() {
expect(result).to.have.all.keys('context', 'options');
});

it('should return on config object with context', function() {
expect(result.context).to.equal('/api');
});
it('should return config object with context', function() {
expect(result.context).to.equal('/api');
});

it('should return on config object with options', function() {
expect(result.options).to.deep.equal({target: 'http://www.example.org:8000'});
it('should return config object with options', function() {
expect(result.options).to.deep.equal({target: 'http://www.example.org:8000'});
});
});
});

describe('shorthand api for whole domain', function() {
beforeEach(function() {
result = configFactory.createConfig('http://www.example.org:8000');
});
describe('shorthand String config for whole domain', function() {
beforeEach(function() {
result = createConfig('http://www.example.org:8000');
});

it('should return on config object with context', function() {
expect(result.context).to.equal('/');
it('should return config object with context', function() {
expect(result.context).to.equal('/');
});
});
});

describe('shorthand api for websocket url', function() {
beforeEach(function() {
result = configFactory.createConfig('ws://www.example.org:8000');
});
describe('shorthand String config for websocket url', function() {
beforeEach(function() {
result = createConfig('ws://www.example.org:8000');
});

it('should return on config object with context', function() {
expect(result.context).to.equal('/');
});
it('should return config object with context', function() {
expect(result.context).to.equal('/');
});

it('should return on options with ws = true', function() {
expect(result.options.ws).to.equal(true);
it('should return options with ws = true', function() {
expect(result.options.ws).to.equal(true);
});
});
});

describe('shorthand api for secure websocket url', function() {
beforeEach(function() {
result = configFactory.createConfig('wss://www.example.org:8000');
});
describe('shorthand String config for secure websocket url', function() {
beforeEach(function() {
result = createConfig('wss://www.example.org:8000');
});

it('should return on config object with context', function() {
expect(result.context).to.equal('/');
});
it('should return config object with context', function() {
expect(result.context).to.equal('/');
});

it('should return on options with ws = true', function() {
expect(result.options.ws).to.equal(true);
it('should return options with ws = true', function() {
expect(result.options.ws).to.equal(true);
});
});
});

describe('shorthand api with globbing', function() {
beforeEach(function() {
result = configFactory.createConfig('http://www.example.org:8000/api/*.json');
describe('shorthand String config with globbing', function() {
beforeEach(function() {
result = createConfig('http://www.example.org:8000/api/*.json');
});

it('should return config object with context', function() {
expect(result.context).to.equal('/api/*.json');
});
});

it('should return on config object with context', function() {
expect(result.context).to.equal('/api/*.json');
describe('shorthand String config with options', function() {
beforeEach(function() {
result = createConfig('http://www.example.org:8000/api', {changeOrigin: true});
});

it('should return config object with additional options', function() {
expect(result.options).to.deep.equal({target: 'http://www.example.org:8000', changeOrigin: true});
});
});
});

describe('shorthand api with options', function() {
describe('shorthand Object config', function() {
beforeEach(function() {
result = configFactory.createConfig('http://www.example.org:8000/api', {changeOrigin: true});
result = createConfig({target: 'http://www.example.org:8000'});
});

it('should return on config object with additional options', function() {
expect(result.options).to.deep.equal({target: 'http://www.example.org:8000', changeOrigin: true});
it('should set the proxy path to everything', function() {
expect(result.context).to.equal('/');
});

it('should return config object', function() {
expect(result.options).to.deep.equal({target: 'http://www.example.org:8000'});
});
});

describe('missing option.target', function() {
var fn;
beforeEach(function() {
fn = function() {
configFactory.createConfig('/api');
createConfig('/api');
};
});

Expand All @@ -119,7 +136,7 @@ describe('configFactory', function() {
describe('faulty config. mixing classic with shorthand', function() {
var fn;
beforeEach(function() {
result = configFactory.createConfig('http://localhost:3000/api', {target: 'http://localhost:8000'});
result = createConfig('http://localhost:3000/api', {target: 'http://localhost:8000'});
});

it('should use the target in the configuration as target', function() {
Expand Down
2 changes: 1 addition & 1 deletion test/http-proxy-middleware.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('context matching', function() {

var middleware;

var mockReq = {url: '/foo/bar'};
var mockReq = {url: '/foo/bar', originalUrl: '/foo/bar'};
var mockRes = {};
var mockNext = function() {
// mockNext will be called when request is not proxied
Expand Down

0 comments on commit 1199174

Please sign in to comment.