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.12.0 to 0.13.0

recipes/context.md

5

CHANGELOG.md
# Changelog
## [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.
## [v0.12.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.12.0)
- add option `onProxyReqWS` (subscribe to http-proxy `proxyReqWs` event)
- add option `onProxyReqWs` (subscribe to http-proxy `proxyReqWs` event)
- add option `onOpen` (subscribe to http-proxy `open` event)

@@ -6,0 +9,0 @@ - add option `onClose` (subscribe to http-proxy `close` event)

4

index.js

@@ -42,3 +42,3 @@ var httpProxy = require('http-proxy');

if (contextMatcher.match(config.context, req.url)) {
if (contextMatcher.match(config.context, req.url, req)) {
var activeProxyOptions = prepareProxyRequest(req);

@@ -66,3 +66,3 @@ proxy.web(req, res, activeProxyOptions);

function handleUpgrade(req, socket, head) {
if (contextMatcher.match(config.context, req.url)) {
if (contextMatcher.match(config.context, req.url, req)) {
var activeProxyOptions = prepareProxyRequest(req);

@@ -69,0 +69,0 @@ proxy.ws(req, socket, head, activeProxyOptions);

@@ -0,1 +1,2 @@

var _ = require('lodash');
var url = require('url');

@@ -9,3 +10,4 @@ var isGlob = require('is-glob');

function matchContext(context, uri) {
function matchContext(context, uri, req) {
// single path

@@ -15,2 +17,3 @@ if (isStringPath(context)) {

}
// single glob path

@@ -20,2 +23,3 @@ if (isGlobPath(context)) {

}
// multi path

@@ -33,2 +37,8 @@ if (Array.isArray(context)) {

// custom matching
if (_.isFunction(context)) {
var path = getUrlPath(uri);
return context(path, req);
}
throw new Error('[HPM] Invalid context. Expecting something like: "/api" or ["/api", "/ajax"]');

@@ -77,3 +87,3 @@ }

function isStringPath(context) {
return typeof context === 'string' && !isGlob(context);
return _.isString(context) && !isGlob(context);
}

@@ -80,0 +90,0 @@

{
"name": "http-proxy-middleware",
"version": "0.12.0",
"version": "0.13.0",
"description": "The one-liner node.js proxy middleware for connect, express and browser-sync",

@@ -45,3 +45,3 @@ "main": "index.js",

"mocha": "^2.3.4",
"mocha-lcov-reporter": "1.0.0",
"mocha-lcov-reporter": "1.2.0",
"ws": "^1.0.1"

@@ -52,5 +52,5 @@ },

"is-glob": "^2.0.1",
"lodash": "^3.10.1",
"lodash": "^4.6.1",
"micromatch": "^2.3.7"
}
}

@@ -61,3 +61,4 @@ # http-proxy-middleware

pathRewrite: {
'^/old/api' : '/new/api' // rewrite paths
'^/old/api' : '/new/api', // rewrite path
'^/remove/api' : '/api' // remove path
},

@@ -97,2 +98,3 @@ proxyTable: {

* **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.

@@ -106,2 +108,13 @@ - `'**'` matches any path, all requests will be proxied.

* **custom matching**
For full control you can provide a custom filter function to determine which requests should be proxied or not.
```javascript
var filter = function (path, req) {
return (path.match('^/api') && req.method === 'GET');
};
var apiProxy = proxyMiddleware(filter, {target: 'http://www.example.org'})
```
## Shorthand

@@ -154,6 +167,10 @@

```javascript
{
"^/old/api" : "/new/api", // rewrite path
"^/remove/api" : "" // remove path
}
// rewrite path
pathRewrite: {"^/old/api" : "/new/api"}
// remove path
pathRewrite: {"^/remove/api" : ""}
// add base path
pathRewrite: {"^/" : "/basepath/"}
```

@@ -163,3 +180,3 @@

```javascript
{
proxyTable: {
"integration.localhost:3000" : "http://localhost:8001", // host only

@@ -166,0 +183,0 @@ "staging.localhost:3000" : "http://localhost:8002", // host only

@@ -5,2 +5,3 @@ # pathRewrite

## Path rewrite
```javascript

@@ -12,4 +13,3 @@ var proxyMiddleware = require("http-proxy-middleware");

pathRewrite: {
"^/old/api" : "/new/api", // rewrite path
"^/remove/api" : "" // remove path
"^/old/api" : "/new/api" // rewrite path
}

@@ -21,4 +21,34 @@ };

// `/old/api/foo/bar` -> `http://localhost:3000/new/api/foo/bar`
```
## Remove base path
```javascript
var proxyMiddleware = require("http-proxy-middleware");
var options = {
target: 'http://localhost:3000',
pathRewrite: {
"^/remove/api" : "" // remove base path
}
};
var proxy = proxyMiddleware('/api', options);
// `/remove/api/lorum/ipsum` -> `http://localhost:3000/lorum/ipsum`
```
## Add base path
```javascript
var proxyMiddleware = require("http-proxy-middleware");
var options = {
target: 'http://localhost:3000',
pathRewrite: {
"^/" : "/extra/" // add base path
}
};
var proxy = proxyMiddleware('/api', options);
// `/api/lorum/ipsum` -> `http://localhost:3000/extra/api/lorum/ipsum`
```
var expect = require('chai').expect;
var contextMatcher = require('../lib/context-matcher');
describe('String path matching', function() {
var result;
describe('Context Matching', function() {
describe('Single path matching', function() {
it('should match all paths', function() {
result = contextMatcher.match('', 'http://localhost/api/foo/bar');
expect(result).to.be.true;
});
describe('String path matching', function() {
var result;
it('should match all paths starting with forward-slash', function() {
result = contextMatcher.match('/', '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 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 match all paths starting with forward-slash', 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 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 is present half way in url', function() {
result = contextMatcher.match('/foo', '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 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 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('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 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 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 empty array provided', function() {
result = contextMatcher.match([], 'http://localhost/api/foo/bar');
expect(result).to.be.false;
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('Wildcard path matching', function() {
describe('Single glob', function() {
var url;
describe('Wildcard path matching', function() {
describe('Single glob', function() {
var url;
beforeEach(function() {
url = 'http://localhost/api/foo/bar.html';
});
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;
beforeEach(function() {
url = 'http://localhost/api/foo/bar.html';
});
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;
});
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 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;
});
});
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;
});
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;
expect(contextMatcher.match('/**/*.*', url)).to.be.true;
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;
});
});
it('should only match .html files', function() {
expect(contextMatcher.match('**/*.html', url)).to.be.true;
expect(contextMatcher.match('/**.html', url)).to.be.true;
expect(contextMatcher.match('/**/*.html', url)).to.be.true;
expect(contextMatcher.match('/**.htm', url)).to.be.false;
expect(contextMatcher.match('/**.jpg', 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;
expect(contextMatcher.match('/**/*.*', url)).to.be.true;
});
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 .html files', function() {
expect(contextMatcher.match('**/*.html', url)).to.be.true;
expect(contextMatcher.match('/**.html', url)).to.be.true;
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 .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 .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 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 .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 not match .html files', function() {
expect(contextMatcher.match('!**/*.html', url)).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;
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;
});
});
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;
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;
});
});

@@ -135,75 +168,84 @@ });

describe('Multi glob matching', function() {
describe('Use function for matching', function() {
testFunctionAsContext = function(val) {
return contextMatcher.match(fn, 'http://localhost/api/foo/bar');
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;
function fn(path, req) {
return val;
};
};
describe('truthy', function() {
it('should match when function returns true', function() {
expect(testFunctionAsContext(true)).to.be.ok;
expect(testFunctionAsContext('true')).to.be.ok;
});
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;
describe('falsy', function() {
it('should not match when function returns falsy value', function() {
expect(testFunctionAsContext()).to.not.be.ok;
expect(testFunctionAsContext(undefined)).to.not.be.ok;
expect(testFunctionAsContext(false)).to.not.be.ok;
expect(testFunctionAsContext('')).to.not.be.ok;
});
});
});
});
describe('Test invalid contexts', function() {
var testContext;
describe('Test invalid contexts', function() {
var testContext;
beforeEach(function() {
testContext = function(context) {
return function() {
contextMatcher.match(context, 'http://localhost/api/foo/bar');
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);
});
describe('Throw error', function() {
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 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 object literal', function() {
expect(testContext({})).to.throw(Error);
});
it('should throw error with integers', function() {
expect(testContext(123)).to.throw(Error);
});
it('should throw error with integers', function() {
expect(testContext(123)).to.throw(Error);
});
it('should throw error with mixed string and glob pattern', function() {
expect(testContext(['/api', '!*.html'])).to.throw(Error);
});
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() {
expect(testContext('/123')).not.to.throw(Error);
});
describe('Do not throw error', function() {
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);
});
it('should not throw error with glob', function() {
expect(testContext('/**')).not.to.throw(Error);
});
it('should not throw error with Array', function() {
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);
it('should not throw error with Array of globs', function() {
expect(testContext(['/**', '!*.html'])).not.to.throw(Error);
});
it('should not throw error with Function', function() {
expect(testContext(function() {})).not.to.throw(Error);
});
});
});
});

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

describe('custom context matcher/filter', function() {
var proxyServer, targetServer;
var targetHeaders;
var targetUrl;
var responseBody;
var filterPath, filterReq;
beforeEach(function(done) {
var filter = function(path, req) {
filterPath = path;
filterReq = req;
return true;
};
var mw_proxy = proxyMiddleware(filter, {target: 'http://localhost:8000'});
var mw_target = function(req, res, next) {
targetUrl = req.url; // store target url.
targetHeaders = req.headers; // store target headers.
res.write('HELLO WEB'); // respond with 'HELLO WEB'
res.end();
};
proxyServer = createServer(3000, mw_proxy);
targetServer = createServer(8000, mw_target);
http.get('http://localhost:3000/api/b/c/d', function(res) {
res.on('data', function(chunk) {
responseBody = chunk.toString();
done();
});
});
});
afterEach(function() {
proxyServer.close();
targetServer.close();
});
it('should have response body: "HELLO WEB"', function() {
expect(responseBody).to.equal('HELLO WEB');
});
it('should provide the url path in the first argument', function() {
expect(filterPath).to.equal('/api/b/c/d');
});
it('should provide the req object in the second argument', function() {
expect(filterReq.method).to.equal('GET');
});
});
describe('multi path', function() {

@@ -90,0 +144,0 @@ var proxyServer, targetServer;

@@ -7,6 +7,8 @@ var expect = require('chai').expect;

var result;
var config;
describe('Configuration and usage', function() {
beforeEach(function() {
var config = {
config = {
'^/api/old': '/api/new',

@@ -19,2 +21,5 @@ '^/remove': '',

};
});
beforeEach(function() {
rewriter = pathRewriter.create(config);

@@ -53,3 +58,20 @@ });

});
});
describe('add base path to requests', function() {
beforeEach(function() {
config = {
'^/': '/extra/base/path/'
};
});
beforeEach(function() {
rewriter = pathRewriter.create(config);
});
it('should add base path to requests', function() {
result = rewriter('/api/books/123');
expect(result).to.equal('/extra/base/path/api/books/123');
});
});

@@ -56,0 +78,0 @@

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