Skip to content

Commit

Permalink
feat(option): pathFilter
Browse files Browse the repository at this point in the history
  • Loading branch information
chimurai committed Feb 20, 2022
1 parent 1540dac commit bf27689
Show file tree
Hide file tree
Showing 19 changed files with 132 additions and 185 deletions.
57 changes: 28 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#option
- [Core concept](#core-concept)
- [Example](#example)
- [app.use(path, proxy)](#appusepath-proxy)
- [Context matching](#context-matching)
- [Path Filter](#path-filter)
- [Options](#options)
- [http-proxy-middleware options](#http-proxy-middleware-options)
- [http-proxy events](#http-proxy-events)
Expand All @@ -76,29 +76,29 @@ _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#option

## Install

```bash
$ npm install --save-dev http-proxy-middleware
```shell
npm install --save-dev http-proxy-middleware
```

## Core concept

Proxy middleware configuration.

#### createProxyMiddleware([context,] config)
#### createProxyMiddleware(config)

```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

const apiProxy = createProxyMiddleware('/api', { target: 'http://www.example.org' });
// \____/ \_____________________________/
// | |
// context options
const apiProxy = createProxyMiddleware({
pathFilter: '/api',
target: 'http://www.example.org',
});

// 'apiProxy' is now ready to be used as middleware in a server.
```

- **context**: Determine which requests should be proxied to the target host.
(more on [context matching](#context-matching))
- **options.pathFilter**: Determine which requests should be proxied to the target host.
(more on [path filter](#path-filter))
- **options.target**: target host to proxy to. _(protocol + host)_

(full list of [`http-proxy-middleware` configuration options](#options))
Expand Down Expand Up @@ -129,7 +129,7 @@ const options = {
},
};

// create the proxy (without context)
// create the proxy
const exampleProxy = createProxyMiddleware(options);

// mount `exampleProxy` in web server
Expand All @@ -140,8 +140,8 @@ app.listen(3000);

### app.use(path, proxy)

If you want to use the server's `app.use` `path` parameter to match requests;
Create and mount the proxy without the http-proxy-middleware `context` parameter:
If you want to use the server's `app.use` `path` parameter to match requests.
Use `pathFilter` option to further include/exclude requests which you want to proxy.

```javascript
app.use('/api', createProxyMiddleware({ target: 'http://www.example.org', changeOrigin: true }));
Expand All @@ -153,11 +153,11 @@ app.use('/api', createProxyMiddleware({ target: 'http://www.example.org', change
- connect: https://github.com/senchalabs/connect#mount-middleware
- polka: https://github.com/lukeed/polka#usebase-fn

## Context matching
## Path Filter

Providing an alternative way to decide which requests should be proxied; In case you are not able to use the server's [`path` parameter](http://expressjs.com/en/4x/api.html#app.use) to mount the proxy or when you need more flexibility.
Decide which requests should be proxied; In case you are not able to use the server's [`path` parameter](http://expressjs.com/en/4x/api.html#app.use) to mount the proxy or when you need more flexibility.

[RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is used for context matching.
[RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is used in `pathFilter`.

```ascii
foo://example.com:8042/over/there?name=ferret#nose
Expand All @@ -169,23 +169,22 @@ Providing an alternative way to decide which requests should be proxied; In case
- **path matching**

- `createProxyMiddleware({...})` - matches any path, all requests will be proxied.
- `createProxyMiddleware('/', {...})` - matches any path, all requests will be proxied.
- `createProxyMiddleware('/api', {...})` - matches paths starting with `/api`
- `createProxyMiddleware({ pathFilter: '/api', ...})` - matches paths starting with `/api`

- **multiple path matching**

- `createProxyMiddleware(['/api', '/ajax', '/someotherpath'], {...})`
- `createProxyMiddleware({ pathFilter: ['/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.

- `createProxyMiddleware('**', {...})` matches any path, all requests will be proxied.
- `createProxyMiddleware('**/*.html', {...})` matches any path which ends with `.html`
- `createProxyMiddleware('/*.html', {...})` matches paths directly under path-absolute
- `createProxyMiddleware('/api/**/*.html', {...})` matches requests ending with `.html` in the path of `/api`
- `createProxyMiddleware(['/api/**', '/ajax/**'], {...})` combine multiple patterns
- `createProxyMiddleware(['/api/**', '!**/bad.json'], {...})` exclusion
- `createProxyMiddleware({ pathFilter: '**', ...})` matches any path, all requests will be proxied.
- `createProxyMiddleware({ pathFilter: '**/*.html', ...})` matches any path which ends with `.html`
- `createProxyMiddleware({ pathFilter: '/*.html', ...})` matches paths directly under path-absolute
- `createProxyMiddleware({ pathFilter: '/api/**/*.html', ...})` matches requests ending with `.html` in the path of `/api`
- `createProxyMiddleware({ pathFilter: ['/api/**', '/ajax/**'], ...})` combine multiple patterns
- `createProxyMiddleware({ pathFilter: ['/api/**', '!**/bad.json'], ...})` exclusion

**Note**: In multiple path matching, you cannot use string paths and wildcard paths together.

Expand All @@ -197,8 +196,8 @@ Providing an alternative way to decide which requests should be proxied; In case
/**
* @return {Boolean}
*/
const filter = function (pathname, req) {
return pathname.match('^/api') && req.method === 'GET';
const filter = function (path, req) {
return path.match('^/api') && req.method === 'GET';
};

const apiProxy = createProxyMiddleware(filter, {
Expand Down Expand Up @@ -431,15 +430,15 @@ The following options are provided by the underlying [http-proxy](https://github

```javascript
// verbose api
createProxyMiddleware('/', { target: 'http://echo.websocket.org', ws: true });
createProxyMiddleware({ pathFilter: '/', target: 'http://echo.websocket.org', ws: true });
```

### External WebSocket upgrade

In the previous WebSocket examples, http-proxy-middleware relies on a initial http request in order to listen to the http `upgrade` event. If you need to proxy WebSockets without the initial http request, you can subscribe to the server's http `upgrade` event manually.

```javascript
const wsProxy = createProxyMiddleware('ws://echo.websocket.org', { changeOrigin: true });
const wsProxy = createProxyMiddleware({ target: 'ws://echo.websocket.org', changeOrigin: true });

const app = express();
app.use(wsProxy);
Expand Down
3 changes: 2 additions & 1 deletion examples/browser-sync/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ const { createProxyMiddleware } = require('../../dist'); // require('http-proxy-
/**
* Configure proxy middleware
*/
const jsonPlaceholderProxy = createProxyMiddleware('/users', {
const jsonPlaceholderProxy = createProxyMiddleware({
target: 'http://jsonplaceholder.typicode.com',
pathFilter: '/users',
changeOrigin: true, // for vhosted sites, changes host header to match to target's host
logLevel: 'debug',
});
Expand Down
2 changes: 1 addition & 1 deletion examples/websocket/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<body>
<h2>WebSocket demo</h2>

<p>Proxy <code>ws://localhost:3000</code> to <code>ws://echo.websocket.org</code></p>
<p>Proxy <code>ws://localhost:3000</code> to <code>ws://ws.ifelse.io</code></p>

<fieldset id="configuration">
<p>
Expand Down
4 changes: 2 additions & 2 deletions examples/websocket/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const { createProxyMiddleware } = require('../../dist'); // require('http-proxy-
/**
* Configure proxy middleware
*/
const wsProxy = createProxyMiddleware('/', {
target: 'http://echo.websocket.org',
const wsProxy = createProxyMiddleware({
target: 'http://ws.ifelse.io',
// pathRewrite: {
// '^/websocket' : '/socket', // rewrite path.
// '^/removepath' : '' // remove path.
Expand Down
10 changes: 4 additions & 6 deletions recipes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ http-proxy-middleware uses Nodejitsu's [http-proxy](https://github.com/nodejitsu
const { createProxyMiddleware } = require('http-proxy-middleware');
const winston = require('winston');

/**
* Context matching: decide which path(s) should be proxied. (wildcards supported)
**/
const context = '/api';

/**
* Proxy options
*/
const options = {
// decide which path(s) should be proxied. (wildcards supported)
pathFilter: '/api',

// hostname to the target server
target: 'http://localhost:3000',

Expand Down Expand Up @@ -104,5 +102,5 @@ const options = {
/**
* Create the proxy middleware, so it can be used in a server.
*/
const apiProxy = createProxyMiddleware(context, options);
const apiProxy = createProxyMiddleware(options);
```
8 changes: 4 additions & 4 deletions recipes/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ This example will create a basic proxy middleware.
```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

const apiProxy = createProxyMiddleware('/api', { target: 'http://localhost:3000' });
// \____/ \________________________________/
// | |
// context options
const apiProxy = createProxyMiddleware({
pathFilter: '/api',
target: 'http://localhost:3000',
});
```

## Alternative configuration
Expand Down
38 changes: 22 additions & 16 deletions recipes/context-matching.md → recipes/pathFilter.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Context matching
# Path Filter

Determine which requests should be proxied.

Context matching is optional and is useful in cases where you are not able to use the regular [middleware mounting](http://expressjs.com/en/4x/api.html#app.use).
`pathFilter` is optional and is useful in cases where you are not able to use the regular [middleware mounting](http://expressjs.com/en/4x/api.html#app.use).

The [RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is used for context matching.
The [RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is used for `pathFilter`.

```
```text
foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
Expand All @@ -15,25 +15,22 @@ The [RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is used f

`http-proxy-middleware` offers several ways to do this:

<!-- MarkdownTOC autolink=true bracket=round -->

- [Path](#path)
- [Multi Path](#multi-path)
- [Wildcard](#wildcard)
- [Multi Wildcard](#multi-wildcard)
- [Wildcard / Exclusion](#wildcard--exclusion)
- [Custom filtering](#custom-filtering)

<!-- /MarkdownTOC -->

## Path

This will match paths starting with `/api`

```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

const apiProxy = createProxyMiddleware('/api', {
const apiProxy = createProxyMiddleware({
pathFilter: '/api',
target: 'http://localhost:3000',
});

Expand All @@ -47,7 +44,10 @@ This will match paths starting with `/api` or `/rest`
```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

const apiProxy = createProxyMiddleware(['/api', '/rest'], { target: 'http://localhost:3000' });
const apiProxy = createProxyMiddleware({
pathFilter: ['/api', '/rest'],
target: 'http://localhost:3000',
});

// `/api/foo/bar` -> `http://localhost:3000/api/foo/bar`
// `/rest/lorum/ipsum` -> `http://localhost:3000/rest/lorum/ipsum`
Expand All @@ -60,7 +60,8 @@ This will match paths starting with `/api/` and should also end with `.json`
```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

const apiProxy = createProxyMiddleware('/api/**/*.json', {
const apiProxy = createProxyMiddleware({
pathFilter: '/api/**/*.json',
target: 'http://localhost:3000',
});
```
Expand All @@ -72,26 +73,28 @@ Multiple wildcards can be used.
```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

const apiProxy = createProxyMiddleware(['/api/**/*.json', '/rest/**'], {
const apiProxy = createProxyMiddleware({
pathFilter: ['/api/**/*.json', '/rest/**'],
target: 'http://localhost:3000',
});
```

## Wildcard / Exclusion

This example will create a proxy with wildcard context matching.
This example will create a proxy with globs.

```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

const apiProxy = createProxyMiddleware(['foo/*.js', '!bar.js'], {
const apiProxy = createProxyMiddleware({
pathFilter: ['foo/*.js', '!bar.js'],
target: 'http://localhost:3000',
});
```

## Custom filtering

Write your custom context matching function to have full control on the matching behavior.
Write your custom `pathFilter` function to have full control on the matching behavior.
The request `pathname` and `req` object are provided to determine which requests should be proxied or not.

```javascript
Expand All @@ -101,5 +104,8 @@ const filter = function (pathname, req) {
return pathname.match('^/api') && req.method === 'GET';
};

const apiProxy = createProxyMiddleware(filter, { target: 'http://localhost:3000' });
const apiProxy = createProxyMiddleware({
pathFilter: filter,
target: 'http://localhost:3000',
});
```
48 changes: 5 additions & 43 deletions src/config-factory.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,18 @@
import isPlainObj = require('is-plain-obj');
import { ERRORS } from './errors';
import { getInstance } from './logger';
import { Filter, Options } from './types';
import { Options } from './types';

const logger = getInstance();

export type Config = { context: Filter; options: Options };
export function verifyConfig(options: Options): void {
configureLogger(options);

export function createConfig(context, opts?: Options): Config {
// structure of config object to be returned
const config: Config = {
context: undefined,
options: {} as Options,
};

// app.use('/api', proxy({target:'http://localhost:9000'}));
if (isContextless(context, opts)) {
config.context = '/';
config.options = Object.assign(config.options, context);

// app.use('/api', proxy('http://localhost:9000'));
// app.use(proxy('http://localhost:9000/api'));
} else {
config.context = context;
config.options = Object.assign(config.options, opts);
}

configureLogger(config.options);

if (!config.options.target && !config.options.router) {
if (!options.target && !options.router) {
throw new Error(ERRORS.ERR_CONFIG_FACTORY_TARGET_MISSING);
}

return config;
}

/**
* Checks if a Object only config is provided, without a context.
* In this case the all paths will be proxied.
*
* @example
* app.use('/api', proxy({target:'http://localhost:9000'}));
*
* @param {Object} context [description]
* @param {*} opts [description]
* @return {Boolean} [description]
*/
function isContextless(context: Filter, opts: Options) {
return isPlainObj(context) && (opts == null || Object.keys(opts).length === 0);
}

function configureLogger(options: Options) {
function configureLogger(options: Options): void {
if (options.logLevel) {
logger.setLevel(options.logLevel);
}
Expand Down

0 comments on commit bf27689

Please sign in to comment.