Skip to content

Commit

Permalink
feat: wildcard path matching with globs
Browse files Browse the repository at this point in the history
  • Loading branch information
chimurai committed Jul 15, 2015
1 parent ac411dc commit 79ca9db
Show file tree
Hide file tree
Showing 5 changed files with 333 additions and 55 deletions.
38 changes: 33 additions & 5 deletions README.md
Expand Up @@ -8,7 +8,7 @@ The one-liner proxy middleware for [connect](https://github.com/senchalabs/conne

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

## Core concept
Expand All @@ -26,13 +26,14 @@ var proxy = proxyMiddleware('/api', {target: 'http://www.example.org'});
```
* **context**: matches provided context against request-urls' path.
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.
Check out available [proxy options](#options).



## Example
A simple example with express server.
```javascript
// include dependencies
var express = require('express');
Expand All @@ -54,6 +55,8 @@ var app = express();
app.listen(3000);
```

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

## Compatible servers:
Expand Down Expand Up @@ -95,10 +98,31 @@ Undocumented options are provided by the underlying [http-proxy](https://github.
* **option.protocolRewrite**: rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null.


## 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:

```bash
$ git clone https://github.com/chimurai/http-proxy-middleware.git
Expand All @@ -112,17 +136,21 @@ $ npm install
$ node examples/connect
```

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)
* `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

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
```


Expand Down
63 changes: 53 additions & 10 deletions lib/context-matcher.js
@@ -1,35 +1,78 @@
var url = require('url');
var isGlob = require('is-glob');
var micromatch = require('micromatch');

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

function matchContext (context, uri) {
// 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"]');
}

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;
/**
* @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;
}
}
return false;
}

function getUrlPath (uri) {
return uri && url.parse(uri).path;
}

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

function isGlobPath (context) {
return isGlob(context);
}
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -41,6 +41,8 @@
},
"dependencies": {
"http-proxy": "^1.11.1",
"is-glob": "^2.0.0",
"micromatch": "^2.1.6",
"url": "^0.10.3"
}
}

0 comments on commit 79ca9db

Please sign in to comment.