Skip to content
Merged
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
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
[![Coveralls](https://img.shields.io/coveralls/chimurai/http-proxy-middleware.svg?style=flat-square)](https://coveralls.io/r/chimurai/http-proxy-middleware)
[![dependency Status](https://img.shields.io/david/chimurai/http-proxy-middleware.svg?style=flat-square)](https://david-dm.org/chimurai/http-proxy-middleware#info=devDependencies)
[![devDependency Status](https://img.shields.io/david/dev/chimurai/http-proxy-middleware.svg?style=flat-square)](https://david-dm.org/chimurai/http-proxy-middleware#info=devDependencies)
[![MIT license](https://img.shields.io/npm/l/http-proxy-middleware.svg?style=flat-square)](https://www.npmjs.com/package/http-proxy-middleware)

The one-liner proxy middleware for [connect](https://github.com/senchalabs/connect), [express](https://github.com/strongloop/express) and [browser-sync](https://github.com/BrowserSync/browser-sync)

Expand All @@ -13,12 +12,25 @@ npm install --save-dev http-proxy-middleware
```

## Core concept
Create and configure the proxy middleware.
Configure the proxy middleware.
```javascript
var proxyMiddleware = require('http-proxy-middleware');

var proxy = proxyMiddleware('/api', {target: 'http://www.example.org'});
// \____/ \________________________________/
// | |
// context options

// 'proxy' is now ready to be used in a server.

```
* **context**: matches provided context against request-urls' path.
Matching requests will be proxied to the target host.
Example: `'/api'` or `['/api', '/ajax']`
* **options.target**: target host to proxy to.
Check out available [proxy options](#options).



## Example
```javascript
Expand All @@ -42,9 +54,6 @@ var app = express();
app.listen(3000);
```

* `context` path to proxy. Example: '/api'
* `options.target` target host to proxy to. (See "[Options](#options)" for all options)

**Tip:** For [name-based virtual hosted sites](http://en.wikipedia.org/wiki/Virtual_hosting#Name-based), you'll need to use the option `changeOrigin` and set it to `true`.

## Compatible servers:
Expand All @@ -57,7 +66,7 @@ http-proxy-middleware is compatible with the following servers:
## Options

* (DEPRECATED) **option.proxyHost**: Use `option.changeOrigin = true` instead.
* **option.pathRewrite**: object, rewrite the url path. Object-keys will be used as _RegEx_ to match paths.
* **option.pathRewrite**: object, rewrite target's url path. Object-keys will be used as _RegExp_ to match paths.

```javascript
{
Expand Down Expand Up @@ -103,10 +112,10 @@ $ npm install
$ node examples/connect
```

Or just [explore the examples sources](https://github.com/chimurai/http-proxy-middleware/tree/master/examples):
* `examples/connect` - Example usage with [connect](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/connect)
* `examples/express` - Example usage with [express](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/express)
* `examples/browser-sync` - Example usage with [browser-sync](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/browser-sync)
Or just explore the [proxy examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples) sources:
* `examples/connect` - [connect proxy middleware example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/connect)
* `examples/express` - [express proxy middleware example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/express)
* `examples/browser-sync` - [browser-sync proxy middleware example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/browser-sync)

## Tests

Expand Down
2 changes: 1 addition & 1 deletion examples/browser-sync/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ browserSync({
server: {
baseDir: "./",
port: 3000,
middleware: [proxy],
middleware: [proxy], // add the proxy to browser-sync
open: false
}
});
Expand Down
2 changes: 1 addition & 1 deletion examples/connect/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var proxy = proxyMiddleware('/api', {
});

var app = connect();
app.use(proxy);
app.use(proxy); // add the proxy to connect

http.createServer(app).listen(3000);

Expand Down
2 changes: 1 addition & 1 deletion examples/express/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var proxy = proxyMiddleware('/api', {
});

var app = express();
app.use(proxy);
app.use(proxy); // add the proxy to express

app.listen(3000);

Expand Down
17 changes: 9 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var httpProxy = require('http-proxy');
var utils = require('./lib/utils');
var handlers = require('./lib/handlers');
var httpProxy = require('http-proxy');
var handlers = require('./lib/handlers');
var contextMatcher = require('./lib/context-matcher');
var pathRewriter = require('./lib/path-rewriter');

var httpProxyMiddleware = function (context, opts) {

Expand All @@ -11,7 +12,7 @@ var httpProxyMiddleware = function (context, opts) {
if (proxyOptions.proxyHost) {
console.log('*************************************');
console.log('[HPM] Deprecated "option.proxyHost"');
console.log(' Use "option.headers.host" instead');
console.log(' Use "option.changeOrigin" or "option.headers.host" instead');
console.log(' "option.proxyHost" will be removed in future release.');
console.log('*************************************');

Expand All @@ -24,10 +25,10 @@ var httpProxyMiddleware = function (context, opts) {

// handle option.pathRewrite
if (proxyOptions.pathRewrite) {
var pathRewriter = utils.createPathRewriter(proxyOptions.pathRewrite);
var rewriter = pathRewriter.create(proxyOptions.pathRewrite);

proxy.on('proxyReq', function (proxyReq, req, res, options) {
handlers.proxyPathRewrite(proxyReq, pathRewriter);
handlers.proxyPathRewrite(proxyReq, rewriter);
});
}

Expand All @@ -36,12 +37,12 @@ var httpProxyMiddleware = function (context, opts) {
handlers.proxyError(err, req, res, proxyOptions);
});

console.log('[HPM] Proxy created:', context, proxyOptions.target);
console.log('[HPM] Proxy created:', context, ' -> ', proxyOptions.target);

return middleware;

function middleware (req, res, next) {
if (utils.hasContext(context, req.url)) {
if (contextMatcher.match(context, req.url)) {
proxy.web(req, res);
} else {
next();
Expand Down
35 changes: 35 additions & 0 deletions lib/context-matcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
var url = require('url');

module.exports = {
match : matchContext,
matchSinglePath : matchSinglePath,
matchMultiPath : matchMultiPath
}

function matchContext (context, uri) {
// single path
if (typeof context === 'string') {
return matchSinglePath(context, uri);
}
// multi path
if (Array.isArray(context)) {
return matchMultiPath(context, uri);
}

throw new Error('[HPM] Invalid context. Expecting something like: "/api" or ["/api", "/ajax"]');
}

function matchSinglePath (context, uri) {
var urlPath = url.parse(uri).path;
return urlPath.indexOf(context) === 0;
}

function matchMultiPath (contextList, uri) {
for (var i = 0; i < contextList.length; i++) {
var context = contextList[i];
if (matchSinglePath(context, uri)) {
return true;
}
}
return false;
}
30 changes: 11 additions & 19 deletions lib/utils.js → lib/path-rewriter.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
var url = require('url');

module.exports = {
hasContext : hasContext,
createPathRewriter : createPathRewriter
}

function hasContext (context, uri) {
var urlPath = url.parse(uri).path;
return urlPath.indexOf(context) === 0;
create : createPathRewriter
}

/**
Expand Down Expand Up @@ -36,16 +28,16 @@ function parsePathRewriteRules (config) {

if (isObject(config) === false) {
throw new Error('[HPM] Invalid pathRewrite config. Expecting an object literal with pathRewrite configuration');
}

for (var key in config) {
if (config.hasOwnProperty(key)) {
rules.push({
regex : new RegExp(key),
value : config[key]
});

console.log('[HPM] Proxy rewrite rule created: "' + key + '" -> "' + config[key] + '"');
} else {
for (var key in config) {
if (config.hasOwnProperty(key)) {
rules.push({
regex : new RegExp(key),
value : config[key]
});

console.log('[HPM] Proxy rewrite rule created: "' + key + '" -> "' + config[key] + '"');
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "http-proxy-middleware",
"version": "0.1.0",
"description": "The one-liner http-proxy middleware",
"description": "The one-liner proxy middleware for connect, express and browser-sync",
"main": "index.js",
"scripts": {
"test": "mocha --reporter spec",
Expand Down
89 changes: 89 additions & 0 deletions test/context-matcher.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
var expect = require('chai').expect;
var contextMatcher = require('../lib/context-matcher');

describe('Single path matching', function () {
var result;

it('should return true when the context is present in url', function () {
result = contextMatcher.match('/api', 'http://localhost/api/foo/bar');
expect(result).to.be.true;
});

it('should return false when the context is not present in url', function () {
result = contextMatcher.match('/abc', 'http://localhost/api/foo/bar');
expect(result).to.be.false;
});

it('should return false when the context is present half way in url', function () {
result = contextMatcher.match('/foo', 'http://localhost/api/foo/bar');
expect(result).to.be.false;
});

it('should return false when the context does not start with /', function () {
result = contextMatcher.match('api', 'http://localhost/api/foo/bar');
expect(result).to.be.false;
});
});

describe('Multi path matching', function () {
var result;

it('should return true when the context is present in url', function () {
result = contextMatcher.match(['/api'], 'http://localhost/api/foo/bar');
expect(result).to.be.true;
});

it('should return true when the context is present in url', function () {
result = contextMatcher.match(['/api', '/ajax'], 'http://localhost/ajax/foo/bar');
expect(result).to.be.true;
});

it('should return false when the context does not match url', function () {
result = contextMatcher.match(['/api', '/ajax'], 'http://localhost/foo/bar');
expect(result).to.be.false;
});

it('should return false when empty array provided', function () {
result = contextMatcher.match([], 'http://localhost/api/foo/bar');
expect(result).to.be.false;
});

});


describe('Test invalid contexts', function () {
var testContext;

beforeEach(function () {
testContext = function (context) {
return function () {
contextMatcher.match(context, 'http://localhost/api/foo/bar');
};
};
});

it('should throw error with undefined', function () {
expect(testContext(undefined)).to.throw(Error);
});

it('should throw error with null', function () {
expect(testContext(null)).to.throw(Error);
});

it('should throw error with object literal', function () {
expect(testContext({})).to.throw(Error);
});

it('should throw error with integers', function () {
expect(testContext(123)).to.throw(Error);
});

it('should not throw error with string', function () {
expect(testContext('/123')).not.to.throw(Error);
});

it('should not throw error with Array', function () {
expect(testContext(['/123'])).not.to.throw(Error);
});

});
Loading