Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

http-proxy-middleware

Package Overview
Dependencies
Maintainers
1
Versions
88
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

http-proxy-middleware - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

63

lib/context-matcher.js
var url = require('url');
var isGlob = require('is-glob');
var micromatch = require('micromatch');
module.exports = {
match : matchContext,
matchSinglePath : matchSinglePath,
matchMultiPath : matchMultiPath
match : matchContext
}

@@ -11,8 +11,19 @@

// single path
if (typeof context === 'string') {
return matchSinglePath(context, uri);
if (isStringPath(context)) {
return matchSingleStringPath(context, uri);
}
// single glob path
if (isGlobPath(context)) {
return matchSingleGlobPath(context, uri);
}
// multi path
if (Array.isArray(context)) {
return matchMultiPath(context, uri);
if (context.every(isStringPath)) {
return matchMultiPath(context, uri);
}
if (context.every(isGlobPath)) {
return matchMultiGlobPath(context, uri);
}
throw new Error('[HPM] Invalid context. Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"]');
}

@@ -23,11 +34,31 @@

function matchSinglePath (context, uri) {
var urlPath = url.parse(uri).path;
return urlPath.indexOf(context) === 0;
/**
* @param {String} context '/api'
* @param {String} uri 'http://example.org/api/b/c/d.html'
* @return {Boolean}
*/
function matchSingleStringPath (context, uri) {
var path = getUrlPath(uri);
return path.indexOf(context) === 0;
}
function matchSingleGlobPath (pattern, uri) {
var path = getUrlPath(uri);
var matches = micromatch(path, pattern);
return matches && (matches.length > 0);
}
function matchMultiGlobPath (patternList, uri) {
return matchSingleGlobPath(patternList, uri);
}
/**
* @param {String} context ['/api', '/ajax']
* @param {String} uri 'http://example.org/api/b/c/d.html'
* @return {Boolean}
*/
function matchMultiPath (contextList, uri) {
for (var i = 0; i < contextList.length; i++) {
var context = contextList[i];
if (matchSinglePath(context, uri)) {
if (matchSingleStringPath(context, uri)) {
return true;

@@ -38,1 +69,13 @@ }

}
function getUrlPath (uri) {
return uri && url.parse(uri).path;
}
function isStringPath (context) {
return typeof context === 'string' && !isGlob(context);
}
function isGlobPath (context) {
return isGlob(context);
}

4

package.json
{
"name": "http-proxy-middleware",
"version": "0.2.0",
"version": "0.3.0",
"description": "The one-liner proxy middleware for connect, express and browser-sync",

@@ -44,4 +44,6 @@ "main": "index.js",

"http-proxy": "^1.11.1",
"is-glob": "^2.0.0",
"micromatch": "^2.1.6",
"url": "^0.10.3"
}
}

@@ -11,3 +11,3 @@ # http-proxy-middleware

```javascript
npm install --save-dev http-proxy-middleware
$ npm install --save-dev http-proxy-middleware
```

@@ -30,3 +30,3 @@

Matching requests will be proxied to the target host.
Example: `'/api'` or `['/api', '/ajax']`
Example: `'/api'` or `['/api', '/ajax']`. (more about [context matching](#context-matching))
* **options.target**: target host to proxy to.

@@ -38,2 +38,3 @@ Check out available [proxy options](#options).

## Example
A simple example with express server.
```javascript

@@ -60,2 +61,4 @@ // include dependencies

See [more examples](#more-examples).
**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`.

@@ -102,6 +105,27 @@

## Context matching
Request URL's [ _path-absolute_ and _query_](https://tools.ietf.org/html/rfc3986#section-3) will be used for context matching .
* URL: `http://example.com:8042/over/there?name=ferret#nose`
* context: `/over/there?name=ferret`
http-proxy-middleware offers several ways to decide which requests should be proxied:
* path matching
* `'/'` - matches any path, all requests will be proxied.
* `'/api'` - matches paths starting with `/api`
* multiple path matching
* `['/api','/ajax','/someotherpath']`
* wildcard path matching
For fine-grained control you can use wildcard matching. Glob pattern matching is done by _micromatch_. Visit [micromatch](https://www.npmjs.com/package/micromatch) or [glob](https://www.npmjs.com/package/glob) for more globbing examples.
* `**` matches any path, all requests will be proxied.
* `**.html` matches any path which ends with `.html`
* `/*.html` matches paths directly under path-absolute
* `/api/**.html` matches requests ending with `.html` in the path of `/api`
* `['/api/**', '/ajax/**']` combine multiple patterns
* `['/api/**', '!**/bad.json']` exclusion
## More Examples
To [view the examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples), clone the http-proxy-middleware repo and install the dependencies:
To run and view the [proxy examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples), clone the http-proxy-middleware repo and install the dependencies:

@@ -120,3 +144,3 @@ ```bash

Or just explore the [proxy examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples) sources:
Or just explore the proxy examples' sources:
* `examples/connect` - [connect proxy middleware example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/connect)

@@ -128,6 +152,10 @@ * `examples/express` - [express proxy middleware example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/express)

To run the test suite, first install the dependencies, then run `npm test`:
To run the test suite, first install the dependencies, then run:
```bash
# unit tests
$ npm test
# code coverage
$ npm run cover
```

@@ -134,0 +162,0 @@

var expect = require('chai').expect;
var contextMatcher = require('../lib/context-matcher');
describe('Single path matching', function () {
describe('String 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;
});
describe('Single path matching', function () {
it('should match all paths', function () {
result = contextMatcher.match('/', '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 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 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 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;
});
});
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 () {
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('Multi path matching', function () {
var result;
describe('Wildcard path matching', function () {
describe('Single glob', function () {
var url;
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;
});
beforeEach(function () {
url = 'http://localhost/api/foo/bar.html';
});
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;
});
describe('url-path matching', function () {
it('should match any path', function () {
expect(contextMatcher.match('**', url)).to.be.true;
expect(contextMatcher.match('/**', url)).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 only match paths starting with "/api" ', function () {
expect(contextMatcher.match('/api/**', url)).to.be.true;
expect(contextMatcher.match('/ajax/**', url)).to.be.false;
});
it('should only match paths starting with "foo" folder in it ', function () {
expect(contextMatcher.match('**/foo/**', url)).to.be.true;
expect(contextMatcher.match('**/invalid/**', url)).to.be.false;
});
});
describe('file matching', function () {
it('should match any path, file and extension', function () {
expect(contextMatcher.match('**', url)).to.be.true;
expect(contextMatcher.match('/**', url)).to.be.true;
expect(contextMatcher.match('**.*', url)).to.be.true;
expect(contextMatcher.match('/**.*', url)).to.be.true;
expect(contextMatcher.match('**/*.*', url)).to.be.true;
expect(contextMatcher.match('/**/*.*', url)).to.be.true;
});
it('should only match .html files', function () {
expect(contextMatcher.match('**.html', url)).to.be.true;
expect(contextMatcher.match('**.htm', url)).to.be.false;
expect(contextMatcher.match('**.jpg', url)).to.be.false;
});
it('should only match .html under root path', function () {
var pattern = '/*.html';
expect(contextMatcher.match(pattern, 'http://localhost/index.html')).to.be.true;
expect(contextMatcher.match(pattern, 'http://localhost/some/path/index.html')).to.be.false;
});
it('should only match .php files with query params', function () {
expect(contextMatcher.match('**.php', 'http://localhost/a/b/c.php?d=e&e=f')).to.be.false;
expect(contextMatcher.match('**.php?*', 'http://localhost/a/b/c.php?d=e&e=f')).to.be.true;
});
it('should only match any file in root path', function () {
expect(contextMatcher.match('/*', 'http://localhost/bar.html')).to.be.true;
expect(contextMatcher.match('/*.*', 'http://localhost/bar.html')).to.be.true;
expect(contextMatcher.match('/*', 'http://localhost/foo/bar.html')).to.be.false;
});
it('should only match .html file is in root path', function () {
expect(contextMatcher.match('/*.html', 'http://localhost/bar.html')).to.be.true;
expect(contextMatcher.match('/*.html', 'http://localhost/api/foo/bar.html')).to.be.false;
});
it('should only match .html files in "foo" folder', function () {
expect(contextMatcher.match('**/foo/*.html', url)).to.be.true;
expect(contextMatcher.match('**/bar/*.html', url)).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('Multi glob matching', function () {
describe('Multiple patterns', function () {
it('should return true when both path patterns match', function () {
var pattern = ['/api/**','/ajax/**'];
expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.json')).to.be.true;
expect(contextMatcher.match(pattern, 'http://localhost/ajax/foo/bar.json')).to.be.true;
expect(contextMatcher.match(pattern, 'http://localhost/rest/foo/bar.json')).to.be.false;
});
it('should return true when both file extensions pattern match', function () {
var pattern = ['**.html','**.jpeg'];
expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.html')).to.be.true;
expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.jpeg')).to.be.true;
expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.gif')).to.be.false;
});
});
describe('Negation patterns', function () {
it('should not match file extension', function () {
var url = 'http://localhost/api/foo/bar.html';
expect(contextMatcher.match(['**', '!**.html'], url)).to.be.false;
expect(contextMatcher.match(['**', '!**.json'], url)).to.be.true;
});
});
});
});

@@ -81,2 +182,6 @@

it('should throw error with mixed string and glob pattern', function () {
expect(testContext(['/api', '!*.html'])).to.throw(Error);
});
it('should not throw error with string', function () {

@@ -89,3 +194,10 @@ expect(testContext('/123')).not.to.throw(Error);

});
it('should not throw error with glob', function () {
expect(testContext('/**')).not.to.throw(Error);
});
it('should not throw error with Array of globs', function () {
expect(testContext(['/**', '!*.html'])).not.to.throw(Error);
});
});

@@ -56,3 +56,3 @@ var expect = require('chai').expect;

res.write('HELLO WEB'); // respond with 'HELLO WEB'
res.end()
res.end();
};

@@ -99,3 +99,3 @@

res.write(req.url); // respond with req.url
res.end()
res.end();
};

@@ -164,2 +164,95 @@

describe('wildcard path matching', function () {
var proxyServer, targetServer;
var targetHeaders;
var response, responseBody;
beforeEach(function () {
var mw_proxy = proxyMiddleware('/api/**', {target:'http://localhost:8000'});
var mw_target = function (req, res, next) {
res.write(req.url); // respond with req.url
res.end();
};
proxyServer = createServer(3000, mw_proxy);
targetServer = createServer(8000, mw_target);
});
beforeEach(function (done) {
http.get('http://localhost:3000/api/some/endpoint', function (res) {
response = res;
res.on('data', function (chunk) {
responseBody = chunk.toString();
done();
});
});
});
afterEach(function () {
proxyServer.close();
targetServer.close();
});
it('should proxy to path', function () {
expect(response.statusCode).to.equal(200);
expect(responseBody).to.equal('/api/some/endpoint');
});
});
describe('multi glob wildcard path matching', function () {
var proxyServer, targetServer;
var targetHeaders;
var responseA, responseBodyA;
var responseB, responseBodyB;
beforeEach(function () {
var mw_proxy = proxyMiddleware(['**.html', '!**.json'], {target:'http://localhost:8000'});
var mw_target = function (req, res, next) {
res.write(req.url); // respond with req.url
res.end();
};
proxyServer = createServer(3000, mw_proxy);
targetServer = createServer(8000, mw_target);
});
beforeEach(function (done) {
http.get('http://localhost:3000/api/some/endpoint/index.html', function (res) {
responseA = res;
res.on('data', function (chunk) {
responseBodyA = chunk.toString();
done();
});
});
});
beforeEach(function (done) {
http.get('http://localhost:3000/api/some/endpoint/data.json', function (res) {
responseB = res;
res.on('data', function (chunk) {
responseBodyB = chunk.toString();
done();
});
});
});
afterEach(function () {
proxyServer.close();
targetServer.close();
});
it('should proxy to paths ending with *.html', function () {
expect(responseA.statusCode).to.equal(200);
expect(responseBodyA).to.equal('/api/some/endpoint/index.html');
});
it('should not proxy to paths ending with *.json', function () {
expect(responseB.statusCode).to.equal(404);
});
});
describe('additional request headers', function () {

@@ -268,3 +361,3 @@ var proxyServer, targetServer;

res.write(req.url); // respond with req.url
res.end()
res.end();
};

@@ -271,0 +364,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc