http-proxy-middleware
Advanced tools
Comparing version 0.2.0 to 0.3.0
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); | ||
} |
{ | ||
"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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
44794
793
180
6
4
+ Addedis-glob@^2.0.0
+ Addedmicromatch@^2.1.6
+ Addedarr-diff@2.0.0(transitive)
+ Addedarr-flatten@1.1.0(transitive)
+ Addedarray-unique@0.2.1(transitive)
+ Addedbraces@1.8.5(transitive)
+ Addedexpand-brackets@0.1.5(transitive)
+ Addedexpand-range@1.8.2(transitive)
+ Addedextglob@0.3.2(transitive)
+ Addedfilename-regex@2.0.1(transitive)
+ Addedfill-range@2.2.4(transitive)
+ Addedfor-in@1.0.2(transitive)
+ Addedfor-own@0.1.5(transitive)
+ Addedglob-base@0.3.0(transitive)
+ Addedglob-parent@2.0.0(transitive)
+ Addedis-buffer@1.1.6(transitive)
+ Addedis-dotfile@1.0.3(transitive)
+ Addedis-equal-shallow@0.1.3(transitive)
+ Addedis-extendable@0.1.1(transitive)
+ Addedis-extglob@1.0.0(transitive)
+ Addedis-glob@2.0.1(transitive)
+ Addedis-number@2.1.04.0.0(transitive)
+ Addedis-posix-bracket@0.1.1(transitive)
+ Addedis-primitive@2.0.0(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedisobject@2.1.0(transitive)
+ Addedkind-of@3.2.26.0.3(transitive)
+ Addedmath-random@1.0.4(transitive)
+ Addedmicromatch@2.3.11(transitive)
+ Addednormalize-path@2.1.1(transitive)
+ Addedobject.omit@2.0.1(transitive)
+ Addedparse-glob@3.0.4(transitive)
+ Addedpreserve@0.2.0(transitive)
+ Addedrandomatic@3.1.1(transitive)
+ Addedregex-cache@0.4.4(transitive)
+ Addedremove-trailing-separator@1.1.0(transitive)
+ Addedrepeat-element@1.1.4(transitive)
+ Addedrepeat-string@1.6.1(transitive)