├── index.js
├── .jscsrc
├── test
├── unit
│ ├── _libs.js
│ ├── handlers.spec.js
│ ├── path-rewriter.spec.js
│ ├── router.spec.js
│ ├── config-factory.spec.js
│ ├── logger.spec.js
│ └── context-matcher.spec.js
└── e2e
│ ├── _utils.js
│ ├── express-router.spec.js
│ ├── path-rewriter.spec.js
│ ├── router.spec.js
│ ├── websocket.spec.js
│ └── http-proxy-middleware.spec.js
├── .editorconfig
├── .travis.yml
├── ISSUE_TEMPLATE.md
├── recipes
├── virtual-hosts.md
├── corporate-proxy.md
├── logLevel.md
├── basic.md
├── websocket.md
├── delay.md
├── shorthand.md
├── logProvider.md
├── pathRewrite.md
├── proxy-events.md
├── router.md
├── context-matching.md
├── modify-post.md
├── README.md
└── servers.md
├── .gitignore
├── examples
├── express
│ └── index.js
├── connect
│ └── index.js
├── browser-sync
│ └── index.js
├── README.md
└── websocket
│ ├── index.js
│ └── index.html
├── LICENSE
├── lib
├── router.js
├── path-rewriter.js
├── handlers.js
├── context-matcher.js
├── config-factory.js
├── logger.js
└── index.js
├── package.json
├── CONTRIBUTING.md
├── CHANGELOG.md
└── README.md
/index.js:
--------------------------------------------------------------------------------
1 | var HPM = require('./lib');
2 |
3 | module.exports = function(context, opts) {
4 | return new HPM(context, opts);
5 | };
6 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "google",
3 | "validateIndentation": 4,
4 | "maximumLineLength": {
5 | "value": 120,
6 | "allExcept": ["comments", "regex"]
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/test/unit/_libs.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | configFactory: require('../../lib/config-factory'),
3 | contextMatcher: require('../../lib/context-matcher'),
4 | handlers: require('../../lib/handlers'),
5 | Logger: require('../../lib/logger'),
6 | pathRewriter: require('../../lib/path-rewriter'),
7 | router: require('../../lib/router')
8 | };
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | indent_style = space
9 | indent_size = 4
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
16 | # Matches the exact files either package.json or .travis.yml
17 | [{package.json,.travis.yml}]
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'stable'
4 | - '6'
5 | - '5'
6 | - '4'
7 | - '0.12'
8 | - '0.11'
9 | - '0.10'
10 | before_install:
11 | # https://github.com/npm/npm/issues/11283
12 | - npm set progress=false
13 | after_success: npm run coveralls
14 | env:
15 | global:
16 | # COVERALLS_REPO_TOKEN=
17 | secure: "I8mDH0n2DQHAPvUFEV/ZNmrMNYTJxgywg8+P3yuCAWwQkZeXQi0DWG+6VUpOylaRhL/4kCdZK9gnJD2MfrqvNqVLDUqBK3tTmZVoyqqJEdE0UdEHcPncAPmxWrG98sDjqFN9r4nWeizHvyA1fNRQHRN56Zy+DcQWjgEhHJaYeNA="
18 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Expected behavior
2 |
3 | ### Actual behavior
4 |
5 | ### Setup
6 |
7 | * http-proxy-middleware: _version_
8 | * server: _connect/express/browser-sync..._ + _version_
9 | * other relevant modules
10 |
11 | #### proxy middleware configuration
12 | ```javascript
13 | var apiProxy = proxy('/api', {target:'http://www.example.org', changeOrigin:true});
14 | ```
15 | #### server mounting
16 | ```javascript
17 | var app = express();
18 |
19 | app.use(apiProxy);
20 | app.listen(3000);
21 | ```
22 |
--------------------------------------------------------------------------------
/test/e2e/_utils.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var proxyMiddleware = require('../../index');
3 |
4 | module.exports = {
5 | createServer: createServer,
6 | proxyMiddleware: proxyMiddleware
7 | };
8 |
9 | function createServer(portNumber, middleware, path) {
10 | var app = express();
11 |
12 | if (middleware, path) {
13 | app.use(path, middleware);
14 | } else if (middleware) {
15 | app.use(middleware);
16 | }
17 |
18 | var server = app.listen(portNumber);
19 |
20 | return server;
21 | }
22 |
--------------------------------------------------------------------------------
/recipes/virtual-hosts.md:
--------------------------------------------------------------------------------
1 | # Name-based Virtual Hosts
2 |
3 | This example will create a basic proxy middleware for [virtual hosted sites](https://en.wikipedia.org/wiki/Virtual_hosting#Name-based).
4 |
5 | When `changeOrigin` is set to `true`; Host [HTTP header](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields) will be set to match target's host.
6 |
7 | The `changeOrigin` option is provided by [http-proxy](https://github.com/nodejitsu/node-http-proxy).
8 |
9 | ```javascript
10 | var proxy = require("http-proxy-middleware");
11 |
12 | var options = {
13 | target: 'http://localhost:3000',
14 | changeOrigin:true
15 | };
16 |
17 | var apiProxy = proxy('/api', options);
18 | ```
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
29 |
--------------------------------------------------------------------------------
/recipes/corporate-proxy.md:
--------------------------------------------------------------------------------
1 | # Corporate Proxy Support
2 |
3 | This example will create a basic proxy middleware with corporate proxy support.
4 |
5 | Provide a custom `http.agent` with [https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) to connect to the corporate proxy server.
6 |
7 | ```javascript
8 | var HttpsProxyAgent = require('https-proxy-agent');
9 | var proxy = require("http-proxy-middleware");
10 |
11 | // corporate proxy to connect to
12 | var proxyServer = process.env.HTTPS_PROXY ||
13 | process.env.HTTP_PROXY;
14 |
15 | var options = {
16 | target: 'http://localhost:3000',
17 | agent: new HttpsProxyAgent(proxyServer)
18 | };
19 |
20 | var apiProxy = proxy('/api', options);
21 | ```
22 |
--------------------------------------------------------------------------------
/recipes/logLevel.md:
--------------------------------------------------------------------------------
1 | # Log Level
2 |
3 | Control the amount of logging of http-proxy-middleware.
4 |
5 | Possible values:
6 | * `debug`
7 | * `info`
8 | * `warn` (default)
9 | * `error`
10 | * `silent`
11 |
12 | ## Level: debug
13 |
14 | Log everyting.
15 |
16 | ```javascript
17 | var proxy = require("http-proxy-middleware");
18 |
19 | var options = {
20 | target: 'http://localhost:3000',
21 | logLevel: 'debug'
22 | };
23 |
24 | var apiProxy = proxy('/api', options);
25 | ```
26 |
27 | ## Level: silent
28 |
29 | Suppress all logging.
30 |
31 | ```javascript
32 | var proxy = require("http-proxy-middleware");
33 |
34 | var options = {
35 | target: 'http://localhost:3000',
36 | logLevel: 'silent'
37 | };
38 |
39 | var apiProxy = proxy('/api', options);
40 | ```
41 |
--------------------------------------------------------------------------------
/examples/express/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var express = require('express');
5 | var proxy = require('../../index'); // require('http-proxy-middleware');
6 |
7 | /**
8 | * Configure proxy middleware
9 | */
10 | var jsonPlaceholderProxy = proxy({
11 | target: 'http://jsonplaceholder.typicode.com',
12 | changeOrigin: true, // for vhosted sites, changes host header to match to target's host
13 | logLevel: 'debug'
14 | });
15 |
16 | var app = express();
17 |
18 | /**
19 | * Add the proxy to express
20 | */
21 | app.use('/users', jsonPlaceholderProxy);
22 |
23 | app.listen(3000);
24 |
25 | console.log('[DEMO] Server: listening on port 3000');
26 | console.log('[DEMO] Opening: http://localhost:3000/users');
27 |
28 | require('opn')('http://localhost:3000/users');
29 |
--------------------------------------------------------------------------------
/examples/connect/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var http = require('http');
5 | var connect = require('connect');
6 | var proxy = require('../../index'); // require('http-proxy-middleware');
7 |
8 | /**
9 | * Configure proxy middleware
10 | */
11 | var jsonPlaceholderProxy = proxy({
12 | target: 'http://jsonplaceholder.typicode.com',
13 | changeOrigin: true, // for vhosted sites, changes host header to match to target's host
14 | logLevel: 'debug'
15 | });
16 |
17 | var app = connect();
18 |
19 | /**
20 | * Add the proxy to connect
21 | */
22 | app.use('/users', jsonPlaceholderProxy);
23 |
24 | http.createServer(app).listen(3000);
25 |
26 | console.log('[DEMO] Server: listening on port 3000');
27 | console.log('[DEMO] Opening: http://localhost:3000/users');
28 |
29 | require('opn')('http://localhost:3000/users');
30 |
--------------------------------------------------------------------------------
/examples/browser-sync/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var browserSync = require('browser-sync').create();
5 | var proxy = require('../../index'); // require('http-proxy-middleware');
6 |
7 | /**
8 | * Configure proxy middleware
9 | */
10 | var jsonPlaceholderProxy = proxy('/users', {
11 | target: 'http://jsonplaceholder.typicode.com',
12 | changeOrigin: true, // for vhosted sites, changes host header to match to target's host
13 | logLevel: 'debug'
14 | });
15 |
16 | /**
17 | * Add the proxy to browser-sync
18 | */
19 | browserSync.init({
20 | server: {
21 | baseDir: './',
22 | port: 3000,
23 | middleware: [jsonPlaceholderProxy],
24 | },
25 | startPath: '/users'
26 | });
27 |
28 | console.log('[DEMO] Server: listening on port 3000');
29 | console.log('[DEMO] Opening: http://localhost:3000/users');
30 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | View working examples of `http-proxy-middleware` implemented in popular servers.
4 |
5 | To run and view the [examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples); Clone the `http-proxy-middleware` repo and install the dependencies:
6 |
7 | ```bash
8 | $ git clone https://github.com/chimurai/http-proxy-middleware.git
9 | $ cd http-proxy-middleware
10 | $ npm install
11 | ```
12 |
13 | Run the example from root folder:
14 |
15 | ```bash
16 | $ node examples/browser-sync
17 | ```
18 |
19 | ```bash
20 | $ node examples/connect
21 | ```
22 |
23 | ```bash
24 | $ node examples/express
25 | ```
26 |
27 | ```bash
28 | $ node examples/websocket
29 | ```
30 |
31 | ## Server recipes
32 |
33 | You can find more server implementations with `http-proxy-middleware` in the [server recipes](https://github.com/chimurai/http-proxy-middleware/tree/master/recipes/servers.md)
34 |
--------------------------------------------------------------------------------
/recipes/basic.md:
--------------------------------------------------------------------------------
1 | # Basic usage
2 |
3 | This example will create a basic proxy middleware.
4 |
5 | ```javascript
6 | var proxy = require("http-proxy-middleware");
7 |
8 | var apiProxy = proxy('/api', {target: 'http://localhost:3000'});
9 | // \____/ \________________________________/
10 | // | |
11 | // context options
12 | ```
13 |
14 | ## Alternative configuration
15 |
16 | The proxy behavior of the following examples are **exactly** the same; Just different ways to configure it.
17 |
18 | ```javascript
19 | app.use(proxy('/api', {target: 'http://localhost:3000', changeOrigin:true}));
20 | ```
21 |
22 | ```javascript
23 | app.use(proxy('http://localhost:3000/api', {changeOrigin:true}));
24 | ```
25 |
26 | ```javascript
27 | app.use('/api', proxy('http://localhost:3000', {changeOrigin:true}));
28 | ```
29 |
30 | ```javascript
31 | app.use('/api', proxy({target: 'http://localhost:3000', changeOrigin:true}));
32 | ```
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Steven Chim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/recipes/websocket.md:
--------------------------------------------------------------------------------
1 | # WebSocket
2 |
3 | This example will create a proxy middleware with websocket support.
4 |
5 | ```javascript
6 | var proxy = require("http-proxy-middleware");
7 |
8 | var socketProxy = proxy('/socket', {target: 'http://localhost:3000', ws: true});
9 | ```
10 |
11 | ## WebSocket - Path Rewrite
12 |
13 | This example will create a proxy middleware with websocket support and pathRewrite.
14 |
15 | ```javascript
16 | var proxy = require("http-proxy-middleware");
17 |
18 | var options = {
19 | target: 'http://localhost:3000',
20 | ws: true,
21 | pathRewrite: {
22 | '^/socket' : ''
23 | }
24 | };
25 |
26 | var socketProxy = proxy('/socket', options);
27 | ```
28 |
29 | ## WebSocket - Server update subscription
30 |
31 | This example will create a proxy middleware with websocket support.
32 |
33 | Subscribe to server's upgrade event.
34 |
35 | ```javascript
36 | var proxy = require("http-proxy-middleware");
37 |
38 | var socketProxy = proxy('/socket', {target: 'http://localhost:3000', ws: true});
39 |
40 | server.on('upgrade', proxy.upgrade); // <-- subscribe to http 'upgrade'
41 | ```
42 |
--------------------------------------------------------------------------------
/lib/router.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var logger = require('./logger.js').getInstance();
3 |
4 | module.exports = {
5 | getTarget: getTarget
6 | };
7 |
8 | function getTarget(req, config) {
9 | var newTarget;
10 | var router = config.router;
11 |
12 | if (_.isPlainObject(router)) {
13 | newTarget = getTargetFromProxyTable(req, router);
14 | } else if (_.isFunction(router)) {
15 | newTarget = router(req);
16 | }
17 |
18 | return newTarget;
19 | }
20 |
21 | function getTargetFromProxyTable(req, table) {
22 | var result;
23 | var host = req.headers.host;
24 | var path = req.url;
25 |
26 | var hostAndPath = host + path;
27 |
28 | _.forIn(table, function(value, key) {
29 | if (containsPath(key)) {
30 |
31 | if (hostAndPath.indexOf(key) > -1) { // match 'localhost:3000/api'
32 | result = table[key];
33 | logger.debug('[HPM] Router table match: "%s"', key);
34 | return false;
35 | }
36 | } else {
37 |
38 | if (key === host) { // match 'localhost:3000'
39 | result = table[key];
40 | logger.debug('[HPM] Router table match: "%s"', host);
41 | return false;
42 | }
43 |
44 | }
45 |
46 | });
47 |
48 | return result;
49 | }
50 |
51 | function containsPath(v) {
52 | return v.indexOf('/') > -1;
53 | }
54 |
--------------------------------------------------------------------------------
/recipes/delay.md:
--------------------------------------------------------------------------------
1 | # Delay proxied request/response
2 |
3 | Sometimes we need the ability to delay request to backend server or response from it back to client to test long execution time of particular url or all of them. With DevTool's [network throttling](https://developers.google.com/web/tools/chrome-devtools/profile/network-performance/network-conditions?hl=en) we can test slowdown of all request, not separately.
4 | But we can handle each request individually via our proxy, and add delay to its execution time.
5 |
6 | Let's assume that we want slow down the access to backend's `/api/get-me-something` resource. Delay request time by 2 seconds and increase response time by 5 seconds.
7 |
8 | For achieving it just put additional route handler to your app before proxy handler:
9 |
10 | ```javascript
11 | const myProxy = proxy({
12 | target: 'http://www.example.com',
13 | changeOrigin: true
14 | });
15 | const proxyDelay = function (req, res, next) {
16 | if (req.originalUrl === '/api/get-me-something') {
17 | // Delay request by 2 seconds
18 | setTimeout(next, 2000);
19 |
20 | // Delay response completion by 5 seconds
21 | const endOriginal = res.end;
22 | res.end = function (...args) {
23 | setTimeout(function () {
24 | endOriginal.apply(res, args);
25 | }, 5000);
26 | };
27 | } else {
28 | next();
29 | }
30 | };
31 |
32 | app.use('/api', proxyDelay, myProxy);
33 | ```
34 |
35 | And you will see result in devtools similar to this:
36 |
37 | 
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "http-proxy-middleware",
3 | "version": "0.17.3",
4 | "description": "The one-liner node.js proxy middleware for connect, express and browser-sync",
5 | "main": "index.js",
6 | "files": [
7 | "index.js",
8 | "lib"
9 | ],
10 | "scripts": {
11 | "clean": "rm -rf coverage",
12 | "test": "mocha --recursive --colors --reporter spec",
13 | "cover": "npm run clean && istanbul cover ./node_modules/mocha/bin/_mocha -- --recursive",
14 | "coveralls": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --recursive --reporter spec && istanbul-coveralls && npm run clean"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/chimurai/http-proxy-middleware.git"
19 | },
20 | "keywords": [
21 | "reverse",
22 | "proxy",
23 | "middleware",
24 | "http",
25 | "https",
26 | "connect",
27 | "express",
28 | "browser-sync",
29 | "gulp",
30 | "grunt-contrib-connect",
31 | "websocket",
32 | "ws",
33 | "cors"
34 | ],
35 | "author": "Steven Chim",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/chimurai/http-proxy-middleware/issues"
39 | },
40 | "homepage": "https://github.com/chimurai/http-proxy-middleware",
41 | "devDependencies": {
42 | "browser-sync": "^2.18.2",
43 | "chai": "^3.5.0",
44 | "connect": "^3.5.0",
45 | "coveralls": "^2.11.15",
46 | "express": "^4.14.0",
47 | "istanbul": "^0.4.5",
48 | "istanbul-coveralls": "^1.0.3",
49 | "mocha": "^3.2.0",
50 | "mocha-lcov-reporter": "1.2.0",
51 | "opn": "^4.0.2",
52 | "ws": "^1.1.1"
53 | },
54 | "dependencies": {
55 | "http-proxy": "^1.16.2",
56 | "is-glob": "^3.1.0",
57 | "lodash": "^4.17.2",
58 | "micromatch": "^2.3.11"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/examples/websocket/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var express = require('express');
5 | var proxy = require('../../index'); // require('http-proxy-middleware');
6 |
7 | /**
8 | * Configure proxy middleware
9 | */
10 | var wsProxy = proxy('/', {
11 | target: 'http://echo.websocket.org',
12 | // pathRewrite: {
13 | // '^/websocket' : '/socket', // rewrite path.
14 | // '^/removepath' : '' // remove path.
15 | // },
16 | changeOrigin: true, // for vhosted sites, changes host header to match to target's host
17 | ws: true, // enable websocket proxy
18 | logLevel: 'debug'
19 | });
20 |
21 | var app = express();
22 | app.use('/', express.static(__dirname)); // demo page
23 | app.use(wsProxy); // add the proxy to express
24 |
25 | var server = app.listen(3000);
26 | server.on('upgrade', wsProxy.upgrade); // optional: upgrade externally
27 |
28 | console.log('[DEMO] Server: listening on port 3000');
29 | console.log('[DEMO] Opening: http://localhost:3000');
30 |
31 | require('opn')('http://localhost:3000');
32 |
33 | /**
34 | * Example:
35 | * Open http://localhost:3000 in WebSocket compatible browser.
36 | * In browser console:
37 | * 1. `var socket = new WebSocket('ws://localhost:3000');` // create new WebSocket
38 | * 2. `socket.onmessage = function (msg) {console.log(msg)};` // listen to socket messages
39 | * 3. `socket.send('hello world');` // send message
40 | * > {data: "hello world"} // server should echo back your message.
41 | **/
42 |
--------------------------------------------------------------------------------
/recipes/shorthand.md:
--------------------------------------------------------------------------------
1 | # Shorthand
2 |
3 | This example will create a proxy middleware using the shorthand notation.
4 |
5 | The http-proxy-middleware `context` and `config.target` will be set automatically.
6 |
7 | ```javascript
8 | var proxy = require("http-proxy-middleware");
9 |
10 | var apiProxy = proxy('http://localhost:3000/api');
11 |
12 | // equivalent:
13 | // var apiProxy = proxy('/api', {target:'http://localhost:3000'});
14 | ```
15 |
16 | ## Shorthand - Wildcard context
17 |
18 | This example will create a proxy middleware with shorthand wildcard context.
19 |
20 | ```javascript
21 | var proxy = require("http-proxy-middleware");
22 |
23 | var apiProxy = proxy('http://localhost:3000/api/books/*/**.json');
24 | // equals:
25 | // var apiProxy = proxy('/api/books/*/**.json', {target:'http://localhost:3000'});
26 | ```
27 |
28 |
29 | ## Shorthand with additional configuration
30 |
31 | This example will create a proxy middleware with shorthand and additional configuration.
32 |
33 | ```javascript
34 | var proxy = require("http-proxy-middleware");
35 |
36 | var apiProxy = proxy('http://localhost:3000/api', {changeOrigin: true});
37 | // equals:
38 | // var apiProxy = proxy('/api', {target:'http://localhost:3000', {changeOrigin:true}});
39 | ```
40 |
41 | ## Shorthand - WebSocket
42 |
43 | This example will create a proxy middleware with shorthand and additional configuration for WebSocket support.
44 |
45 | ```javascript
46 | var proxy = require("http-proxy-middleware");
47 |
48 | var apiProxy = proxy('http://localhost:3000/api', {ws: true});
49 | // equals:
50 | // var apiProxy = proxy('/api', {target:'http://localhost:3000', ws: true});
51 | ```
52 |
53 | ## Shorthand - WebSocket only
54 |
55 | This example will create a proxy middleware with websocket shorthand only configuration.
56 |
57 | ```javascript
58 | var proxy = require("http-proxy-middleware");
59 |
60 | var apiProxy = proxy('ws://localhost:3000/api');
61 | // equals:
62 | // var apiProxy = proxy('/api', {target:'ws://localhost:3000', ws: true});
63 | ```
64 |
--------------------------------------------------------------------------------
/recipes/logProvider.md:
--------------------------------------------------------------------------------
1 | # Log Provider
2 |
3 | Configure your own logger with the `logProvider` option.
4 |
5 | In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging.
6 |
7 | ```javascript
8 | var winston = require('winston');
9 | var proxy = require("http-proxy-middleware");
10 |
11 | var options = {
12 | target: 'http://localhost:3000',
13 | logProvider: function (provider) {
14 | return winston;
15 | }
16 | };
17 |
18 | var apiProxy = proxy('/api', options);
19 | ```
20 |
21 | ## Winston
22 |
23 | Configure your own logger with the `logProvider` option.
24 |
25 | In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging. Map the logging api if needed.
26 |
27 | ```javascript
28 |
29 | var winston = require('winston');
30 | var proxy = require("http-proxy-middleware");
31 |
32 | var logProvider = function (provider) {
33 | return {
34 | log : winston.log,
35 | debug : winston.debug,
36 | info : winston.info,
37 | warn : winston.warn,
38 | error : winston.error
39 | };
40 | };
41 |
42 | var options = {
43 | target: 'http://localhost:3000',
44 | logProvider: logProvider
45 | };
46 |
47 | var apiProxy = proxy('/api', options);
48 | ```
49 |
50 | # Winston Multi Transport
51 |
52 | Configure your own logger with the `logProvider` option.
53 |
54 | In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging.
55 |
56 | ```javascript
57 | var winston = require('winston');
58 | var proxy = require("http-proxy-middleware");
59 |
60 | var logProvider = function (provider) {
61 | var logger = new (winston.Logger)({
62 | transports: [
63 | new (winston.transports.Console)(),
64 | new (winston.transports.File)({ filename: 'somefile.log' })
65 | ]
66 | });
67 |
68 | return logger;
69 | };
70 |
71 | var options = {
72 | target: 'http://localhost:3000',
73 | logProvider: logProvider
74 | };
75 |
76 | var apiProxy = proxy('/api', options);
77 | ```
78 |
--------------------------------------------------------------------------------
/test/e2e/express-router.spec.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var expect = require('chai').expect;
3 | var http = require('http');
4 | var proxy = require('../../index');
5 |
6 | describe('Usage in Express', function() {
7 |
8 | var app;
9 | var server;
10 |
11 | beforeEach(function() {
12 | app = express();
13 | });
14 |
15 | afterEach(function() {
16 | server && server.close();
17 | });
18 |
19 | // https://github.com/chimurai/http-proxy-middleware/issues/94
20 | describe('Express Sub Route', function() {
21 |
22 | beforeEach(function() {
23 |
24 | // sub route config
25 | var sub = new express.Router();
26 |
27 | function filter(pathname, req) {
28 | var urlFilter = new RegExp('^/sub/api');
29 | var match = urlFilter.test(pathname);
30 | return match;
31 | }
32 |
33 | /**
34 | * Mount proxy without 'path' in sub route
35 | */
36 | var proxyConfig = {target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, logLevel: 'silent'};
37 | sub.use(proxy(filter, proxyConfig));
38 |
39 | sub.get('/hello', jsonMiddleware({'content': 'foobar'}));
40 |
41 | // configure sub route on /sub junction
42 | app.use('/sub', sub);
43 |
44 | // start server
45 | server = app.listen(3000);
46 | });
47 |
48 | it('should still return a response when route does not match proxyConfig', function(done) {
49 | var responseBody;
50 | http.get('http://localhost:3000/sub/hello', function(res) {
51 | res.on('data', function(chunk) {
52 | responseBody = chunk.toString();
53 | expect(responseBody).to.equal('{"content":"foobar"}');
54 | done();
55 | });
56 | });
57 | });
58 |
59 | });
60 |
61 | function jsonMiddleware(data) {
62 | return function(req, res) {
63 | res.json(data);
64 | };
65 | }
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/recipes/pathRewrite.md:
--------------------------------------------------------------------------------
1 | # pathRewrite
2 |
3 | Modify request paths before requests are send to the target.
4 |
5 |
6 |
7 | - [rewrite paths](#rewrite-paths)
8 | - [remove paths](#remove-paths)
9 | - [add paths](#add-paths)
10 | - [custom rewrite function](#custom-rewrite-function)
11 |
12 |
13 |
14 |
15 | ## rewrite paths
16 |
17 | Rewrite paths
18 |
19 | ```javascript
20 | var proxy = require('http-proxy-middleware');
21 |
22 | var options = {
23 | target: 'http://localhost:3000',
24 | pathRewrite: {
25 | '^/api/old-path' : '/api/new-path', // rewrite path
26 | },
27 | };
28 |
29 | var apiProxy = proxy('/api', options);
30 |
31 | // `/old/api/foo/bar` -> `http://localhost:3000/new/api/foo/bar`
32 | ```
33 |
34 | ## remove paths
35 |
36 | Remove base path
37 |
38 | ```javascript
39 | var proxy = require('http-proxy-middleware');
40 |
41 | var options = {
42 | target: 'http://localhost:3000',
43 | pathRewrite: {
44 | '^/api/' : '/' // remove base path
45 | }
46 | };
47 |
48 | var apiProxy = proxy('/api', options);
49 |
50 | // `/api/lorum/ipsum` -> `http://localhost:3000/lorum/ipsum`
51 | ```
52 |
53 | ## add paths
54 |
55 | Add base path
56 |
57 | ```javascript
58 | var proxy = require('http-proxy-middleware');
59 |
60 | var options = {
61 | target: 'http://localhost:3000',
62 | pathRewrite: {
63 | '^/' : '/extra/' // add base path
64 | }
65 | };
66 |
67 | var apiProxy = proxy('/api', options);
68 |
69 | // `/api/lorum/ipsum` -> `http://localhost:3000/extra/api/lorum/ipsum`
70 | ```
71 |
72 | ## custom rewrite function
73 |
74 | Implement you own path rewrite logic.
75 |
76 | The unmodified path will be used, when rewrite function returns `undefined`
77 |
78 | ```javascript
79 | var proxy = require('http-proxy-middleware');
80 |
81 | var rewriteFn = function (path, req) {
82 | return path.replace('/api/foo', '/api/bar');
83 | }
84 |
85 | var options = {
86 | target: 'http://localhost:3000',
87 | pathRewrite: rewriteFn
88 | };
89 |
90 | var apiProxy = proxy('/api', options);
91 |
92 | // `/api/foo/lorum/ipsum` -> `http://localhost:3000/api/bar/lorum/ipsum`
93 | ```
94 |
--------------------------------------------------------------------------------
/lib/path-rewriter.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var logger = require('./logger').getInstance();
3 |
4 | module.exports = {
5 | create: createPathRewriter
6 | };
7 |
8 | /**
9 | * Create rewrite function, to cache parsed rewrite rules.
10 | *
11 | * @returns {function} Function to rewrite paths; This function should accept `path` (request.url) as parameter
12 | */
13 | function createPathRewriter(rewriteConfig) {
14 | var rulesCache;
15 |
16 | if (!isValidRewriteConfig(rewriteConfig)) {
17 | return;
18 | }
19 |
20 | if (_.isFunction(rewriteConfig)) {
21 | var customRewriteFn = rewriteConfig;
22 | return customRewriteFn;
23 | } else {
24 | rulesCache = parsePathRewriteRules(rewriteConfig);
25 | return rewritePath;
26 | }
27 |
28 | function rewritePath(path) {
29 | var result = path;
30 |
31 | _.forEach(rulesCache, function(rule) {
32 | if (rule.regex.test(path)) {
33 | result = result.replace(rule.regex, rule.value);
34 | logger.debug('[HPM] Rewriting path from "%s" to "%s"', path, result);
35 | return false;
36 | }
37 | });
38 |
39 | return result;
40 | }
41 | }
42 |
43 | function isValidRewriteConfig(rewriteConfig) {
44 | if (_.isFunction(rewriteConfig)) {
45 | return true;
46 | } else if (!_.isEmpty(rewriteConfig) && _.isPlainObject(rewriteConfig)) {
47 | return true;
48 | } else if (_.isUndefined(rewriteConfig) ||
49 | _.isNull(rewriteConfig) ||
50 | _.isEqual(rewriteConfig, {})) {
51 | return false;
52 | } else {
53 | throw new Error('[HPM] Invalid pathRewrite config. Expecting object with pathRewrite config or a rewrite function');
54 | }
55 | }
56 |
57 | function parsePathRewriteRules(rewriteConfig) {
58 | var rules = [];
59 |
60 | if (_.isPlainObject(rewriteConfig)) {
61 | _.forIn(rewriteConfig, function(value, key) {
62 | rules.push({
63 | regex: new RegExp(key),
64 | value: rewriteConfig[key]
65 | });
66 | logger.info('[HPM] Proxy rewrite rule created: "%s" ~> "%s"', key, rewriteConfig[key]);
67 | });
68 | }
69 |
70 | return rules;
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/lib/handlers.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var logger = require('./logger').getInstance();
3 |
4 | module.exports = {
5 | init: init,
6 | getHandlers: getProxyEventHandlers
7 | };
8 |
9 | function init(proxy, opts) {
10 | var handlers = getProxyEventHandlers(opts);
11 |
12 | _.forIn(handlers, function(handler, eventName) {
13 | proxy.on(eventName, handlers[eventName]);
14 | });
15 |
16 | logger.debug('[HPM] Subscribed to http-proxy events: ', _.keys(handlers));
17 | }
18 |
19 | function getProxyEventHandlers(opts) {
20 | // https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events
21 | var proxyEvents = ['error', 'proxyReq', 'proxyReqWs', 'proxyRes', 'open', 'close'];
22 | var handlers = {};
23 |
24 | _.forEach(proxyEvents, function(event) {
25 | // all handlers for the http-proxy events are prefixed with 'on'.
26 | // loop through options and try to find these handlers
27 | // and add them to the handlers object for subscription in init().
28 | var eventName = _.camelCase('on ' + event);
29 | var fnHandler = _.get(opts, eventName);
30 |
31 | if (_.isFunction(fnHandler)) {
32 | handlers[event] = fnHandler;
33 | }
34 | });
35 |
36 | // add default error handler in absence of error handler
37 | if (!_.isFunction(handlers.error)) {
38 | handlers.error = defaultErrorHandler;
39 | }
40 |
41 | // add default close handler in absence of close handler
42 | if (!_.isFunction(handlers.close)) {
43 | handlers.close = logClose;
44 | }
45 |
46 | return handlers;
47 | };
48 |
49 | function defaultErrorHandler(err, req, res) {
50 | var host = (req.headers && req.headers.host);
51 | var code = err.code;
52 |
53 | if (res.writeHead && !res.headersSent) {
54 | if (/HPE_INVALID/.test(code)) {
55 | res.writeHead(502);
56 | } else {
57 | switch(code) {
58 | case 'ECONNRESET':
59 | case 'ENOTFOUND':
60 | case 'ECONNREFUSED':
61 | res.writeHead(504);
62 | break;
63 | default: res.writeHead(500);
64 | }
65 | }
66 | }
67 |
68 | res.end('Error occured while trying to proxy to: ' + host + req.url);
69 | }
70 |
71 | function logClose(req, socket, head) {
72 | // view disconnected websocket connections
73 | logger.info('[HPM] Client disconnected');
74 | }
75 |
--------------------------------------------------------------------------------
/recipes/proxy-events.md:
--------------------------------------------------------------------------------
1 | # Proxy Events
2 |
3 | Subscribe to [`http-proxy`](https://github.com/nodejitsu/node-http-proxy) [](https://github.com/nodejitsu/node-http-proxy) events: `error`, `proxyReq`, `proxyReqWs`, `proxyRes`, `open`, `close`.
4 |
5 | ## onError
6 |
7 | Subscribe to http-proxy's [error event](https://www.npmjs.com/package/http-proxy#listening-for-proxy-events).
8 |
9 | ```javascript
10 | var proxy = require("http-proxy-middleware");
11 |
12 | var onError = function (err, req, res) {
13 | console.log('Something went wrong.');
14 | console.log('And we are reporting a custom error message.');
15 | };
16 |
17 | var options = {target:'http://localhost:3000', onError: onError};
18 |
19 | var apiProxy = proxy('/api', options);
20 | ```
21 |
22 | ## onProxyReq
23 |
24 | Subscribe to http-proxy's [proxyReq event](https://www.npmjs.com/package/http-proxy#listening-for-proxy-events).
25 |
26 | ```javascript
27 | var proxy = require("http-proxy-middleware");
28 |
29 | var onProxyReq = function (proxyReq, req, res) {
30 | // add new header to request
31 | proxyReq.setHeader('x-added', 'foobar');
32 | };
33 |
34 | var options = {target:'http://localhost:3000', onProxyReq: onProxyReq};
35 |
36 | var apiProxy = proxy('/api', options);
37 | ```
38 |
39 | ## onProxyReqWs
40 |
41 | Subscribe to http-proxy's [proxyReqWs event](https://www.npmjs.com/package/http-proxy#listening-for-proxy-events).
42 |
43 | ```javascript
44 | var proxy = require("http-proxy-middleware");
45 |
46 | var onProxyReqWs = function (proxyReq, req, socket, options, head) {
47 | // add custom header
48 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');
49 | };
50 |
51 | var options = {target:'http://localhost:3000', onProxyReqWs: onProxyReqWs};
52 |
53 | var apiProxy = proxy('/api', options);
54 | ```
55 |
56 | ## onProxyRes
57 |
58 | Subscribe to http-proxy's [proxyRes event](https://www.npmjs.com/package/http-proxy#listening-for-proxy-events).
59 |
60 | ```javascript
61 | var proxy = require("http-proxy-middleware");
62 |
63 | var onProxyRes = function (proxyRes, req, res) {
64 | // add new header to response
65 | proxyRes.headers['x-added'] = 'foobar';
66 |
67 | // remove header from response
68 | delete proxyRes.headers['x-removed'];
69 | };
70 |
71 | var options = {target:'http://localhost:3000', onProxyRes: onProxyRes};
72 |
73 | var apiProxy = proxy('/api', options);
74 | ```
75 |
--------------------------------------------------------------------------------
/recipes/router.md:
--------------------------------------------------------------------------------
1 | # router
2 |
3 | Allows you to route to a different `target` by using a table of a custom function.
4 |
5 |
6 |
7 | - [Custom router function](#custom-router-function)
8 | - [Proxy Table](#proxy-table)
9 |
10 |
11 |
12 |
13 | ## Custom router function
14 |
15 | Write your own router to dynamically route to a different `target`.
16 | The `req` object is provided to retrieve contextual data.
17 |
18 | ```javascript
19 | var express = require('express');
20 | var proxy = require("http-proxy-middleware");
21 |
22 | var customRouter = function(req) {
23 | return 'http://www.example.org' // protocol + host
24 | };
25 |
26 | var options = {
27 | target: 'http://localhost:8000',
28 | router: customRouter
29 | };
30 |
31 | var myProxy = proxy(options);
32 |
33 | var app = express();
34 | app.use(myProxy); // add the proxy to express
35 |
36 | app.listen(3000);
37 | ```
38 |
39 |
40 | ## Proxy Table
41 |
42 | Use a Proxy Table to proxy requests to a different `target` based on:
43 | * Host [HTTP header](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields).
44 | * Request path
45 | * Host HTTP header + path
46 |
47 | ```javascript
48 | var express = require('express');
49 | var proxy = require("http-proxy-middleware");
50 |
51 | var proxyTable = {
52 | "integration.localhost:3000" : "http://localhost:8001", // host only
53 | "staging.localhost:3000" : "http://localhost:8002", // host only
54 | "localhost:3000/api" : "http://localhost:8003", // host + path
55 | "/rest" : "http://localhost:8004" // path only
56 | };
57 |
58 | var options = {
59 | target: 'http://localhost:8000',
60 | router: proxyTable
61 | };
62 |
63 | var myProxy = proxy(options);
64 |
65 | var app = express();
66 | app.use(myProxy); // add the proxy to express
67 |
68 | app.listen(3000);
69 | ```
70 |
71 | ### Example
72 |
73 | In the example above; all requests will be proxied to `http://localhost:8000`.
74 |
75 | When request's `Host HTTP header` and/or `path` match a configuration in the proxyTable, they will be send to matching target.
76 |
77 | ```
78 | http://localhost:3000/lorum/ipsum -> http://localhost:8000/lorum/ipsum
79 | http://integration.localhost:3000/lorum/ipsum -> http://localhost:8001/lorum/ipsum
80 | http://staging.localhost:3000/rest/foo/bar -> http://localhost:8002/rest/foo/bar
81 | http://localhost:3000/api/houses/123 -> http://localhost:8003/api/houses/123
82 | http://localhost:3000/rest/books/123 -> http://localhost:8004/rest/books/123
83 | ```
84 |
--------------------------------------------------------------------------------
/lib/context-matcher.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var url = require('url');
3 | var isGlob = require('is-glob');
4 | var micromatch = require('micromatch');
5 |
6 | module.exports = {
7 | match: matchContext
8 | };
9 |
10 | function matchContext(context, uri, req) {
11 |
12 | // single path
13 | if (isStringPath(context)) {
14 | return matchSingleStringPath(context, uri);
15 | }
16 |
17 | // single glob path
18 | if (isGlobPath(context)) {
19 | return matchSingleGlobPath(context, uri);
20 | }
21 |
22 | // multi path
23 | if (Array.isArray(context)) {
24 | if (context.every(isStringPath)) {
25 | return matchMultiPath(context, uri);
26 | }
27 | if (context.every(isGlobPath)) {
28 | return matchMultiGlobPath(context, uri);
29 | }
30 |
31 | throw new Error('[HPM] Invalid context. Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"]');
32 | }
33 |
34 | // custom matching
35 | if (_.isFunction(context)) {
36 | var pathname = getUrlPathName(uri);
37 | return context(pathname, req);
38 | }
39 |
40 | throw new Error('[HPM] Invalid context. Expecting something like: "/api" or ["/api", "/ajax"]');
41 | }
42 |
43 | /**
44 | * @param {String} context '/api'
45 | * @param {String} uri 'http://example.org/api/b/c/d.html'
46 | * @return {Boolean}
47 | */
48 | function matchSingleStringPath(context, uri) {
49 | var pathname = getUrlPathName(uri);
50 | return pathname.indexOf(context) === 0;
51 | }
52 |
53 | function matchSingleGlobPath(pattern, uri) {
54 | var pathname = getUrlPathName(uri);
55 | var matches = micromatch(pathname, pattern);
56 | return matches && (matches.length > 0);
57 | }
58 |
59 | function matchMultiGlobPath(patternList, uri) {
60 | return matchSingleGlobPath(patternList, uri);
61 | }
62 |
63 | /**
64 | * @param {String} context ['/api', '/ajax']
65 | * @param {String} uri 'http://example.org/api/b/c/d.html'
66 | * @return {Boolean}
67 | */
68 | function matchMultiPath(contextList, uri) {
69 | for (var i = 0; i < contextList.length; i++) {
70 | var context = contextList[i];
71 | if (matchSingleStringPath(context, uri)) {
72 | return true;
73 | }
74 | }
75 | return false;
76 | }
77 |
78 | /**
79 | * Parses URI and returns RFC 3986 path
80 | *
81 | * @param {String} uri from req.url
82 | * @return {String} RFC 3986 path
83 | */
84 | function getUrlPathName(uri) {
85 | return uri && url.parse(uri).pathname;
86 | }
87 |
88 | function isStringPath(context) {
89 | return _.isString(context) && !isGlob(context);
90 | }
91 |
92 | function isGlobPath(context) {
93 | return isGlob(context);
94 | }
95 |
--------------------------------------------------------------------------------
/recipes/context-matching.md:
--------------------------------------------------------------------------------
1 | # Context matching
2 |
3 | Determine which requests should be proxied.
4 |
5 | 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).
6 |
7 | The [RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is used for context matching.
8 |
9 | ```
10 | foo://example.com:8042/over/there?name=ferret#nose
11 | \_/ \______________/\_________/ \_________/ \__/
12 | | | | | |
13 | scheme authority path query fragment
14 | ```
15 |
16 |
17 | `http-proxy-middleware` offers several ways to do this:
18 |
19 |
20 |
21 | - [Path](#path)
22 | - [Multi Path](#multi-path)
23 | - [Wildcard](#wildcard)
24 | - [Multi Wildcard](#multi-wildcard)
25 | - [Wildcard / Exclusion](#wildcard--exclusion)
26 | - [Custom filtering](#custom-filtering)
27 |
28 |
29 |
30 |
31 | ## Path
32 |
33 | This will match paths starting with `/api`
34 |
35 | ```javascript
36 | var proxy = require("http-proxy-middleware");
37 |
38 | var apiProxy = proxy('/api', {target: 'http://localhost:3000'});
39 |
40 | // `/api/foo/bar` -> `http://localhost:3000/api/foo/bar`
41 | ```
42 |
43 | ## Multi Path
44 |
45 | This will match paths starting with `/api` or `/rest`
46 |
47 | ```javascript
48 | var proxy = require("http-proxy-middleware");
49 |
50 | var apiProxy = proxy(['/api', '/rest'], {target: 'http://localhost:3000'});
51 |
52 | // `/api/foo/bar` -> `http://localhost:3000/api/foo/bar`
53 | // `/rest/lorum/ipsum` -> `http://localhost:3000/rest/lorum/ipsum`
54 | ```
55 |
56 | ## Wildcard
57 |
58 | This will match paths starting with `/api/` and should also end with `.json`
59 |
60 | ```javascript
61 | var proxy = require("http-proxy-middleware");
62 |
63 | var apiProxy = proxy('/api/**/*.json', {target: 'http://localhost:3000'});
64 | ```
65 |
66 | ## Multi Wildcard
67 |
68 | Multiple wildcards can be used.
69 |
70 | ```javascript
71 | var proxy = require("http-proxy-middleware");
72 |
73 | var apiProxy = proxy(['/api/**/*.json', '/rest/**'], {target: 'http://localhost:3000'});
74 | ```
75 |
76 | ## Wildcard / Exclusion
77 |
78 | This example will create a proxy with wildcard context matching.
79 |
80 | ```javascript
81 | var proxy = require("http-proxy-middleware");
82 |
83 | var apiProxy = proxy(['foo/*.js', '!bar.js'], {target: 'http://localhost:3000'});
84 | ```
85 |
86 | ## Custom filtering
87 |
88 | Write your custom context matching function to have full control on the matching behavior.
89 | The request `pathname` and `req` object are provided to determine which requests should be proxied or not.
90 |
91 | ```javascript
92 | var proxy = require("http-proxy-middleware");
93 |
94 | var filter = function (pathname, req) {
95 | return (pathname.match('^/api') && req.method === 'GET');
96 | };
97 |
98 | var apiProxy = proxy(filter, {target: 'http://localhost:3000'});
99 | ```
100 |
--------------------------------------------------------------------------------
/examples/websocket/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | http-proxy-middleware - WebSocket example
6 |
19 |
20 |
21 |
22 |
23 | WebSocket demo
24 |
25 |
26 | Proxy ws://localhost:3000 to ws://echo.websocket.org
27 |
28 |
29 |
37 |
48 |
49 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/recipes/modify-post.md:
--------------------------------------------------------------------------------
1 | ##Modify Post Parameters:
2 | The code example below illustrates how to modify POST body data prior to forwarding to the proxy target.
3 | Key to this example is the *"OnProxyReq"* event handler that creates a new POST body that can be manipulated to format the POST data as required. For example: inject new POST parameters that should only be visible server side.
4 |
5 | This example uses the *"body-parser"* module in the main app to create a req.body object with the decoded POST parameters. Side note - the code below will allow *"http-proxy-middleware"* to work with *"body-parser"*.
6 |
7 | Since this only modifys the request body stream the original POST body parameters remain in tact, so any POST data changes will not be sent back in the response to the client.
8 |
9 | ## Example:
10 |
11 | 'use strict';
12 |
13 | var express = require('express');
14 | var ProxyMiddleware = require('http-proxy-middleware');
15 | var router = express.Router();
16 |
17 | var proxy_filter = function (path, req) {
18 | return path.match('^/docs') && ( req.method === 'GET' || req.method === 'POST' );
19 | };
20 |
21 | var proxy_options = {
22 | target: 'http://localhost:8080',
23 | pathRewrite: {
24 | '^/docs' : '/java/rep/server1' // Host path & target path conversion
25 | },
26 | onError(err, req, res) {
27 | res.writeHead(500, {
28 | 'Content-Type': 'text/plain'
29 | });
30 | res.end('Something went wrong. And we are reporting a custom error message.' + err);
31 | },
32 | onProxyReq(proxyReq, req, res) {
33 | if ( req.method == "POST" && req.body ) {
34 | // Add req.body logic here if needed....
35 |
36 | // ....
37 |
38 | // Remove body-parser body object from the request
39 | if ( req.body ) delete req.body;
40 |
41 | // Make any needed POST parameter changes
42 | let body = new Object();
43 |
44 | body.filename = 'reports/statistics/summary_2016.pdf';
45 | body.routeid = 's003b012d002';
46 | body.authid = 'bac02c1d-258a-4177-9da6-862580154960';
47 |
48 | // URI encode JSON object
49 | body = Object.keys( body ).map(function( key ) {
50 | return encodeURIComponent( key ) + '=' + encodeURIComponent( body[ key ])
51 | }).join('&');
52 |
53 | // Update header
54 | proxyReq.setHeader( 'content-type', 'application/x-www-form-urlencoded' );
55 | proxyReq.setHeader( 'content-length', body.length );
56 |
57 | // Write out body changes to the proxyReq stream
58 | proxyReq.write( body );
59 | proxyReq.end();
60 | }
61 | }
62 | };
63 |
64 | // Proxy configuration
65 | var proxy = ProxyMiddleware( proxy_filter, proxy_options );
66 |
67 | /* GET home page. */
68 | router.get('/', function(req, res, next) {
69 | res.render('index', { title: 'Node.js Express Proxy Test' });
70 | });
71 |
72 | router.all('/docs', proxy );
73 |
74 | module.exports = router;
75 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We love contributors! Your help is welcome to make this project better!
4 |
5 | Some simple guidelines we'd like you to follow.
6 |
7 | ## Got Questions or Problems?
8 |
9 | If you have questions about http-proxy-middle usage; Please check if your question hasn't been already answered on [Stack Overflow](http://stackoverflow.com/search?q=%22http-proxy-middleware%22) or in our [issue archive](https://github.com/chimurai/http-proxy-middleware/issues?utf8=%E2%9C%93&q=is%3Aissue+), [examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples) and [recipes](https://github.com/chimurai/http-proxy-middleware/tree/master/recipes).
10 |
11 | Since Nodejitsu's `http-proxy` is providing the actual proxy functionality; You might find your answer in their [documentation](https://github.com/nodejitsu/node-http-proxy), [issue archive](https://github.com/nodejitsu/node-http-proxy/issues?utf8=%E2%9C%93&q=is%3Aissue) or [examples](https://github.com/nodejitsu/node-http-proxy/tree/master/examples).
12 |
13 | Feel free to [ask a question](https://github.com/chimurai/http-proxy-middleware/issues) if the above resources didn't answer your question.
14 |
15 | Tips on how to ask: http://stackoverflow.com/help/how-to-ask
16 |
17 | ## Report Issues
18 |
19 | If you think you've found an issue, please submit it to the [Github issue tracker](https://github.com/chimurai/http-proxy-middleware/issues).
20 |
21 | "*[It doesn't work](https://goo.gl/GzkkTg)*" is not very useful for anyone.
22 | A good issue report should have a well described **problem description** and proxy **configuration**. A great issue report includes a **minimal example**.
23 |
24 | Properly format your code example for easier reading: [Code and Syntax Highlighting](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code-and-syntax-highlighting).
25 |
26 | The quality of your issue report will determine how quickly and deeply we'll delve into it. Some simple pointers for reporting an issue:
27 |
28 | #### Problem Description
29 |
30 | - describe the problem
31 | - steps taken (request urls)
32 | - found
33 | - expected
34 |
35 | #### Configuration / Setup
36 |
37 | - http-proxy-middleware version
38 | - http-proxy-middleware configuration
39 |
40 | It might be useful to provide server information in which http-proxy-middleware is used and the target server information to which requests are being proxied.
41 |
42 | - server + version (express, connect, browser-sync, etc...)
43 | + server port number
44 | - target server
45 | + target server port number
46 |
47 | ### Minimal example
48 |
49 | Provide a minimal example to exclude external factors. This will greatly help identifying the issue.
50 |
51 | Tips on how to create a minimal example: http://stackoverflow.com/help/mcve
52 |
53 | ## New Feature?
54 |
55 | Request a new feature by submitting an issue into our [Github issue tracker](https://github.com/chimurai/http-proxy-middleware/issues).
56 |
57 | PRs are welcome. Please discuss it in our [Github issue tracker](https://github.com/chimurai/http-proxy-middleware/issues) before you start working on it, to avoid wasting your time and effort in case we are already (planning to) working on it.
58 |
59 | ## Documentation
60 |
61 | Feel free to send PRs to improve the documentation, [examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples) and [recipes](https://github.com/chimurai/http-proxy-middleware/tree/master/recipes).
62 |
--------------------------------------------------------------------------------
/recipes/README.md:
--------------------------------------------------------------------------------
1 | # Recipes
2 |
3 | Common usages of `http-proxy-middleware`.
4 |
5 | # Configuration example
6 |
7 | Overview of `http-proxy-middleware` specific options.
8 |
9 | http-proxy-middleware uses Nodejitsu's [http-proxy](https://github.com/nodejitsu/node-http-proxy) to do the actual proxying. All of its [options](https://github.com/nodejitsu/node-http-proxy#options) are exposed via http-proxy-middleware's configuration object.
10 |
11 |
12 | ```javascript
13 | var proxy = require("http-proxy-middleware");
14 | var winston = require('winston');
15 |
16 | /**
17 | * Context matching: decide which path(s) should be proxied. (wildcards supported)
18 | **/
19 | var context = '/api';
20 |
21 | /**
22 | * Proxy options
23 | */
24 | var options = {
25 | // hostname to the target server
26 | target: 'http://localhost:3000',
27 |
28 | // set correct host headers for name-based virtual hosted sites
29 | changeOrigin: true,
30 |
31 | // enable websocket proxying
32 | ws: true,
33 |
34 | // additional request headers
35 | headers: {
36 | 'x-powered-by': 'foobar'
37 | },
38 |
39 | // rewrite paths
40 | pathRewrite: {
41 | '^/api/old-path' : '/api/new-path', // rewrite path
42 | '^/api/remove/path' : '/path' // remove base path
43 | },
44 |
45 | // re-target based on the request's host header and/or path
46 | router: {
47 | // host[/path] :
48 | // /path :
49 | 'integration.localhost:8000' : 'http://localhost:8001', // host only
50 | 'staging.localhost:8000' : 'http://localhost:8002', // host only
51 | 'localhost:8000/api' : 'http://localhost:8003', // host + path
52 | '/rest' : 'http://localhost:8004' // path only
53 | },
54 |
55 | // control logging
56 | logLevel: 'silent',
57 |
58 | // use a different lib for logging;
59 | // i.e., write logs to file or server
60 | logProvider: function (provider) {
61 | return winston;
62 | },
63 |
64 | // subscribe to http-proxy's error event
65 | onError: function onError(err, req, res) {
66 | res.writeHead(500, {'Content-Type': 'text/plain'});
67 | res.end('Something went wrong.');
68 | },
69 |
70 | // subscribe to http-proxy's proxyRes event
71 | onProxyRes: function (proxyRes, req, res) {
72 | proxyRes.headers['x-added'] = 'foobar';
73 | delete proxyRes.headers['x-removed'];
74 | },
75 |
76 | // subscribe to http-proxy's proxyReq event
77 | onProxyReq: function (proxyReq, req, res) {
78 | // add custom header to request
79 | proxyReq.setHeader('x-powered-by', 'foobar');
80 | }
81 |
82 | /**
83 | * The following options are provided by Nodejitsu's http-proxy
84 | */
85 |
86 | // target
87 | // forward
88 | // agent
89 | // ssl
90 | // ws
91 | // xfwd
92 | // secure
93 | // toProxy
94 | // prependPath
95 | // ignorePath
96 | // localAddress
97 | // changeOrigin
98 | // auth
99 | // hostRewrite
100 | // autoRewrite
101 | // protocolRewrite
102 | // headers
103 |
104 | };
105 |
106 | /**
107 | * Create the proxy middleware, so it can be used in a server.
108 | */
109 | var apiProxy = proxy(context, options);
110 | ```
111 |
--------------------------------------------------------------------------------
/test/e2e/path-rewriter.spec.js:
--------------------------------------------------------------------------------
1 | var utils = require('./_utils');
2 | var expect = require('chai').expect;
3 | var http = require('http');
4 |
5 | describe('E2E pathRewrite', function() {
6 | var createServer;
7 | var proxyMiddleware;
8 |
9 | beforeEach(function() {
10 | createServer = utils.createServer;
11 | proxyMiddleware = utils.proxyMiddleware;
12 | });
13 |
14 | var targetMiddleware;
15 | var targetData;
16 |
17 | beforeEach(function() {
18 | targetData = {};
19 | targetMiddleware = function(req, res, next) {
20 | targetData.url = req.url; // store target url.
21 | targetData.headers = req.headers; // store target headers.
22 | res.write(req.url); // respond with target url.
23 | res.end();
24 | };
25 | });
26 |
27 | var proxyServer;
28 | var targetServer;
29 |
30 | beforeEach(function() {
31 | targetServer = createServer(8000, targetMiddleware);
32 | });
33 |
34 | afterEach(function() {
35 | proxyServer && proxyServer.close();
36 | targetServer.close();
37 | });
38 |
39 | describe('Rewrite paths with rules table', function() {
40 | beforeEach(function() {
41 | var proxyConfig = {
42 | target: 'http://localhost:8000',
43 | pathRewrite: {
44 | '^/foobar/api/': '/api/'
45 | }
46 | };
47 | var proxy = proxyMiddleware(proxyConfig);
48 | proxyServer = createServer(3000, proxy);
49 | });
50 |
51 | beforeEach(function(done) {
52 | http.get('http://localhost:3000/foobar/api/lorum/ipsum', function(res) {
53 | done();
54 | });
55 | });
56 |
57 | it('should remove "/foobar" from path', function() {
58 | expect(targetData.url).to.equal('/api/lorum/ipsum');
59 | });
60 | });
61 |
62 | describe('Rewrite paths with function', function() {
63 | var originalPath;
64 | var pathRewriteReqObject;
65 |
66 | beforeEach(function() {
67 | var proxyConfig = {
68 | target: 'http://localhost:8000',
69 | pathRewrite: function(path, req) {
70 | originalPath = path;
71 | pathRewriteReqObject = req;
72 | return path.replace('/foobar', '');
73 | }
74 | };
75 | var proxy = proxyMiddleware(proxyConfig);
76 | proxyServer = createServer(3000, proxy);
77 | });
78 |
79 | beforeEach(function(done) {
80 | http.get('http://localhost:3000/foobar/api/lorum/ipsum', function(res) {
81 | done();
82 | });
83 | });
84 |
85 | it('should remove "/foobar" from path', function() {
86 | expect(targetData.url).to.equal('/api/lorum/ipsum');
87 | });
88 |
89 | it('should provide the `path` parameter with the unmodified path value', function() {
90 | expect(originalPath).to.equal('/foobar/api/lorum/ipsum');
91 | });
92 |
93 | it('should provide the `req` object as second parameter of the rewrite function', function() {
94 | expect(pathRewriteReqObject.method).to.equal('GET');
95 | expect(pathRewriteReqObject.url).to.equal('/api/lorum/ipsum');
96 | });
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/lib/config-factory.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var url = require('url');
3 | var logger = require('./logger').getInstance();
4 |
5 | module.exports = {
6 | createConfig: createConfig
7 | };
8 |
9 | function createConfig(context, opts) {
10 | // structure of config object to be returned
11 | var config = {
12 | context: undefined,
13 | options: {}
14 | };
15 |
16 | // app.use('/api', proxy({target:'http://localhost:9000'}));
17 | if (isContextless(context, opts)) {
18 | config.context = '/';
19 | config.options = _.assign(config.options, context);
20 | }
21 | // app.use('/api', proxy('http://localhost:9000'));
22 | // app.use(proxy('http://localhost:9000/api'));
23 | else if (isStringShortHand(context)) {
24 | var oUrl = url.parse(context);
25 | var target = [oUrl.protocol, '//', oUrl.host].join('');
26 |
27 | config.context = oUrl.pathname || '/';
28 | config.options = _.assign(config.options, {target: target}, opts);
29 |
30 | if (oUrl.protocol === 'ws:' || oUrl.protocol === 'wss:') {
31 | config.options.ws = true;
32 | }
33 | // app.use('/api', proxy({target:'http://localhost:9000'}));
34 | } else {
35 | config.context = context;
36 | config.options = _.assign(config.options, opts);
37 | }
38 |
39 | configureLogger(config.options);
40 |
41 | if (!config.options.target) {
42 | throw new Error('[HPM] Missing "target" option. Example: {target: "http://www.example.org"}');
43 | }
44 |
45 | // Legacy option.proxyHost
46 | config.options = mapLegacyProxyHostOption(config.options);
47 |
48 | // Legacy option.proxyTable > option.router
49 | config.options = mapLegacyProxyTableOption(config.options);
50 |
51 | return config;
52 | }
53 |
54 | /**
55 | * Checks if a String only target/config is provided.
56 | * This can be just the host or with the optional path.
57 | *
58 | * @example
59 | * app.use('/api', proxy('http://localhost:9000'));
60 | app.use(proxy('http://localhost:9000/api'));
61 | *
62 | * @param {String} context [description]
63 | * @return {Boolean} [description]
64 | */
65 | function isStringShortHand(context) {
66 | if (_.isString(context)) {
67 | return (url.parse(context).host) ? true : false;
68 | }
69 | }
70 |
71 | /**
72 | * Checks if a Object only config is provided, without a context.
73 | * In this case the all paths will be proxied.
74 | *
75 | * @example
76 | * app.use('/api', proxy({target:'http://localhost:9000'}));
77 | *
78 | * @param {Object} context [description]
79 | * @param {*} opts [description]
80 | * @return {Boolean} [description]
81 | */
82 | function isContextless(context, opts) {
83 | return (_.isPlainObject(context) && _.isEmpty(opts));
84 | }
85 |
86 | function mapLegacyProxyHostOption(options) {
87 | // set options.headers.host when option.proxyHost is provided
88 | if (options.proxyHost) {
89 | logger.warn('*************************************');
90 | logger.warn('[HPM] Deprecated "option.proxyHost"');
91 | logger.warn(' Use "option.changeOrigin" or "option.headers.host" instead');
92 | logger.warn(' "option.proxyHost" will be removed in future release.');
93 | logger.warn('*************************************');
94 |
95 | options.headers = options.headers || {};
96 | options.headers.host = options.proxyHost;
97 | }
98 |
99 | return options;
100 | }
101 |
102 | // Warn deprecated proxyTable api usage
103 | function mapLegacyProxyTableOption(options) {
104 | if (options.proxyTable) {
105 | logger.warn('*************************************');
106 | logger.warn('[HPM] Deprecated "option.proxyTable"');
107 | logger.warn(' Use "option.router" instead');
108 | logger.warn(' "option.proxyTable" will be removed in future release.');
109 | logger.warn('*************************************');
110 |
111 | options.router = _.clone(options.proxyTable);
112 | _.omit(options, 'proxyTable');
113 | }
114 |
115 | return options;
116 | }
117 |
118 | function configureLogger(options) {
119 | if (options.logLevel) {
120 | logger.setLevel(options.logLevel);
121 | }
122 |
123 | if (options.logProvider) {
124 | logger.setProvider(options.logProvider);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/lib/logger.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var _ = require('lodash');
3 |
4 | var loggerInstance;
5 |
6 | var defaultProvider = {
7 | log: console.log,
8 | debug: console.log, // use .log(); since console does not have .debug()
9 | info: console.info,
10 | warn: console.warn,
11 | error: console.error
12 | };
13 |
14 | // log level 'weight'
15 | var LEVELS = {
16 | debug: 10,
17 | info: 20,
18 | warn: 30,
19 | error: 50,
20 | silent: 80
21 | };
22 |
23 | module.exports = {
24 | // singleton
25 | getInstance: function() {
26 | if (!loggerInstance) {
27 | loggerInstance = new Logger();
28 | }
29 |
30 | return loggerInstance;
31 | },
32 | getArrow: getArrow
33 | };
34 |
35 | function Logger() {
36 | var logLevel;
37 | var provider;
38 |
39 | var api = {
40 | log: log,
41 | debug: debug,
42 | info: info,
43 | warn: warn,
44 | error: error,
45 | setLevel: function(v) {
46 | if (isValidLevel(v)) {
47 | logLevel = v;
48 | }
49 | },
50 | setProvider: function(fn) {
51 | if (fn && isValidProvider(fn)) {
52 | provider = fn(defaultProvider);
53 | }
54 | }
55 | };
56 |
57 | init();
58 |
59 | return api;
60 |
61 | function init() {
62 | api.setLevel('info');
63 | api.setProvider(function() {
64 | return defaultProvider;
65 | });
66 | }
67 |
68 | // log will log messages, regardless of logLevels
69 | function log() {
70 | provider.log(_interpolate.apply(null, arguments));
71 | }
72 |
73 | function debug() {
74 | if (_showLevel('debug')) {
75 | provider.debug(_interpolate.apply(null, arguments));
76 | }
77 | }
78 |
79 | function info() {
80 | if (_showLevel('info')) {
81 | provider.info(_interpolate.apply(null, arguments));
82 | }
83 | }
84 |
85 | function warn() {
86 | if (_showLevel('warn')) {
87 | provider.warn(_interpolate.apply(null, arguments));
88 | }
89 | }
90 |
91 | function error() {
92 | if (_showLevel('error')) {
93 | provider.error(_interpolate.apply(null, arguments));
94 | }
95 | }
96 |
97 | /**
98 | * Decide to log or not to log, based on the log levels 'weight'
99 | * @param {String} showLevel [debug, info, warn, error, silent]
100 | * @return {Boolean}
101 | */
102 | function _showLevel(showLevel) {
103 | var result = false;
104 | var currentLogLevel = LEVELS[logLevel];
105 |
106 | if (currentLogLevel && (currentLogLevel <= LEVELS[showLevel])) {
107 | result = true;
108 | }
109 |
110 | return result;
111 | }
112 |
113 | // make sure logged messages and its data are return interpolated
114 | // make it possible for additional log data, such date/time or custom prefix.
115 | function _interpolate() {
116 | var fn = _.spread(util.format);
117 | var result = fn(_.slice(arguments));
118 |
119 | return result;
120 | }
121 |
122 | function isValidProvider(fnProvider) {
123 | var result = true;
124 |
125 | if (fnProvider && !_.isFunction(fnProvider)) {
126 | throw new Error('[HPM] Log provider config error. Expecting a function.');
127 | }
128 |
129 | return result;
130 | }
131 |
132 | function isValidLevel(levelName) {
133 | var validLevels = _.keys(LEVELS);
134 | var isValid = _.includes(validLevels, levelName);
135 |
136 | if (!isValid) {
137 | throw new Error('[HPM] Log level error. Invalid logLevel.');
138 | }
139 |
140 | return isValid;
141 | }
142 | }
143 |
144 | /**
145 | * -> normal proxy
146 | * => router
147 | * ~> pathRewrite
148 | * ≈> router + pathRewrite
149 | */
150 | function getArrow(originalPath, newPath, originalTarget, newTarget) {
151 | var arrow = ['>'];
152 | var isNewTarget = (originalTarget !== newTarget); // router
153 | var isNewPath = (originalPath !== newPath); // pathRewrite
154 |
155 | if (isNewPath && !isNewTarget) {arrow.unshift('~');} else if (!isNewPath && isNewTarget) {arrow.unshift('=');} else if (isNewPath && isNewTarget) {arrow.unshift('≈');} else {arrow.unshift('-');}
156 |
157 | return arrow.join('');
158 | }
159 |
--------------------------------------------------------------------------------
/test/e2e/router.spec.js:
--------------------------------------------------------------------------------
1 | var utils = require('./_utils');
2 | var expect = require('chai').expect;
3 | var http = require('http');
4 |
5 | describe('E2E router', function() {
6 | var proxyServer, targetServerA, targetServerB, targetServerC;
7 | var createServer;
8 | var proxyMiddleware;
9 |
10 | beforeEach(function() {
11 | createServer = utils.createServer;
12 | proxyMiddleware = utils.proxyMiddleware;
13 | });
14 |
15 | beforeEach(function() {
16 | targetServerA = createServer(6001, function(req, res, next) {
17 | res.write('A');
18 | res.end();
19 | });
20 |
21 | targetServerB = createServer(6002, function(req, res, next) {
22 | res.write('B');
23 | res.end();
24 | });
25 |
26 | targetServerC = createServer(6003, function(req, res, next) {
27 | res.write('C');
28 | res.end();
29 | });
30 | });
31 |
32 | afterEach(function() {
33 | targetServerA.close();
34 | targetServerB.close();
35 | targetServerC.close();
36 | });
37 |
38 | describe('router with proxyTable', function() {
39 | beforeEach(function() {
40 | proxyServer = createServer(6000, proxyMiddleware({
41 | target: 'http://localhost:6001',
42 | router: function(req) {
43 | return 'http://localhost:6003';
44 | }
45 | }));
46 | });
47 |
48 | afterEach(function() {
49 | proxyServer.close();
50 | });
51 |
52 | it('should proxy to: "localhost:6003/api"', function(done) {
53 | var options = {hostname: 'localhost', port: 6000, path: '/api'};
54 | http.get(options, function(res) {
55 | res.on('data', function(chunk) {
56 | var responseBody = chunk.toString();
57 | expect(responseBody).to.equal('C');
58 | done();
59 | });
60 | });
61 | });
62 |
63 | });
64 |
65 | describe('router with proxyTable', function() {
66 |
67 | beforeEach(function setupServers() {
68 | proxyServer = createServer(6000, proxyMiddleware('/', {
69 | target: 'http://localhost:6001',
70 | router: {
71 | 'alpha.localhost:6000': 'http://localhost:6001',
72 | 'beta.localhost:6000': 'http://localhost:6002',
73 | 'localhost:6000/api': 'http://localhost:6003'
74 | }
75 | }));
76 |
77 | });
78 |
79 | afterEach(function() {
80 | proxyServer.close();
81 | });
82 |
83 | it('should proxy to option.target', function(done) {
84 | http.get('http://localhost:6000', function(res) {
85 | res.on('data', function(chunk) {
86 | var responseBody = chunk.toString();
87 | expect(responseBody).to.equal('A');
88 | done();
89 | });
90 | });
91 | });
92 |
93 | it('should proxy when host is "alpha.localhost"', function(done) {
94 | var options = {hostname: 'localhost', port: 6000, path: '/'};
95 | options.headers = {host: 'alpha.localhost:6000'};
96 | http.get(options, function(res) {
97 | res.on('data', function(chunk) {
98 | var responseBody = chunk.toString();
99 | expect(responseBody).to.equal('A');
100 | done();
101 | });
102 | });
103 | });
104 |
105 | it('should proxy when host is "beta.localhost"', function(done) {
106 | var options = {hostname: 'localhost', port: 6000, path: '/'};
107 | options.headers = {host: 'beta.localhost:6000'};
108 | http.get(options, function(res) {
109 | res.on('data', function(chunk) {
110 | var responseBody = chunk.toString();
111 | expect(responseBody).to.equal('B');
112 | done();
113 | });
114 | });
115 | });
116 |
117 | it('should proxy with host & path config: "localhost:6000/api"', function(done) {
118 | var options = {hostname: 'localhost', port: 6000, path: '/api'};
119 | http.get(options, function(res) {
120 | res.on('data', function(chunk) {
121 | var responseBody = chunk.toString();
122 | expect(responseBody).to.equal('C');
123 | done();
124 | });
125 | });
126 | });
127 | });
128 | });
129 |
--------------------------------------------------------------------------------
/test/unit/handlers.spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var handlers = require('./_libs').handlers;
3 |
4 | describe('handlers factory', function() {
5 | var handlersMap;
6 |
7 | it('should return default handlers when no handlers are provided', function() {
8 | handlersMap = handlers.getHandlers();
9 | expect(handlersMap.error).to.be.a('function');
10 | expect(handlersMap.close).to.be.a('function');
11 | });
12 |
13 | describe('custom handlers', function() {
14 | beforeEach(function() {
15 | var fnCustom = function() {
16 | return 42;
17 | };
18 |
19 | var proxyOptions = {
20 | target: 'http://www.example.org',
21 | onError: fnCustom,
22 | onOpen: fnCustom,
23 | onClose: fnCustom,
24 | onProxyReq: fnCustom,
25 | onProxyReqWs: fnCustom,
26 | onProxyRes: fnCustom,
27 | onDummy: fnCustom,
28 | foobar: fnCustom
29 | };
30 |
31 | handlersMap = handlers.getHandlers(proxyOptions);
32 | });
33 |
34 | it('should only return http-proxy handlers', function() {
35 | expect(handlersMap.error).to.be.a('function');
36 | expect(handlersMap.open).to.be.a('function');
37 | expect(handlersMap.close).to.be.a('function');
38 | expect(handlersMap.proxyReq).to.be.a('function');
39 | expect(handlersMap.proxyReqWs).to.be.a('function');
40 | expect(handlersMap.proxyRes).to.be.a('function');
41 | expect(handlersMap.dummy).to.be.undefined;
42 | expect(handlersMap.foobar).to.be.undefined;
43 | expect(handlersMap.target).to.be.undefined;
44 | });
45 |
46 | it('should use the provided custom handlers', function() {
47 | expect(handlersMap.error()).to.equal(42);
48 | expect(handlersMap.open()).to.equal(42);
49 | expect(handlersMap.close()).to.equal(42);
50 | expect(handlersMap.proxyReq()).to.equal(42);
51 | expect(handlersMap.proxyReqWs()).to.equal(42);
52 | expect(handlersMap.proxyRes()).to.equal(42);
53 | });
54 |
55 | });
56 | });
57 |
58 | describe('default proxy error handler', function() {
59 |
60 | var mockError = {
61 | code: 'ECONNREFUSED'
62 | };
63 |
64 | var mockReq = {
65 | headers: {
66 | host: 'localhost:3000'
67 | },
68 | url: '/api'
69 | };
70 |
71 | var proxyOptions = {
72 | target: {
73 | host: 'localhost.dev'
74 | }
75 | };
76 |
77 | var httpErrorCode;
78 | var errorMessage;
79 |
80 | var mockRes = {
81 | writeHead: function(v) {
82 | httpErrorCode = v;
83 | return v;
84 | },
85 | end: function(v) {
86 | errorMessage = v;
87 | return v;
88 | },
89 | headersSent: false
90 | };
91 |
92 | var proxyError;
93 |
94 | beforeEach(function() {
95 | var handlersMap = handlers.getHandlers();
96 | proxyError = handlersMap.error;
97 | });
98 |
99 | afterEach(function() {
100 | httpErrorCode = undefined;
101 | errorMessage = undefined;
102 | });
103 |
104 | var codes = [
105 | ['HPE_INVALID_FOO', 502],
106 | ['HPE_INVALID_BAR', 502],
107 | ['ECONNREFUSED', 504],
108 | ['ENOTFOUND', 504],
109 | ['ECONNREFUSED', 504],
110 | ['any', 500],
111 | ];
112 | codes.forEach(function(item) {
113 | var msg = item[0];
114 | var code = item[1];
115 | it('should set the http status code for ' + msg + ' to: ' + code, function() {
116 | proxyError({ code: msg }, mockReq, mockRes, proxyOptions);
117 | expect(httpErrorCode).to.equal(code);
118 | });
119 | });
120 |
121 | it('should end the response and return error message', function() {
122 | proxyError(mockError, mockReq, mockRes, proxyOptions);
123 | expect(errorMessage).to.equal('Error occured while trying to proxy to: localhost:3000/api');
124 | });
125 |
126 | it('should not set the http status code to: 500 if headers have already been sent', function() {
127 | mockRes.headersSent = true;
128 | proxyError(mockError, mockReq, mockRes, proxyOptions);
129 | expect(httpErrorCode).to.equal(undefined);
130 | });
131 |
132 | it('should end the response and return error message', function() {
133 | mockRes.headersSent = true;
134 | proxyError(mockError, mockReq, mockRes, proxyOptions);
135 | expect(errorMessage).to.equal('Error occured while trying to proxy to: localhost:3000/api');
136 | });
137 |
138 | });
139 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [v0.17.3](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.3)
4 | - fix(onError): improve default proxy error handling. http status codes (504, 502 and 500). ([#132](https://github.com/chimurai/http-proxy-middleware/pull/132)) ([graingert](https://github.com/graingert))
5 |
6 | ## [v0.17.2](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.2)
7 | - feat(logging): improve error message & add link to Node errors page. ([#106](https://github.com/chimurai/http-proxy-middleware/pull/106)) ([cloudmu](https://github.com/cloudmu))
8 | - feat(pathRewrite): path can be empty string. ([#110](https://github.com/chimurai/http-proxy-middleware/pull/110)) ([sunnylqm](https://github.com/sunnylqm))
9 | - bug(websocket): memory leak when option 'ws:true' is used. ([#114](https://github.com/chimurai/http-proxy-middleware/pull/114)) ([julbra](https://github.com/julbra))
10 | - chore(package.json): reduce package size. ([#109](https://github.com/chimurai/http-proxy-middleware/pull/109))
11 |
12 | ## [v0.17.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.1)
13 | - fix(Express sub Router): 404 on non-proxy routes ([#94](https://github.com/chimurai/http-proxy-middleware/issues/94))
14 |
15 | ## [v0.17.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.0)
16 | - fix(context matching): Use [RFC 3986 path](https://tools.ietf.org/html/rfc3986#section-3.3) in context matching. (excludes query parameters)
17 |
18 | ## [v0.16.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.16.0)
19 | - deprecated(proxyTable): renamed `proxyTable` to `router`.
20 | - feat(router): support for custom `router` function.
21 |
22 | ## [v0.15.2](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.15.2)
23 | - fix(websocket): fixes websocket upgrade.
24 |
25 | ## [v0.15.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.15.1)
26 | - feat(pathRewrite): expose `req` object to pathRewrite function.
27 | - fix(websocket): fixes websocket upgrade when both config.ws and external .upgrade() are used.
28 |
29 | ## [v0.15.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.15.0)
30 | - feat(pathRewrite): support for custom pathRewrite function.
31 |
32 | ## [v0.14.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.14.0)
33 | - feat(proxy): support proxy creation without context.
34 | - fix(connect mounting): use connect's `path` configuration to mount proxy.
35 |
36 | ## [v0.13.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.13.0)
37 | - feat(context): custom context matcher; when simple `path` matching is not sufficient.
38 |
39 | ## [v0.12.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.12.0)
40 | - add option `onProxyReqWs` (subscribe to http-proxy `proxyReqWs` event)
41 | - add option `onOpen` (subscribe to http-proxy `open` event)
42 | - add option `onClose` (subscribe to http-proxy `close` event)
43 |
44 | ## [v0.11.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.11.0)
45 | - improved logging
46 |
47 | ## [v0.10.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.10.0)
48 | - feat(proxyTable) - added proxyTable support for WebSockets.
49 | - fixed(proxyTable) - ensure original path (not rewritten path) is being used when `proxyTable` is used in conjunction with `pathRewrite`.
50 |
51 | ## [v0.9.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.9.1)
52 | - fix server crash when socket error not handled correctly.
53 |
54 | ## [v0.9.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.9.0)
55 | - support subscribing to http-proxy `proxyReq` event ([trbngr](https://github.com/trbngr))
56 | - add `logLevel` and `logProvider` support
57 |
58 | ## [v0.8.2](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.8.2)
59 | - fix proxyError handler ([mTazelaar](https://github.com/mTazelaar))
60 |
61 | ## [v0.8.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.8.1)
62 | - fix pathRewrite when `agent` is configured
63 |
64 | ## [v0.8.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.8.0)
65 | - support external websocket upgrade
66 | - fix websocket shorthand
67 |
68 | ## [v0.7.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.7.0)
69 | - support shorthand syntax
70 | - fix express/connect mounting
71 |
72 | ## [v0.6.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.6.0)
73 | - support proxyTable
74 |
75 | ## [v0.5.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.5.0)
76 | - support subscribing to http-proxy `error` event
77 | - support subscribing to http-proxy `proxyRes` event
78 |
79 | ## [v0.4.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.4.0)
80 | - support websocket
81 |
82 | ## [v0.3.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.3.0)
83 | - support wildcard / glob
84 |
85 | ## [v0.2.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.2.0)
86 | - support multiple paths
87 |
88 | ## [v0.1.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.1.0)
89 | - support path rewrite
90 | - deprecate proxyHost option
91 |
92 | ## [v0.0.5](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.0.5)
93 | - initial release
94 |
--------------------------------------------------------------------------------
/test/e2e/websocket.spec.js:
--------------------------------------------------------------------------------
1 | var utils = require('./_utils');
2 | var expect = require('chai').expect;
3 | var http = require('http');
4 | var WebSocket = require('ws');
5 | var WebSocketServer = require('ws').Server;
6 |
7 | describe('E2E WebSocket proxy', function() {
8 | var createServer;
9 | var proxyMiddleware;
10 |
11 | beforeEach(function() {
12 | createServer = utils.createServer;
13 | proxyMiddleware = utils.proxyMiddleware;
14 | });
15 |
16 | var proxyServer, ws, wss;
17 | var targetHeaders;
18 | var responseMessage;
19 | var proxy;
20 |
21 | beforeEach(function() {
22 | proxy = proxyMiddleware('/', {
23 | target: 'http://localhost:8000',
24 | ws: true,
25 | pathRewrite: {'^/socket': ''}
26 | });
27 |
28 | proxyServer = createServer(3000, proxy);
29 |
30 | wss = new WebSocketServer({port: 8000});
31 |
32 | wss.on('connection', function connection(ws) {
33 | ws.on('message', function incoming(message) {
34 | ws.send(message); // echo received message
35 | });
36 | });
37 | });
38 |
39 | describe('option.ws', function() {
40 | beforeEach(function(done) {
41 | // need to make a normal http request,
42 | // so http-proxy-middleware can catch the upgrade request
43 | http.get('http://localhost:3000/', function() {
44 | // do a second http request to make
45 | // sure only 1 listener subscribes to upgrade request
46 | http.get('http://localhost:3000/', function() {
47 | ws = new WebSocket('ws://localhost:3000/socket');
48 |
49 | ws.on('message', function incoming(message) {
50 | responseMessage = message;
51 | done();
52 | });
53 |
54 | ws.on('open', function open() {
55 | ws.send('foobar');
56 | });
57 | });
58 | });
59 | });
60 |
61 | it('should proxy to path', function() {
62 | expect(responseMessage).to.equal('foobar');
63 | });
64 | });
65 |
66 | describe('option.ws with external server "upgrade"', function() {
67 | beforeEach(function(done) {
68 | proxyServer.on('upgrade', proxy.upgrade);
69 |
70 | ws = new WebSocket('ws://localhost:3000/socket');
71 |
72 | ws.on('message', function incoming(message) {
73 | responseMessage = message;
74 | done();
75 | });
76 |
77 | ws.on('open', function open() {
78 | ws.send('foobar');
79 | });
80 | });
81 |
82 | it('should proxy to path', function() {
83 | expect(responseMessage).to.equal('foobar');
84 | });
85 | });
86 |
87 | describe('option.ws with external server "upgrade" and shorthand usage', function() {
88 |
89 | beforeEach(function() {
90 | proxyServer.close();
91 | // override
92 | proxy = proxyMiddleware('ws://localhost:8000', {pathRewrite: {'^/socket': ''}});
93 | proxyServer = createServer(3000, proxy);
94 | });
95 |
96 | beforeEach(function(done) {
97 | proxyServer.on('upgrade', proxy.upgrade);
98 |
99 | ws = new WebSocket('ws://localhost:3000/socket');
100 |
101 | ws.on('message', function incoming(message) {
102 | responseMessage = message;
103 | done();
104 | });
105 |
106 | ws.on('open', function open() {
107 | ws.send('foobar');
108 | });
109 | });
110 |
111 | it('should proxy to path', function() {
112 | expect(responseMessage).to.equal('foobar');
113 | });
114 | });
115 |
116 | describe('with router and pathRewrite', function() {
117 |
118 | beforeEach(function() {
119 | proxyServer.close();
120 | // override
121 | proxy = proxyMiddleware('ws://notworkinghost:6789', {router: {'/socket': 'ws://localhost:8000'}, pathRewrite: {'^/socket': ''}});
122 | proxyServer = createServer(3000, proxy);
123 | });
124 |
125 | beforeEach(function(done) {
126 | proxyServer.on('upgrade', proxy.upgrade);
127 |
128 | ws = new WebSocket('ws://localhost:3000/socket');
129 |
130 | ws.on('message', function incoming(message) {
131 | responseMessage = message;
132 | done();
133 | });
134 |
135 | ws.on('open', function open() {
136 | ws.send('foobar');
137 | });
138 | });
139 |
140 | it('should proxy to path', function() {
141 | expect(responseMessage).to.equal('foobar');
142 | });
143 | });
144 |
145 | afterEach(function() {
146 | proxyServer.close();
147 | wss.close();
148 | ws = null;
149 | });
150 | });
151 |
--------------------------------------------------------------------------------
/test/unit/path-rewriter.spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var pathRewriter = require('./_libs').pathRewriter;
3 |
4 | describe('Path rewriting', function() {
5 | var rewriter;
6 | var result;
7 | var config;
8 |
9 | describe('Rewrite rules configuration and usage', function() {
10 |
11 | beforeEach(function() {
12 | config = {
13 | '^/api/old': '/api/new',
14 | '^/remove': '',
15 | 'invalid': 'path/new',
16 | '/valid': '/path/new',
17 | '/some/specific/path': '/awe/some/specific/path',
18 | '/some': '/awe/some'
19 | };
20 | });
21 |
22 | beforeEach(function() {
23 | rewriter = pathRewriter.create(config);
24 | });
25 |
26 | it('should rewrite path', function() {
27 | result = rewriter('/api/old/index.json');
28 | expect(result).to.equal('/api/new/index.json');
29 | });
30 |
31 | it('should remove path', function() {
32 | result = rewriter('/remove/old/index.json');
33 | expect(result).to.equal('/old/index.json');
34 | });
35 |
36 | it('should leave path intact', function() {
37 | result = rewriter('/foo/bar/index.json');
38 | expect(result).to.equal('/foo/bar/index.json');
39 | });
40 |
41 | it('should not rewrite path when config-key does not match url with test(regex)', function() {
42 | result = rewriter('/invalid/bar/foo.json');
43 | expect(result).to.equal('/path/new/bar/foo.json');
44 | expect(result).to.not.equal('/invalid/new/bar/foo.json');
45 | });
46 |
47 | it('should rewrite path when config-key does match url with test(regex)', function() {
48 | result = rewriter('/valid/foo/bar.json');
49 | expect(result).to.equal('/path/new/foo/bar.json');
50 | });
51 |
52 | it('should return first match when similar paths are configured', function() {
53 | result = rewriter('/some/specific/path/bar.json');
54 | expect(result).to.equal('/awe/some/specific/path/bar.json');
55 | });
56 | });
57 |
58 | describe('Rewrite rule: add base path to requests', function() {
59 |
60 | beforeEach(function() {
61 | config = {
62 | '^/': '/extra/base/path/'
63 | };
64 | });
65 |
66 | beforeEach(function() {
67 | rewriter = pathRewriter.create(config);
68 | });
69 |
70 | it('should add base path to requests', function() {
71 | result = rewriter('/api/books/123');
72 | expect(result).to.equal('/extra/base/path/api/books/123');
73 | });
74 | });
75 |
76 | describe('Rewrite function', function() {
77 | var rewriter;
78 |
79 | beforeEach(function() {
80 | rewriter = function(fn) {
81 | var rewriteFn = pathRewriter.create(fn);
82 | var requestPath = '/123/456';
83 | return rewriteFn(requestPath);
84 | };
85 | });
86 |
87 | it('should return unmodified path', function() {
88 | var rewriteFn = function(path) {
89 | return path;
90 | };
91 | expect(rewriter(rewriteFn)).to.equal('/123/456');
92 | });
93 |
94 | it('should return alternative path', function() {
95 | var rewriteFn = function(path) {
96 | return '/foo/bar';
97 | };
98 | expect(rewriter(rewriteFn)).to.equal('/foo/bar');
99 | });
100 |
101 | it('should return replaced path', function() {
102 | var rewriteFn = function(path) {
103 | return path.replace('/456', '/789');
104 | };
105 | expect(rewriter(rewriteFn)).to.equal('/123/789');
106 | });
107 | });
108 |
109 | describe('Invalid configuration', function() {
110 | var badFn;
111 |
112 | beforeEach(function() {
113 | badFn = function(config) {
114 | return function() {
115 | pathRewriter.create(config);
116 | };
117 | };
118 | });
119 |
120 | it('should return undefined when no config is provided', function() {
121 | expect((badFn())()).to.equal(undefined);
122 | expect((badFn(null)())).to.equal(undefined);
123 | expect((badFn(undefined)())).to.equal(undefined);
124 | });
125 |
126 | it('should throw when bad config is provided', function() {
127 | expect(badFn(123)).to.throw(Error);
128 | expect(badFn('abc')).to.throw(Error);
129 | expect(badFn([])).to.throw(Error);
130 | expect(badFn([1,2,3])).to.throw(Error);
131 | });
132 |
133 | it('should not throw when empty Object config is provided', function() {
134 | expect(badFn({})).to.not.throw(Error);
135 | });
136 |
137 | it('should not throw when function config is provided', function() {
138 | expect(badFn(function() {})).to.not.throw(Error);
139 | });
140 |
141 | });
142 | });
143 |
144 |
--------------------------------------------------------------------------------
/test/unit/router.spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var router = require('./_libs').router;
3 |
4 | describe('router unit test', function() {
5 | var req, config, result;
6 |
7 | beforeEach(function() {
8 | req = {
9 | headers: {
10 | host: 'localhost'
11 | },
12 | url: '/'
13 | };
14 |
15 | config = {
16 | target: 'http://localhost:6000'
17 | };
18 |
19 | });
20 |
21 | describe('router.getTarget from function', function() {
22 | var request;
23 |
24 | beforeEach(function() {
25 | proxyOptionWithRouter = {
26 | target: 'http://localhost:6000',
27 | router: function(req) {
28 | request = req;
29 | return 'http://foobar.com:666';
30 | }
31 | };
32 |
33 | result = router.getTarget(req, proxyOptionWithRouter);
34 | });
35 |
36 | describe('custom dynamic router function', function() {
37 | it('should provide the request object for dynamic routing', function() {
38 | expect(request.headers.host).to.equal('localhost');
39 | expect(request.url).to.equal('/');
40 | });
41 | it('should return new target', function() {
42 | expect(result).to.equal('http://foobar.com:666');
43 | });
44 | });
45 | });
46 |
47 | describe('router.getTarget from table', function() {
48 | beforeEach(function() {
49 | proxyOptionWithRouter = {
50 | target: 'http://localhost:6000',
51 | router: {
52 | 'alpha.localhost': 'http://localhost:6001',
53 | 'beta.localhost': 'http://localhost:6002',
54 | 'gamma.localhost/api': 'http://localhost:6003',
55 | 'gamma.localhost': 'http://localhost:6004',
56 | '/rest': 'http://localhost:6005',
57 | '/some/specific/path': 'http://localhost:6006',
58 | '/some': 'http://localhost:6007'
59 | }
60 | };
61 | });
62 |
63 | describe('without router config', function() {
64 | it('should return the normal target when router not present in config', function() {
65 | result = router.getTarget(req, config);
66 | expect(result).to.equal(undefined);
67 | });
68 | });
69 |
70 | describe('with just the host in router config', function() {
71 | it('should target http://localhost:6001 when for router:"alpha.localhost"', function() {
72 | req.headers.host = 'alpha.localhost';
73 | result = router.getTarget(req, proxyOptionWithRouter);
74 | expect(result).to.equal('http://localhost:6001');
75 | });
76 |
77 | it('should target http://localhost:6002 when for router:"beta.localhost"', function() {
78 | req.headers.host = 'beta.localhost';
79 | result = router.getTarget(req, proxyOptionWithRouter);
80 | expect(result).to.equal('http://localhost:6002');
81 | });
82 | });
83 |
84 | describe('with host and host + path config', function() {
85 | it('should target http://localhost:6004 without path', function() {
86 | req.headers.host = 'gamma.localhost';
87 | result = router.getTarget(req, proxyOptionWithRouter);
88 | expect(result).to.equal('http://localhost:6004');
89 | });
90 |
91 | it('should target http://localhost:6003 exact path match', function() {
92 | req.headers.host = 'gamma.localhost';
93 | req.url = '/api';
94 | result = router.getTarget(req, proxyOptionWithRouter);
95 | expect(result).to.equal('http://localhost:6003');
96 | });
97 |
98 | it('should target http://localhost:6004 when contains path', function() {
99 | req.headers.host = 'gamma.localhost';
100 | req.url = '/api/books/123';
101 | result = router.getTarget(req, proxyOptionWithRouter);
102 | expect(result).to.equal('http://localhost:6003');
103 | });
104 | });
105 |
106 | describe('with just the path', function() {
107 | it('should target http://localhost:6005 with just a path as router config', function() {
108 | req.url = '/rest';
109 | result = router.getTarget(req, proxyOptionWithRouter);
110 | expect(result).to.equal('http://localhost:6005');
111 | });
112 |
113 | it('should target http://localhost:6005 with just a path as router config', function() {
114 | req.url = '/rest/deep/path';
115 | result = router.getTarget(req, proxyOptionWithRouter);
116 | expect(result).to.equal('http://localhost:6005');
117 | });
118 |
119 | it('should target http://localhost:6000 path in not present in router config', function() {
120 | req.url = '/unknow-path';
121 | result = router.getTarget(req, proxyOptionWithRouter);
122 | expect(result).to.equal(undefined);
123 | });
124 | });
125 |
126 | describe('matching order of router config', function() {
127 | it('should return first matching target when similar paths are configured', function() {
128 | req.url = '/some/specific/path';
129 | result = router.getTarget(req, proxyOptionWithRouter);
130 | expect(result).to.equal('http://localhost:6006');
131 | });
132 | });
133 |
134 | });
135 |
136 | });
137 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var httpProxy = require('http-proxy');
3 | var configFactory = require('./config-factory');
4 | var handlers = require('./handlers');
5 | var contextMatcher = require('./context-matcher');
6 | var PathRewriter = require('./path-rewriter');
7 | var Router = require('./router');
8 | var logger = require('./logger').getInstance();
9 | var getArrow = require('./logger').getArrow;
10 |
11 | module.exports = HttpProxyMiddleware;
12 |
13 | function HttpProxyMiddleware(context, opts) {
14 | // https://github.com/chimurai/http-proxy-middleware/issues/57
15 | var wsUpgradeDebounced = _.debounce(handleUpgrade);
16 | var wsInitialized = false;
17 | var config = configFactory.createConfig(context, opts);
18 | var proxyOptions = config.options;
19 |
20 | // create proxy
21 | var proxy = httpProxy.createProxyServer({});
22 | logger.info('[HPM] Proxy created:', config.context, ' -> ', proxyOptions.target);
23 |
24 | var pathRewriter = PathRewriter.create(proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided
25 |
26 | // attach handler to http-proxy events
27 | handlers.init(proxy, proxyOptions);
28 |
29 | // log errors for debug purpose
30 | proxy.on('error', logError);
31 |
32 | // https://github.com/chimurai/http-proxy-middleware/issues/19
33 | // expose function to upgrade externally
34 | middleware.upgrade = wsUpgradeDebounced;
35 |
36 | return middleware;
37 |
38 | function middleware(req, res, next) {
39 | if (shouldProxy(config.context, req)) {
40 | var activeProxyOptions = prepareProxyRequest(req);
41 | proxy.web(req, res, activeProxyOptions);
42 | } else {
43 | next();
44 | }
45 |
46 | if (proxyOptions.ws === true) {
47 | // use initial request to access the server object to subscribe to http upgrade event
48 | catchUpgradeRequest(req.connection.server);
49 | }
50 | }
51 |
52 | function catchUpgradeRequest(server) {
53 | // subscribe once; don't subscribe on every request...
54 | // https://github.com/chimurai/http-proxy-middleware/issues/113
55 | if (!wsInitialized) {
56 | server.on('upgrade', wsUpgradeDebounced);
57 | wsInitialized = true;
58 | }
59 | }
60 |
61 | function handleUpgrade(req, socket, head) {
62 | // set to initialized when used externally
63 | wsInitialized = true;
64 |
65 | if (shouldProxy(config.context, req)) {
66 | var activeProxyOptions = prepareProxyRequest(req);
67 | proxy.ws(req, socket, head, activeProxyOptions);
68 | logger.info('[HPM] Upgrading to WebSocket');
69 | }
70 | }
71 |
72 | /**
73 | * Determine whether request should be proxied.
74 | *
75 | * @private
76 | * @return {Boolean}
77 | */
78 | function shouldProxy(context, req) {
79 | var path = (req.originalUrl || req.url);
80 | return contextMatcher.match(context, path, req);
81 | }
82 |
83 | /**
84 | * Apply option.router and option.pathRewrite
85 | * Order matters:
86 | Router uses original path for routing;
87 | NOT the modified path, after it has been rewritten by pathRewrite
88 | */
89 | function prepareProxyRequest(req) {
90 | // https://github.com/chimurai/http-proxy-middleware/issues/17
91 | // https://github.com/chimurai/http-proxy-middleware/issues/94
92 | req.url = (req.originalUrl || req.url);
93 |
94 | // store uri before it gets rewritten for logging
95 | var originalPath = req.url;
96 | var newProxyOptions = _.cloneDeep(proxyOptions);
97 |
98 | // Apply in order:
99 | // 1. option.router
100 | // 2. option.pathRewrite
101 | __applyRouter(req, newProxyOptions);
102 | __applyPathRewrite(req, pathRewriter);
103 |
104 | // debug logging for both http(s) and websockets
105 | if (proxyOptions.logLevel === 'debug') {
106 | var arrow = getArrow(originalPath, req.url, proxyOptions.target, newProxyOptions.target);
107 | logger.debug('[HPM] %s %s %s %s', req.method, originalPath, arrow, newProxyOptions.target);
108 | }
109 |
110 | return newProxyOptions;
111 | }
112 |
113 | // Modify option.target when router present.
114 | function __applyRouter(req, options) {
115 | var newTarget;
116 |
117 | if (options.router) {
118 | newTarget = Router.getTarget(req, options);
119 |
120 | if (newTarget) {
121 | logger.debug('[HPM] Router new target: %s -> "%s"', options.target, newTarget);
122 | options.target = newTarget;
123 | }
124 | }
125 | }
126 |
127 | // rewrite path
128 | function __applyPathRewrite(req, pathRewriter) {
129 | if (pathRewriter) {
130 | var path = pathRewriter(req.url, req);
131 |
132 | if (typeof path === 'string') {
133 | req.url = path;
134 | } else {
135 | logger.info('[HPM] pathRewrite: No rewritten path found. (%s)', req.url);
136 | }
137 | }
138 | }
139 |
140 | function logError(err, req, res) {
141 | var hostname = (req.headers && req.headers.host) || (req.hostname || req.host); // (websocket) || (node0.10 || node 4/5)
142 | var target = proxyOptions.target.host || proxyOptions.target;
143 | var errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors'; // link to Node Common Systems Errors page
144 |
145 | logger.error('[HPM] Error occurred while trying to proxy request %s from %s to %s (%s) (%s)', req.url, hostname, target, err.code, errReference);
146 | }
147 | };
148 |
--------------------------------------------------------------------------------
/test/unit/config-factory.spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var configFactory = require('./_libs').configFactory;
3 |
4 | describe('configFactory', function() {
5 | var result;
6 | var createConfig = configFactory.createConfig;
7 |
8 | describe('createConfig()', function() {
9 |
10 | describe('classic config', function() {
11 | var context = '/api';
12 | var options = {target: 'http://www.example.org'};
13 |
14 | beforeEach(function() {
15 | result = createConfig(context, options);
16 | });
17 |
18 | it('should return config object', function() {
19 | expect(result).to.have.all.keys('context', 'options');
20 | });
21 |
22 | it('should return config object with context', function() {
23 | expect(result.context).to.equal(context);
24 | });
25 |
26 | it('should return config object with options', function() {
27 | expect(result.options).to.deep.equal(options);
28 | });
29 | });
30 |
31 | describe('shorthand String', function() {
32 | describe('shorthand String config', function() {
33 | beforeEach(function() {
34 | result = createConfig('http://www.example.org:8000/api');
35 | });
36 |
37 | it('should return config object', function() {
38 | expect(result).to.have.all.keys('context', 'options');
39 | });
40 |
41 | it('should return config object with context', function() {
42 | expect(result.context).to.equal('/api');
43 | });
44 |
45 | it('should return config object with options', function() {
46 | expect(result.options).to.deep.equal({target: 'http://www.example.org:8000'});
47 | });
48 | });
49 |
50 | describe('shorthand String config for whole domain', function() {
51 | beforeEach(function() {
52 | result = createConfig('http://www.example.org:8000');
53 | });
54 |
55 | it('should return config object with context', function() {
56 | expect(result.context).to.equal('/');
57 | });
58 | });
59 |
60 | describe('shorthand String config for websocket url', function() {
61 | beforeEach(function() {
62 | result = createConfig('ws://www.example.org:8000');
63 | });
64 |
65 | it('should return config object with context', function() {
66 | expect(result.context).to.equal('/');
67 | });
68 |
69 | it('should return options with ws = true', function() {
70 | expect(result.options.ws).to.equal(true);
71 | });
72 | });
73 |
74 | describe('shorthand String config for secure websocket url', function() {
75 | beforeEach(function() {
76 | result = createConfig('wss://www.example.org:8000');
77 | });
78 |
79 | it('should return config object with context', function() {
80 | expect(result.context).to.equal('/');
81 | });
82 |
83 | it('should return options with ws = true', function() {
84 | expect(result.options.ws).to.equal(true);
85 | });
86 | });
87 |
88 | describe('shorthand String config with globbing', function() {
89 | beforeEach(function() {
90 | result = createConfig('http://www.example.org:8000/api/*.json');
91 | });
92 |
93 | it('should return config object with context', function() {
94 | expect(result.context).to.equal('/api/*.json');
95 | });
96 | });
97 |
98 | describe('shorthand String config with options', function() {
99 | beforeEach(function() {
100 | result = createConfig('http://www.example.org:8000/api', {changeOrigin: true});
101 | });
102 |
103 | it('should return config object with additional options', function() {
104 | expect(result.options).to.deep.equal({target: 'http://www.example.org:8000', changeOrigin: true});
105 | });
106 | });
107 | });
108 |
109 | describe('shorthand Object config', function() {
110 | beforeEach(function() {
111 | result = createConfig({target: 'http://www.example.org:8000'});
112 | });
113 |
114 | it('should set the proxy path to everything', function() {
115 | expect(result.context).to.equal('/');
116 | });
117 |
118 | it('should return config object', function() {
119 | expect(result.options).to.deep.equal({target: 'http://www.example.org:8000'});
120 | });
121 | });
122 |
123 | describe('missing option.target', function() {
124 | var fn;
125 | beforeEach(function() {
126 | fn = function() {
127 | createConfig('/api');
128 | };
129 | });
130 |
131 | it('should throw an error when target option is missing', function() {
132 | expect(fn).to.throw(Error);
133 | });
134 | });
135 |
136 | describe('faulty config. mixing classic with shorthand', function() {
137 | var fn;
138 | beforeEach(function() {
139 | result = createConfig('http://localhost:3000/api', {target: 'http://localhost:8000'});
140 | });
141 |
142 | it('should use the target in the configuration as target', function() {
143 | expect(result.options.target).to.equal('http://localhost:8000');
144 | });
145 |
146 | it('should not use the host from the shorthand as target', function() {
147 | expect(result.options.target).not.to.equal('http://localhost:3000');
148 | });
149 | });
150 |
151 | });
152 |
153 | });
154 |
155 |
--------------------------------------------------------------------------------
/recipes/servers.md:
--------------------------------------------------------------------------------
1 | # Servers
2 |
3 | Overview of `http-proxy-middleware` implementation in different servers.
4 |
5 | Missing a server? Feel free to extend this list of examples.
6 |
7 |
8 |
9 | - [Browser-Sync](#browser-sync)
10 | - [Express](#express)
11 | - [Connect](#connect)
12 | - [lite-server](#lite-server)
13 | - [grunt-contrib-connect](#grunt-contrib-connect)
14 | - [grunt-browser-sync](#grunt-browser-sync)
15 | - [gulp-connect](#gulp-connect)
16 | - [gulp-webserver](#gulp-webserver)
17 |
18 |
19 |
20 | ## Browser-Sync
21 |
22 | https://github.com/BrowserSync/browser-sync
23 | [](https://github.com/BrowserSync/browser-sync)
24 |
25 | ```javascript
26 | var browserSync = require('browser-sync').create();
27 | var proxy = require('http-proxy-middleware');
28 |
29 | var apiProxy = proxy('/api', {
30 | target: 'http://www.example.org',
31 | changeOrigin: true // for vhosted sites
32 | });
33 |
34 | browserSync.init({
35 | server: {
36 | baseDir: './',
37 | port: 3000,
38 | middleware: [apiProxy],
39 | },
40 | startPath: '/api'
41 | });
42 |
43 | ```
44 |
45 | ## Express
46 |
47 | https://github.com/expressjs/express
48 | [](https://github.com/expressjs/express)
49 |
50 | ```javascript
51 | var express = require('express');
52 | var proxy = require('http-proxy-middleware');
53 |
54 | var apiProxy = proxy('/api', {
55 | target: 'http://www.example.org',
56 | changeOrigin: true // for vhosted sites
57 | });
58 |
59 | var app = express();
60 |
61 | app.use(apiProxy);
62 | app.listen(3000);
63 | ```
64 |
65 | ## Connect
66 |
67 | https://github.com/senchalabs/connect
68 | [](https://github.com/senchalabs/connect)
69 |
70 | ```javascript
71 | var http = require('http');
72 | var connect = require('connect');
73 | var proxy = require('http-proxy-middleware');
74 |
75 | var apiProxy = proxy('/api', {
76 | target: 'http://www.example.org',
77 | changeOrigin: true // for vhosted sites
78 | });
79 |
80 | var app = connect();
81 | app.use(apiProxy);
82 |
83 | http.createServer(app).listen(3000);
84 | ```
85 |
86 | ## lite-server
87 |
88 | https://github.com/johnpapa/lite-server
89 | [](https://github.com/johnpapa/lite-server) ([example source](https://github.com/johnpapa/lite-server/issues/61#issuecomment-205997607))
90 |
91 | File: `bs-config.js`
92 |
93 | ```javascript
94 | var proxy = require('http-proxy-middleware');
95 |
96 | var apiProxy = proxy('/api', {
97 | target: 'http://www.example.org',
98 | changeOrigin: true // for vhosted sites
99 | });
100 |
101 | module.exports = {
102 | server: {
103 | middleware: {
104 | 1: apiProxy
105 | }
106 | }
107 | };
108 | ```
109 |
110 | ## grunt-contrib-connect
111 |
112 | https://github.com/gruntjs/grunt-contrib-connect
113 | [](https://github.com/gruntjs/grunt-contrib-connect)
114 |
115 | As an `Array`:
116 | ```javascript
117 | var proxy = require('http-proxy-middleware');
118 |
119 | var apiProxy = proxy('/api', {
120 | target: 'http://www.example.org',
121 | changeOrigin: true // for vhosted sites
122 | });
123 |
124 | grunt.initConfig({
125 | connect: {
126 | server: {
127 | options: {
128 | middleware: [apiProxy],
129 | },
130 | },
131 | },
132 | });
133 | ```
134 |
135 | As a `function`:
136 | ```javascript
137 | var proxy = require('http-proxy-middleware');
138 |
139 | var apiProxy = proxy('/api', {
140 | target: 'http://www.example.org',
141 | changeOrigin: true // for vhosted sites
142 | });
143 |
144 | grunt.initConfig({
145 | connect: {
146 | server: {
147 | options: {
148 | middleware: function(connect, options, middlewares) {
149 | // inject a custom middleware into the array of default middlewares
150 | middlewares.unshift(apiProxy);
151 |
152 | return middlewares;
153 | },
154 | },
155 | },
156 | },
157 | });
158 | ```
159 |
160 |
161 | ## grunt-browser-sync
162 |
163 | https://github.com/BrowserSync/grunt-browser-sync
164 | [](https://github.com/BrowserSync/grunt-browser-sync)
165 |
166 |
167 | ```javascript
168 | var proxy = require('http-proxy-middleware');
169 |
170 | var apiProxy = proxy('/api', {
171 | target: 'http://www.example.org',
172 | changeOrigin: true // for vhosted sites
173 | });
174 |
175 | grunt.initConfig({
176 |
177 | // BrowserSync Task
178 | browserSync: {
179 | default_options: {
180 | options: {
181 | files: [
182 | "css/*.css",
183 | "*.html"
184 | ],
185 | port: 9000,
186 | server: {
187 | baseDir: ['app'],
188 | middleware: apiProxy
189 | }
190 | }
191 | }
192 | }
193 |
194 | });
195 | ```
196 |
197 | ## gulp-connect
198 |
199 | https://github.com/avevlad/gulp-connect
200 | [](https://github.com/avevlad/gulp-connect)
201 |
202 | ```javascript
203 | var gulp = require('gulp');
204 | var connect = require('gulp-connect');
205 | var proxy = require('http-proxy-middleware');
206 |
207 | gulp.task('connect', function() {
208 | connect.server({
209 | root: ['./app'],
210 | middleware: function(connect, opt) {
211 |
212 | var apiProxy = proxy('/api', {
213 | target: 'http://www.example.org',
214 | changeOrigin: true // for vhosted sites
215 | });
216 |
217 | return [apiProxy];
218 | }
219 |
220 | });
221 | });
222 |
223 | gulp.task('default', ['connect']);
224 | ```
225 |
226 | ## gulp-webserver
227 |
228 | https://github.com/schickling/gulp-webserver
229 | [](https://github.com/schickling/gulp-webserver)
230 |
231 | ```javascript
232 | var gulp = require('gulp');
233 | var webserver = require('gulp-webserver');
234 | var proxy = require('http-proxy-middleware');
235 |
236 | gulp.task('webserver', function() {
237 | var apiProxy = proxy('/api', {
238 | target: 'http://www.example.org',
239 | changeOrigin: true // for vhosted sites
240 | });
241 |
242 | gulp.src('app')
243 | .pipe(webserver({
244 | livereload: true,
245 | directoryListing: true,
246 | open: true,
247 | middleware: [apiProxy]
248 | }));
249 | });
250 |
251 | gulp.task('default', ['webserver']);
252 | ```
253 |
--------------------------------------------------------------------------------
/test/unit/logger.spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var Logger = require('./_libs').Logger;
3 | var getArrow = Logger.getArrow;
4 |
5 | describe('Logger', function() {
6 | var logger;
7 | var logMessage, debugMessage, infoMessage, warnMessage, errorMessage;
8 |
9 | beforeEach(function() {
10 | logMessage = undefined;
11 | debugMessage = undefined;
12 | infoMessage = undefined;
13 | warnMessage = undefined;
14 | errorMessage = undefined;
15 | });
16 |
17 | beforeEach(function() {
18 | logger = Logger.getInstance();
19 | });
20 |
21 | beforeEach(function() {
22 | logger.setProvider(function(provider) {
23 | provider.log = function(message) {logMessage = message;};
24 | provider.debug = function(message) {debugMessage = message;};
25 | provider.info = function(message) {infoMessage = message;};
26 | provider.warn = function(message) {warnMessage = message;};
27 | provider.error = function(message) {errorMessage = message;};
28 |
29 | return provider;
30 | });
31 | });
32 |
33 | describe('logging with different levels', function() {
34 | beforeEach(function() {
35 | logger.log('log');
36 | logger.debug('debug');
37 | logger.info('info');
38 | logger.warn('warn');
39 | logger.error('error');
40 | });
41 |
42 | describe('level: debug', function() {
43 | beforeEach(function() {
44 | logger.setLevel('debug');
45 | });
46 |
47 | it('should log .log() messages', function() {
48 | expect(logMessage).to.equal('log');
49 | });
50 | it('should log .debug() messages', function() {
51 | expect(debugMessage).to.equal('debug');
52 | });
53 | it('should log .info() messages', function() {
54 | expect(infoMessage).to.equal('info');
55 | });
56 | it('should log .warn() messages', function() {
57 | expect(warnMessage).to.equal('warn');
58 | });
59 | it('should log .error() messages', function() {
60 | expect(errorMessage).to.equal('error');
61 | });
62 | });
63 |
64 | describe('level: info', function() {
65 | beforeEach(function() {
66 | logger.setLevel('info');
67 | });
68 |
69 | it('should log .log() messages', function() {
70 | expect(logMessage).to.equal('log');
71 | });
72 | it('should not log .debug() messages', function() {
73 | expect(debugMessage).to.equal(undefined);
74 | });
75 | it('should log .info() messages', function() {
76 | expect(infoMessage).to.equal('info');
77 | });
78 | it('should log .warn() messages', function() {
79 | expect(warnMessage).to.equal('warn');
80 | });
81 | it('should log .error() messages', function() {
82 | expect(errorMessage).to.equal('error');
83 | });
84 | });
85 |
86 | describe('level: warn', function() {
87 | beforeEach(function() {
88 | logger.setLevel('warn');
89 | });
90 |
91 | it('should log .log() messages', function() {
92 | expect(logMessage).to.equal('log');
93 | });
94 | it('should not log .debug() messages', function() {
95 | expect(debugMessage).to.equal(undefined);
96 | });
97 | it('should not log .info() messages', function() {
98 | expect(infoMessage).to.equal(undefined);
99 | });
100 | it('should log .warn() messages', function() {
101 | expect(warnMessage).to.equal('warn');
102 | });
103 | it('should log .error() messages', function() {
104 | expect(errorMessage).to.equal('error');
105 | });
106 | });
107 |
108 | describe('level: error', function() {
109 | beforeEach(function() {
110 | logger.setLevel('error');
111 | });
112 |
113 | it('should log .log() messages', function() {
114 | expect(logMessage).to.equal('log');
115 | });
116 | it('should not log .debug() messages', function() {
117 | expect(debugMessage).to.equal(undefined);
118 | });
119 | it('should not log .info() messages', function() {
120 | expect(infoMessage).to.equal(undefined);
121 | });
122 | it('should log .warn() messages', function() {
123 | expect(warnMessage).to.equal(undefined);
124 | });
125 | it('should log .error() messages', function() {
126 | expect(errorMessage).to.equal('error');
127 | });
128 | });
129 |
130 | describe('level: silent', function() {
131 | beforeEach(function() {
132 | logger.setLevel('silent');
133 | });
134 |
135 | it('should log .log() messages', function() {
136 | expect(logMessage).to.equal('log');
137 | });
138 | it('should not log .debug() messages', function() {
139 | expect(debugMessage).to.equal(undefined);
140 | });
141 | it('should not log .info() messages', function() {
142 | expect(infoMessage).to.equal(undefined);
143 | });
144 | it('should not log .warn() messages', function() {
145 | expect(warnMessage).to.equal(undefined);
146 | });
147 | it('should not log .error() messages', function() {
148 | expect(errorMessage).to.equal(undefined);
149 | });
150 | });
151 |
152 | describe('Interpolation', function() {
153 | // make sure all messages are logged
154 | beforeEach(function() {
155 | logger.setLevel('debug');
156 | });
157 |
158 | beforeEach(function() {
159 | logger.log('log %s %s', 123, 456);
160 | logger.debug('debug %s %s', 123, 456);
161 | logger.info('info %s %s', 123, 456);
162 | logger.warn('warn %s %s', 123, 456);
163 | logger.error('error %s %s', 123, 456);
164 | });
165 |
166 | it('should interpolate .log() messages', function() {
167 | expect(logMessage).to.equal('log 123 456');
168 | });
169 | it('should interpolate .debug() messages', function() {
170 | expect(debugMessage).to.equal('debug 123 456');
171 | });
172 | it('should interpolate .info() messages', function() {
173 | expect(infoMessage).to.equal('info 123 456');
174 | });
175 | it('should interpolate .warn() messages', function() {
176 | expect(warnMessage).to.equal('warn 123 456');
177 | });
178 | it('should interpolate .error() messages', function() {
179 | expect(errorMessage).to.equal('error 123 456');
180 | });
181 | });
182 | });
183 |
184 | describe('Erroneous usage.', function() {
185 | var fn;
186 |
187 | describe('Log provider is not a function', function() {
188 | beforeEach(function() {
189 | fn = function() {
190 | logger.setProvider({});
191 | };
192 | });
193 |
194 | it('should throw an error', function() {
195 | expect(fn).to.throw(Error);
196 | });
197 | });
198 |
199 | describe('Invalid logLevel', function() {
200 | beforeEach(function() {
201 | fn = function() {
202 | logger.setLevel('foo');
203 | };
204 | });
205 |
206 | it('should throw an error', function() {
207 | expect(fn).to.throw(Error);
208 | });
209 | });
210 |
211 | });
212 |
213 | });
214 |
215 | describe('getArrow', function() {
216 | var arrow;
217 | // scenario = [originalPath, newPath, originalTarget, newTarget]
218 |
219 | describe('default arrow', function() {
220 | beforeEach(function() {
221 | arrow = getArrow('/api', '/api', 'localhost:1337', 'localhost:1337');
222 | });
223 |
224 | it('should return arrow: "->"', function() {
225 | expect(arrow).to.equal('->');
226 | });
227 | });
228 |
229 | describe('"pathRewrite" arrow', function() {
230 | beforeEach(function() {
231 | arrow = getArrow('/api', '/rest', 'localhost:1337', 'localhost:1337');
232 | });
233 |
234 | it('should return arrow: "~>"', function() {
235 | expect(arrow).to.equal('~>');
236 | });
237 | });
238 |
239 | describe('"router" arrow', function() {
240 | beforeEach(function() {
241 | arrow = getArrow('/api', '/api', 'localhost:1337', 'localhost:8888');
242 | });
243 |
244 | it('should return arrow: "=>"', function() {
245 | expect(arrow).to.equal('=>');
246 | });
247 | });
248 |
249 | describe('"pathRewrite" + "router" arrow', function() {
250 | beforeEach(function() {
251 | arrow = getArrow('/api', '/rest', 'localhost:1337', 'localhost:8888');
252 | });
253 |
254 | it('should return arrow: "≈>"', function() {
255 | expect(arrow).to.equal('≈>');
256 | });
257 | });
258 |
259 | });
260 |
--------------------------------------------------------------------------------
/test/unit/context-matcher.spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var contextMatcher = require('./_libs').contextMatcher;
3 |
4 | describe('Context Matching', function() {
5 |
6 | describe('String path matching', function() {
7 | var result;
8 |
9 | describe('Single path matching', function() {
10 | it('should match all paths', function() {
11 | result = contextMatcher.match('', 'http://localhost/api/foo/bar');
12 | expect(result).to.be.true;
13 | });
14 |
15 | it('should match all paths starting with forward-slash', function() {
16 | result = contextMatcher.match('/', 'http://localhost/api/foo/bar');
17 | expect(result).to.be.true;
18 | });
19 |
20 | it('should return true when the context is present in url', function() {
21 | result = contextMatcher.match('/api', 'http://localhost/api/foo/bar');
22 | expect(result).to.be.true;
23 | });
24 |
25 | it('should return false when the context is not present in url', function() {
26 | result = contextMatcher.match('/abc', 'http://localhost/api/foo/bar');
27 | expect(result).to.be.false;
28 | });
29 |
30 | it('should return false when the context is present half way in url', function() {
31 | result = contextMatcher.match('/foo', 'http://localhost/api/foo/bar');
32 | expect(result).to.be.false;
33 | });
34 |
35 | it('should return false when the context does not start with /', function() {
36 | result = contextMatcher.match('api', 'http://localhost/api/foo/bar');
37 | expect(result).to.be.false;
38 | });
39 | });
40 |
41 | describe('Multi path matching', function() {
42 | it('should return true when the context is present in url', function() {
43 | result = contextMatcher.match(['/api'], 'http://localhost/api/foo/bar');
44 | expect(result).to.be.true;
45 | });
46 |
47 | it('should return true when the context is present in url', function() {
48 | result = contextMatcher.match(['/api', '/ajax'], 'http://localhost/ajax/foo/bar');
49 | expect(result).to.be.true;
50 | });
51 |
52 | it('should return false when the context does not match url', function() {
53 | result = contextMatcher.match(['/api', '/ajax'], 'http://localhost/foo/bar');
54 | expect(result).to.be.false;
55 | });
56 |
57 | it('should return false when empty array provided', function() {
58 | result = contextMatcher.match([], 'http://localhost/api/foo/bar');
59 | expect(result).to.be.false;
60 | });
61 | });
62 | });
63 |
64 | describe('Wildcard path matching', function() {
65 | describe('Single glob', function() {
66 | var url;
67 |
68 | beforeEach(function() {
69 | url = 'http://localhost/api/foo/bar.html';
70 | });
71 |
72 | describe('url-path matching', function() {
73 | it('should match any path', function() {
74 | expect(contextMatcher.match('**', url)).to.be.true;
75 | expect(contextMatcher.match('/**', url)).to.be.true;
76 | });
77 |
78 | it('should only match paths starting with "/api" ', function() {
79 | expect(contextMatcher.match('/api/**', url)).to.be.true;
80 | expect(contextMatcher.match('/ajax/**', url)).to.be.false;
81 | });
82 |
83 | it('should only match paths starting with "foo" folder in it ', function() {
84 | expect(contextMatcher.match('**/foo/**', url)).to.be.true;
85 | expect(contextMatcher.match('**/invalid/**', url)).to.be.false;
86 | });
87 | });
88 |
89 | describe('file matching', function() {
90 | it('should match any path, file and extension', function() {
91 | expect(contextMatcher.match('**', url)).to.be.true;
92 | expect(contextMatcher.match('**/*', url)).to.be.true;
93 | expect(contextMatcher.match('**/*.*', url)).to.be.true;
94 | expect(contextMatcher.match('/**', url)).to.be.true;
95 | expect(contextMatcher.match('/**.*', url)).to.be.true;
96 | expect(contextMatcher.match('/**/*', url)).to.be.true;
97 | expect(contextMatcher.match('/**/*.*', url)).to.be.true;
98 | });
99 |
100 | it('should only match .html files', function() {
101 | expect(contextMatcher.match('**/*.html', url)).to.be.true;
102 | expect(contextMatcher.match('/**.html', url)).to.be.true;
103 | expect(contextMatcher.match('/**/*.html', url)).to.be.true;
104 | expect(contextMatcher.match('/**.htm', url)).to.be.false;
105 | expect(contextMatcher.match('/**.jpg', url)).to.be.false;
106 | });
107 |
108 | it('should only match .html under root path', function() {
109 | var pattern = '/*.html';
110 | expect(contextMatcher.match(pattern, 'http://localhost/index.html')).to.be.true;
111 | expect(contextMatcher.match(pattern, 'http://localhost/some/path/index.html')).to.be.false;
112 | });
113 |
114 | it('should ignore query params', function() {
115 | expect(contextMatcher.match('/**/*.php', 'http://localhost/a/b/c.php?d=e&e=f')).to.be.true;
116 | expect(contextMatcher.match('/**/*.php?*', 'http://localhost/a/b/c.php?d=e&e=f')).to.be.false;
117 | });
118 |
119 | it('should only match any file in root path', function() {
120 | expect(contextMatcher.match('/*', 'http://localhost/bar.html')).to.be.true;
121 | expect(contextMatcher.match('/*.*', 'http://localhost/bar.html')).to.be.true;
122 | expect(contextMatcher.match('/*', 'http://localhost/foo/bar.html')).to.be.false;
123 | });
124 |
125 | it('should only match .html file is in root path', function() {
126 | expect(contextMatcher.match('/*.html', 'http://localhost/bar.html')).to.be.true;
127 | expect(contextMatcher.match('/*.html', 'http://localhost/api/foo/bar.html')).to.be.false;
128 | });
129 |
130 | it('should only match .html files in "foo" folder', function() {
131 | expect(contextMatcher.match('**/foo/*.html', url)).to.be.true;
132 | expect(contextMatcher.match('**/bar/*.html', url)).to.be.false;
133 | });
134 |
135 | it('should not match .html files', function() {
136 | expect(contextMatcher.match('!**/*.html', url)).to.be.false;
137 | });
138 | });
139 | });
140 |
141 | describe('Multi glob matching', function() {
142 |
143 | describe('Multiple patterns', function() {
144 | it('should return true when both path patterns match', function() {
145 | var pattern = ['/api/**','/ajax/**'];
146 | expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.json')).to.be.true;
147 | expect(contextMatcher.match(pattern, 'http://localhost/ajax/foo/bar.json')).to.be.true;
148 | expect(contextMatcher.match(pattern, 'http://localhost/rest/foo/bar.json')).to.be.false;
149 | });
150 | it('should return true when both file extensions pattern match', function() {
151 | var pattern = ['/**.html','/**.jpeg'];
152 | expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.html')).to.be.true;
153 | expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.jpeg')).to.be.true;
154 | expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.gif')).to.be.false;
155 | });
156 | });
157 |
158 | describe('Negation patterns', function() {
159 | it('should not match file extension', function() {
160 | var url = 'http://localhost/api/foo/bar.html';
161 | expect(contextMatcher.match(['**', '!**/*.html'], url)).to.be.false;
162 | expect(contextMatcher.match(['**', '!**/*.json'], url)).to.be.true;
163 | });
164 | });
165 | });
166 | });
167 |
168 | describe('Use function for matching', function() {
169 | testFunctionAsContext = function(val) {
170 | return contextMatcher.match(fn, 'http://localhost/api/foo/bar');
171 |
172 | function fn(path, req) {
173 | return val;
174 | };
175 | };
176 |
177 | describe('truthy', function() {
178 | it('should match when function returns true', function() {
179 | expect(testFunctionAsContext(true)).to.be.ok;
180 | expect(testFunctionAsContext('true')).to.be.ok;
181 | });
182 | });
183 |
184 | describe('falsy', function() {
185 | it('should not match when function returns falsy value', function() {
186 | expect(testFunctionAsContext()).to.not.be.ok;
187 | expect(testFunctionAsContext(undefined)).to.not.be.ok;
188 | expect(testFunctionAsContext(false)).to.not.be.ok;
189 | expect(testFunctionAsContext('')).to.not.be.ok;
190 | });
191 | });
192 |
193 | });
194 |
195 | describe('Test invalid contexts', function() {
196 | var testContext;
197 |
198 | beforeEach(function() {
199 | testContext = function(context) {
200 | return function() {
201 | contextMatcher.match(context, 'http://localhost/api/foo/bar');
202 | };
203 | };
204 | });
205 |
206 | describe('Throw error', function() {
207 | it('should throw error with undefined', function() {
208 | expect(testContext(undefined)).to.throw(Error);
209 | });
210 |
211 | it('should throw error with null', function() {
212 | expect(testContext(null)).to.throw(Error);
213 | });
214 |
215 | it('should throw error with object literal', function() {
216 | expect(testContext({})).to.throw(Error);
217 | });
218 |
219 | it('should throw error with integers', function() {
220 | expect(testContext(123)).to.throw(Error);
221 | });
222 |
223 | it('should throw error with mixed string and glob pattern', function() {
224 | expect(testContext(['/api', '!*.html'])).to.throw(Error);
225 | });
226 | });
227 |
228 | describe('Do not throw error', function() {
229 | it('should not throw error with string', function() {
230 | expect(testContext('/123')).not.to.throw(Error);
231 | });
232 |
233 | it('should not throw error with Array', function() {
234 | expect(testContext(['/123'])).not.to.throw(Error);
235 | });
236 | it('should not throw error with glob', function() {
237 | expect(testContext('/**')).not.to.throw(Error);
238 | });
239 |
240 | it('should not throw error with Array of globs', function() {
241 | expect(testContext(['/**', '!*.html'])).not.to.throw(Error);
242 | });
243 |
244 | it('should not throw error with Function', function() {
245 | expect(testContext(function() {})).not.to.throw(Error);
246 | });
247 | });
248 |
249 | });
250 | });
251 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # http-proxy-middleware
2 |
3 | [](https://travis-ci.org/chimurai/http-proxy-middleware)
4 | [](https://coveralls.io/r/chimurai/http-proxy-middleware)
5 | [](https://david-dm.org/chimurai/http-proxy-middleware#info=dependencies)
6 | [](https://snyk.io/test/npm/http-proxy-middleware)
7 |
8 | Node.js proxying made simple. Configure proxy middleware with ease for [connect](https://github.com/senchalabs/connect), [express](https://github.com/strongloop/express), [browser-sync](https://github.com/BrowserSync/browser-sync) and [many more](#compatible-servers).
9 |
10 | Powered by the popular Nodejitsu [`http-proxy`](https://github.com/nodejitsu/node-http-proxy). [](https://github.com/nodejitsu/node-http-proxy)
11 |
12 | ## TL;DR
13 |
14 | Proxy `/api` requests to `http://www.example.org`
15 |
16 | ```javascript
17 | var express = require('express');
18 | var proxy = require('http-proxy-middleware');
19 |
20 | var app = express();
21 |
22 | app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
23 | app.listen(3000);
24 |
25 | // http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
26 | ```
27 |
28 | _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#options) can be used, along with some extra `http-proxy-middleware` [options](#options).
29 |
30 | :bulb: **Tip:** Set the option `changeOrigin` to `true` for [name-based virtual hosted sites](http://en.wikipedia.org/wiki/Virtual_hosting#Name-based).
31 |
32 | ## Table of Contents
33 |
34 |
35 |
36 | - [Install](#install)
37 | - [Core concept](#core-concept)
38 | - [Example](#example)
39 | - [Context matching](#context-matching)
40 | - [Options](#options)
41 | - [http-proxy-middleware options](#http-proxy-middleware-options)
42 | - [http-proxy events](#http-proxy-events)
43 | - [http-proxy options](#http-proxy-options)
44 | - [Shorthand](#shorthand)
45 | - [app.use\(path, proxy\)](#appusepath-proxy)
46 | - [WebSocket](#websocket)
47 | - [External WebSocket upgrade](#external-websocket-upgrade)
48 | - [Working examples](#working-examples)
49 | - [Recipes](#recipes)
50 | - [Compatible servers](#compatible-servers)
51 | - [Tests](#tests)
52 | - [Changelog](#changelog)
53 | - [License](#license)
54 |
55 |
56 |
57 |
58 | ## Install
59 |
60 | ```javascript
61 | $ npm install --save-dev http-proxy-middleware
62 | ```
63 |
64 | ## Core concept
65 |
66 | Proxy middleware configuration.
67 |
68 | #### proxy([context,] config)
69 |
70 | ```javascript
71 | var proxy = require('http-proxy-middleware');
72 |
73 | var apiProxy = proxy('/api', {target: 'http://www.example.org'});
74 | // \____/ \_____________________________/
75 | // | |
76 | // context options
77 |
78 | // 'apiProxy' is now ready to be used as middleware in a server.
79 | ```
80 | * **context**: Determine which requests should be proxied to the target host.
81 | (more on [context matching](#context-matching))
82 | * **options.target**: target host to proxy to. _(protocol + host)_
83 |
84 | (full list of [`http-proxy-middleware` configuration options](#options))
85 |
86 | #### proxy(uri [, config])
87 |
88 | ``` javascript
89 | // shorthand syntax for the example above:
90 | var apiProxy = proxy('http://www.example.org/api');
91 |
92 | ```
93 | More about the [shorthand configuration](#shorthand).
94 |
95 | ## Example
96 |
97 | An example with `express` server.
98 |
99 | ```javascript
100 | // include dependencies
101 | var express = require('express');
102 | var proxy = require('http-proxy-middleware');
103 |
104 | // proxy middleware options
105 | var options = {
106 | target: 'http://www.example.org', // target host
107 | changeOrigin: true, // needed for virtual hosted sites
108 | ws: true, // proxy websockets
109 | pathRewrite: {
110 | '^/api/old-path' : '/api/new-path', // rewrite path
111 | '^/api/remove/path' : '/path' // remove base path
112 | },
113 | router: {
114 | // when request.headers.host == 'dev.localhost:3000',
115 | // override target 'http://www.example.org' to 'http://localhost:8000'
116 | 'dev.localhost:3000' : 'http://localhost:8000'
117 | }
118 | };
119 |
120 | // create the proxy (without context)
121 | var exampleProxy = proxy(options);
122 |
123 | // mount `exampleProxy` in web server
124 | var app = express();
125 | app.use('/api', exampleProxy);
126 | app.listen(3000);
127 | ```
128 |
129 | ## Context matching
130 |
131 | 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.
132 |
133 | The [RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is be used for context matching.
134 |
135 | ```
136 | foo://example.com:8042/over/there?name=ferret#nose
137 | \_/ \______________/\_________/ \_________/ \__/
138 | | | | | |
139 | scheme authority path query fragment
140 | ```
141 |
142 | * **path matching**
143 | - `proxy({...})` - matches any path, all requests will be proxied.
144 | - `proxy('/', {...})` - matches any path, all requests will be proxied.
145 | - `proxy('/api', {...})` - matches paths starting with `/api`
146 |
147 | * **multiple path matching**
148 | - `proxy(['/api', '/ajax', '/someotherpath'], {...})`
149 |
150 | * **wildcard path matching**
151 |
152 | 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.
153 | - `proxy('**', {...})` matches any path, all requests will be proxied.
154 | - `proxy('**/*.html', {...})` matches any path which ends with `.html`
155 | - `proxy('/*.html', {...})` matches paths directly under path-absolute
156 | - `proxy('/api/**/*.html', {...})` matches requests ending with `.html` in the path of `/api`
157 | - `proxy(['/api/**', '/ajax/**'], {...})` combine multiple patterns
158 | - `proxy(['/api/**', '!**/bad.json'], {...})` exclusion
159 |
160 | * **custom matching**
161 |
162 | For full control you can provide a custom function to determine which requests should be proxied or not.
163 | ```javascript
164 | /**
165 | * @return {Boolean}
166 | */
167 | var filter = function (pathname, req) {
168 | return (pathname.match('^/api') && req.method === 'GET');
169 | };
170 |
171 | var apiProxy = proxy(filter, {target: 'http://www.example.org'})
172 | ```
173 |
174 | ## Options
175 |
176 | ### http-proxy-middleware options
177 |
178 | * **option.pathRewrite**: object/function, rewrite target's url path. Object-keys will be used as _RegExp_ to match paths.
179 | ```javascript
180 | // rewrite path
181 | pathRewrite: {'^/old/api' : '/new/api'}
182 |
183 | // remove path
184 | pathRewrite: {'^/remove/api' : ''}
185 |
186 | // add base path
187 | pathRewrite: {'^/' : '/basepath/'}
188 |
189 | // custom rewriting
190 | pathRewrite: function (path, req) { return path.replace('/api', '/base/api') }
191 | ```
192 |
193 | * **option.router**: object/function, re-target `option.target` for specific requests.
194 | ```javascript
195 | // Use `host` and/or `path` to match requests. First match will be used.
196 | // The order of the configuration matters.
197 | router: {
198 | 'integration.localhost:3000' : 'http://localhost:8001', // host only
199 | 'staging.localhost:3000' : 'http://localhost:8002', // host only
200 | 'localhost:3000/api' : 'http://localhost:8003', // host + path
201 | '/rest' : 'http://localhost:8004' // path only
202 | }
203 |
204 | // Custom router function
205 | router: function(req) {
206 | return 'http://localhost:8004';
207 | }
208 | ```
209 |
210 | * **option.logLevel**: string, ['debug', 'info', 'warn', 'error', 'silent']. Default: `'info'`
211 |
212 | * **option.logProvider**: function, modify or replace log provider. Default: `console`.
213 | ```javascript
214 | // simple replace
215 | function logProvider(provider) {
216 | // replace the default console log provider.
217 | return require('winston');
218 | }
219 | ```
220 |
221 | ```javascript
222 | // verbose replacement
223 | function logProvider(provider) {
224 | var logger = new (require('winston').Logger)();
225 |
226 | var myCustomProvider = {
227 | log: logger.log,
228 | debug: logger.debug,
229 | info: logger.info,
230 | warn: logger.warn,
231 | error: logger.error
232 | }
233 | return myCustomProvider;
234 | }
235 | ```
236 | * (DEPRECATED) **option.proxyHost**: Use `option.changeOrigin = true` instead.
237 | * (DEPRECATED) **option.proxyTable**: Use `option.router` instead.
238 |
239 |
240 | ### http-proxy events
241 |
242 | Subscribe to [http-proxy events](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events):
243 |
244 | * **option.onError**: function, subscribe to http-proxy's `error` event for custom error handling.
245 | ```javascript
246 | function onError(err, req, res) {
247 | res.writeHead(500, {
248 | 'Content-Type': 'text/plain'
249 | });
250 | res.end('Something went wrong. And we are reporting a custom error message.');
251 | }
252 | ```
253 |
254 | * **option.onProxyRes**: function, subscribe to http-proxy's `proxyRes` event.
255 | ```javascript
256 | function onProxyRes(proxyRes, req, res) {
257 | proxyRes.headers['x-added'] = 'foobar'; // add new header to response
258 | delete proxyRes.headers['x-removed']; // remove header from response
259 | }
260 | ```
261 |
262 | * **option.onProxyReq**: function, subscribe to http-proxy's `proxyReq` event.
263 | ```javascript
264 | function onProxyReq(proxyReq, req, res) {
265 | // add custom header to request
266 | proxyReq.setHeader('x-added', 'foobar');
267 | // or log the req
268 | }
269 | ```
270 |
271 | * **option.onProxyReqWs**: function, subscribe to http-proxy's `proxyReqWs` event.
272 | ```javascript
273 | function onProxyReqWs(proxyReq, req, socket, options, head) {
274 | // add custom header
275 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');
276 | }
277 | ```
278 |
279 | * **option.onOpen**: function, subscribe to http-proxy's `open` event.
280 | ```javascript
281 | function onOpen(proxySocket) {
282 | // listen for messages coming FROM the target here
283 | proxySocket.on('data', hybiParseAndLogMessage);
284 | }
285 | ```
286 |
287 | * **option.onClose**: function, subscribe to http-proxy's `close` event.
288 | ```javascript
289 | function onClose(res, socket, head) {
290 | // view disconnected websocket connections
291 | console.log('Client disconnected');
292 | }
293 | ```
294 |
295 | ### http-proxy options
296 |
297 | The following options are provided by the underlying [http-proxy](https://github.com/nodejitsu/node-http-proxy#options) library.
298 |
299 | * **option.target**: url string to be parsed with the url module
300 | * **option.forward**: url string to be parsed with the url module
301 | * **option.agent**: object to be passed to http(s).request (see Node's [https agent](http://nodejs.org/api/https.html#https_class_https_agent) and [http agent](http://nodejs.org/api/http.html#http_class_http_agent) objects)
302 | * **option.ssl**: object to be passed to https.createServer()
303 | * **option.ws**: true/false: if you want to proxy websockets
304 | * **option.xfwd**: true/false, adds x-forward headers
305 | * **option.secure**: true/false, if you want to verify the SSL Certs
306 | * **option.toProxy**: true/false, passes the absolute URL as the `path` (useful for proxying to proxies)
307 | * **option.prependPath**: true/false, Default: true - specify whether you want to prepend the target's path to the proxy path
308 | * **option.ignorePath**: true/false, Default: false - specify whether you want to ignore the proxy path of the incoming request (note: you will have to append / manually if required).
309 | * **option.localAddress** : Local interface string to bind for outgoing connections
310 | * **option.changeOrigin**: true/false, Default: false - changes the origin of the host header to the target URL
311 | * **option.auth** : Basic authentication i.e. 'user:password' to compute an Authorization header.
312 | * **option.hostRewrite**: rewrites the location hostname on (301/302/307/308) redirects.
313 | * **option.autoRewrite**: rewrites the location host/port on (301/302/307/308) redirects based on requested host/port. Default: false.
314 | * **option.protocolRewrite**: rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null.
315 | * **option.cookieDomainRewrite**: rewrites domain of `set-cookie` headers. Possible values:
316 | * `false` (default): disable cookie rewriting
317 | * String: new domain, for example `cookieDomainRewrite: "new.domain"`. To remove the domain, use `cookieDomainRewrite: ""`.
318 | * Object: mapping of domains to new domains, use `"*"` to match all domains.
319 | For example keep one domain unchanged, rewrite one domain and remove other domains:
320 | ```
321 | cookieDomainRewrite: {
322 | "unchanged.domain": "unchanged.domain",
323 | "old.domain": "new.domain",
324 | "*": ""
325 | }
326 | ```
327 | * **option.headers**: object, adds [request headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields). (Example: `{host:'www.example.org'}`)
328 | * **option.proxyTimeout**: timeout (in millis) when proxy receives no response from target
329 |
330 |
331 |
332 | ## Shorthand
333 |
334 | Use the shorthand syntax when verbose configuration is not needed. The `context` and `option.target` will be automatically configured when shorthand is used. Options can still be used if needed.
335 |
336 | ```javascript
337 | proxy('http://www.example.org:8000/api');
338 | // proxy('/api', {target: 'http://www.example.org:8000'});
339 |
340 |
341 | proxy('http://www.example.org:8000/api/books/*/**.json');
342 | // proxy('/api/books/*/**.json', {target: 'http://www.example.org:8000'});
343 |
344 |
345 | proxy('http://www.example.org:8000/api', {changeOrigin:true});
346 | // proxy('/api', {target: 'http://www.example.org:8000', changeOrigin: true});
347 | ```
348 |
349 | ### app.use(path, proxy)
350 |
351 | If you want to use the server's `app.use` `path` parameter to match requests;
352 | Create and mount the proxy without the http-proxy-middleware `context` parameter:
353 | ```javascript
354 | app.use('/api', proxy({target:'http://www.example.org', changeOrigin:true}));
355 | ```
356 |
357 | `app.use` documentation:
358 | * express: http://expressjs.com/en/4x/api.html#app.use
359 | * connect: https://github.com/senchalabs/connect#mount-middleware
360 |
361 | ## WebSocket
362 |
363 | ```javascript
364 | // verbose api
365 | proxy('/', {target:'http://echo.websocket.org', ws:true});
366 |
367 | // shorthand
368 | proxy('http://echo.websocket.org', {ws:true});
369 |
370 | // shorter shorthand
371 | proxy('ws://echo.websocket.org');
372 | ```
373 |
374 | ### External WebSocket upgrade
375 |
376 | 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.
377 | ```javascript
378 | var wsProxy = proxy('ws://echo.websocket.org', {changeOrigin:true});
379 |
380 | var app = express();
381 | app.use(wsProxy);
382 |
383 | var server = app.listen(3000);
384 | server.on('upgrade', wsProxy.upgrade); // <-- subscribe to http 'upgrade'
385 | ```
386 |
387 |
388 | ## Working examples
389 |
390 | View and play around with [working examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples).
391 |
392 | * Browser-Sync ([example source](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/browser-sync/index.js))
393 | * express ([example source](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/express/index.js))
394 | * connect ([example source](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/connect/index.js))
395 | * WebSocket ([example source](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/websocket/index.js))
396 |
397 | ## Recipes
398 |
399 | View the [recipes](https://github.com/chimurai/http-proxy-middleware/tree/master/recipes) for common use cases.
400 |
401 | ## Compatible servers
402 |
403 | `http-proxy-middleware` is compatible with the following servers:
404 | * [connect](https://www.npmjs.com/package/connect)
405 | * [express](https://www.npmjs.com/package/express)
406 | * [browser-sync](https://www.npmjs.com/package/browser-sync)
407 | * [lite-server](https://www.npmjs.com/package/lite-server)
408 | * [grunt-contrib-connect](https://www.npmjs.com/package/grunt-contrib-connect)
409 | * [grunt-browser-sync](https://www.npmjs.com/package/grunt-browser-sync)
410 | * [gulp-connect](https://www.npmjs.com/package/gulp-connect)
411 | * [gulp-webserver](https://www.npmjs.com/package/gulp-webserver)
412 |
413 | Sample implementations can be found in the [server recipes](https://github.com/chimurai/http-proxy-middleware/tree/master/recipes/servers.md).
414 |
415 | ## Tests
416 |
417 | Run the test suite:
418 |
419 | ```bash
420 | # install dependencies
421 | $ npm install
422 | ```
423 |
424 | unit testing
425 |
426 | ```bash
427 | # unit tests
428 | $ npm test
429 | ```
430 |
431 | coverage
432 |
433 | ```bash
434 | # code coverage
435 | $ npm run cover
436 | ```
437 |
438 | ## Changelog
439 |
440 | - [View changelog](https://github.com/chimurai/http-proxy-middleware/blob/master/CHANGELOG.md)
441 |
442 |
443 | ## License
444 |
445 | The MIT License (MIT)
446 |
447 | Copyright (c) 2015-2016 Steven Chim
448 |
--------------------------------------------------------------------------------
/test/e2e/http-proxy-middleware.spec.js:
--------------------------------------------------------------------------------
1 | var utils = require('./_utils');
2 | var expect = require('chai').expect;
3 | var http = require('http');
4 |
5 | describe('E2E http-proxy-middleware', function() {
6 | var createServer;
7 | var proxyMiddleware;
8 |
9 | beforeEach(function() {
10 | createServer = utils.createServer;
11 | proxyMiddleware = utils.proxyMiddleware;
12 | });
13 |
14 | describe('http-proxy-middleware creation', function() {
15 | it('should create a middleware', function() {
16 | var middleware;
17 | middleware = proxyMiddleware('/api', {target: 'http://localhost:8000'});
18 | expect(middleware).to.be.a('function');
19 | });
20 | });
21 |
22 | describe('context matching', function() {
23 | describe('do not proxy', function() {
24 | var isSkipped;
25 |
26 | beforeEach(function() {
27 | isSkipped = false;
28 |
29 | var middleware;
30 |
31 | var mockReq = {url: '/foo/bar', originalUrl: '/foo/bar'};
32 | var mockRes = {};
33 | var mockNext = function() {
34 | // mockNext will be called when request is not proxied
35 | isSkipped = true;
36 | };
37 |
38 | middleware = proxyMiddleware('/api', {target: 'http://localhost:8000'});
39 | middleware(mockReq, mockRes, mockNext);
40 | });
41 |
42 | it('should not proxy requests when request url does not match context' , function() {
43 | expect(isSkipped).to.be.true;
44 | });
45 |
46 | });
47 | });
48 |
49 | describe('http-proxy-middleware in actual server', function() {
50 |
51 | describe('basic setup, requests to target', function() {
52 | var proxyServer, targetServer;
53 | var targetHeaders;
54 | var targetUrl;
55 | var responseBody;
56 |
57 | beforeEach(function(done) {
58 | var mw_proxy = proxyMiddleware('/api', {target: 'http://localhost:8000'});
59 |
60 | var mw_target = function(req, res, next) {
61 | targetUrl = req.url; // store target url.
62 | targetHeaders = req.headers; // store target headers.
63 | res.write('HELLO WEB'); // respond with 'HELLO WEB'
64 | res.end();
65 | };
66 |
67 | proxyServer = createServer(3000, mw_proxy);
68 | targetServer = createServer(8000, mw_target);
69 |
70 | http.get('http://localhost:3000/api/b/c/d;p?q=1&r=[2,3]#s"', function(res) {
71 | res.on('data', function(chunk) {
72 | responseBody = chunk.toString();
73 | done();
74 | });
75 | });
76 | });
77 |
78 | afterEach(function() {
79 | proxyServer.close();
80 | targetServer.close();
81 | });
82 |
83 | it('should have the same headers.host value', function() {
84 | expect(targetHeaders.host).to.equal('localhost:3000');
85 | });
86 |
87 | it('should have proxied the uri-path and uri-query, but not the uri-hash', function() {
88 | expect(targetUrl).to.equal('/api/b/c/d;p?q=1&r=[2,3]');
89 | });
90 |
91 | it('should have response body: "HELLO WEB"', function() {
92 | expect(responseBody).to.equal('HELLO WEB');
93 | });
94 | });
95 |
96 | describe('custom context matcher/filter', function() {
97 | var proxyServer, targetServer;
98 | var targetHeaders;
99 | var targetUrl;
100 | var responseBody;
101 |
102 | var filterPath, filterReq;
103 |
104 | beforeEach(function(done) {
105 | var filter = function(path, req) {
106 | filterPath = path;
107 | filterReq = req;
108 | return true;
109 | };
110 |
111 | var mw_proxy = proxyMiddleware(filter, {target: 'http://localhost:8000'});
112 |
113 | var mw_target = function(req, res, next) {
114 | targetUrl = req.url; // store target url.
115 | targetHeaders = req.headers; // store target headers.
116 | res.write('HELLO WEB'); // respond with 'HELLO WEB'
117 | res.end();
118 | };
119 |
120 | proxyServer = createServer(3000, mw_proxy);
121 | targetServer = createServer(8000, mw_target);
122 |
123 | http.get('http://localhost:3000/api/b/c/d', function(res) {
124 | res.on('data', function(chunk) {
125 | responseBody = chunk.toString();
126 | done();
127 | });
128 | });
129 | });
130 |
131 | afterEach(function() {
132 | proxyServer.close();
133 | targetServer.close();
134 | });
135 |
136 | it('should have response body: "HELLO WEB"', function() {
137 | expect(responseBody).to.equal('HELLO WEB');
138 | });
139 |
140 | it('should provide the url path in the first argument', function() {
141 | expect(filterPath).to.equal('/api/b/c/d');
142 | });
143 |
144 | it('should provide the req object in the second argument', function() {
145 | expect(filterReq.method).to.equal('GET');
146 | });
147 | });
148 |
149 | describe('multi path', function() {
150 | var proxyServer, targetServer;
151 | var targetHeaders;
152 | var response, responseBody;
153 |
154 | beforeEach(function() {
155 | var mw_proxy = proxyMiddleware(['/api', '/ajax'], {target: 'http://localhost:8000'});
156 |
157 | var mw_target = function(req, res, next) {
158 | res.write(req.url); // respond with req.url
159 | res.end();
160 | };
161 |
162 | proxyServer = createServer(3000, mw_proxy);
163 | targetServer = createServer(8000, mw_target);
164 | });
165 |
166 | afterEach(function() {
167 | proxyServer.close();
168 | targetServer.close();
169 | });
170 |
171 | describe('request to path A, configured', function() {
172 | beforeEach(function(done) {
173 | http.get('http://localhost:3000/api/some/endpoint', function(res) {
174 | response = res;
175 | res.on('data', function(chunk) {
176 | responseBody = chunk.toString();
177 | done();
178 | });
179 | });
180 | });
181 |
182 | it('should proxy to path A', function() {
183 | expect(response.statusCode).to.equal(200);
184 | expect(responseBody).to.equal('/api/some/endpoint');
185 | });
186 | });
187 |
188 | describe('request to path B, configured', function() {
189 | beforeEach(function(done) {
190 | http.get('http://localhost:3000/ajax/some/library', function(res) {
191 | response = res;
192 | res.on('data', function(chunk) {
193 | responseBody = chunk.toString();
194 | done();
195 | });
196 | });
197 | });
198 |
199 | it('should proxy to path B', function() {
200 | expect(response.statusCode).to.equal(200);
201 | expect(responseBody).to.equal('/ajax/some/library');
202 | });
203 | });
204 |
205 | describe('request to path C, not configured', function() {
206 | beforeEach(function(done) {
207 | http.get('http://localhost:3000/lorum/ipsum', function(res) {
208 | response = res;
209 | res.on('data', function(chunk) {
210 | responseBody = chunk.toString();
211 | done();
212 | });
213 | });
214 | });
215 |
216 | it('should not proxy to this path', function() {
217 | expect(response.statusCode).to.equal(404);
218 | });
219 | });
220 |
221 | });
222 |
223 | describe('wildcard path matching', function() {
224 | var proxyServer, targetServer;
225 | var targetHeaders;
226 | var response, responseBody;
227 |
228 | beforeEach(function() {
229 | var mw_proxy = proxyMiddleware('/api/**', {target: 'http://localhost:8000'});
230 |
231 | var mw_target = function(req, res, next) {
232 | res.write(req.url); // respond with req.url
233 | res.end();
234 | };
235 |
236 | proxyServer = createServer(3000, mw_proxy);
237 | targetServer = createServer(8000, mw_target);
238 | });
239 |
240 | beforeEach(function(done) {
241 | http.get('http://localhost:3000/api/some/endpoint', function(res) {
242 | response = res;
243 | res.on('data', function(chunk) {
244 | responseBody = chunk.toString();
245 | done();
246 | });
247 | });
248 | });
249 |
250 | afterEach(function() {
251 | proxyServer.close();
252 | targetServer.close();
253 | });
254 |
255 | it('should proxy to path', function() {
256 | expect(response.statusCode).to.equal(200);
257 | expect(responseBody).to.equal('/api/some/endpoint');
258 | });
259 | });
260 |
261 | describe('multi glob wildcard path matching', function() {
262 | var proxyServer, targetServer;
263 | var targetHeaders;
264 | var responseA, responseBodyA;
265 | var responseB, responseBodyB;
266 |
267 | beforeEach(function() {
268 | var mw_proxy = proxyMiddleware(['/**.html', '!**.json'], {target: 'http://localhost:8000'});
269 |
270 | var mw_target = function(req, res, next) {
271 | res.write(req.url); // respond with req.url
272 | res.end();
273 | };
274 |
275 | proxyServer = createServer(3000, mw_proxy);
276 | targetServer = createServer(8000, mw_target);
277 | });
278 |
279 | beforeEach(function(done) {
280 | http.get('http://localhost:3000/api/some/endpoint/index.html', function(res) {
281 | responseA = res;
282 | res.on('data', function(chunk) {
283 | responseBodyA = chunk.toString();
284 | done();
285 | });
286 | });
287 | });
288 |
289 | beforeEach(function(done) {
290 | http.get('http://localhost:3000/api/some/endpoint/data.json', function(res) {
291 | responseB = res;
292 | res.on('data', function(chunk) {
293 | responseBodyB = chunk.toString();
294 | done();
295 | });
296 | });
297 | });
298 |
299 | afterEach(function() {
300 | proxyServer.close();
301 | targetServer.close();
302 | });
303 |
304 | it('should proxy to paths ending with *.html', function() {
305 | expect(responseA.statusCode).to.equal(200);
306 | expect(responseBodyA).to.equal('/api/some/endpoint/index.html');
307 | });
308 |
309 | it('should not proxy to paths ending with *.json', function() {
310 | expect(responseB.statusCode).to.equal(404);
311 | });
312 | });
313 |
314 | describe('option.headers - additional request headers', function() {
315 | var proxyServer, targetServer;
316 | var targetHeaders;
317 |
318 | beforeEach(function(done) {
319 | var mw_proxy = proxyMiddleware('/api', {target: 'http://localhost:8000', headers: {host: 'foobar.dev'}});
320 |
321 | var mw_target = function(req, res, next) {
322 | targetHeaders = req.headers;
323 | res.end();
324 | };
325 |
326 | proxyServer = createServer(3000, mw_proxy);
327 | targetServer = createServer(8000, mw_target);
328 |
329 | http.get('http://localhost:3000/api/', function(res) {
330 | done();
331 | });
332 | });
333 |
334 | afterEach(function() {
335 | proxyServer.close();
336 | targetServer.close();
337 | });
338 |
339 | it('should send request header "host" to target server', function() {
340 | expect(targetHeaders.host).to.equal('foobar.dev');
341 | });
342 | });
343 |
344 | describe('legacy option.proxyHost', function() {
345 | var proxyServer, targetServer;
346 | var targetHeaders;
347 |
348 | beforeEach(function(done) {
349 | var mw_proxy = proxyMiddleware('/api', {target: 'http://localhost:8000', proxyHost: 'foobar.dev'});
350 |
351 | var mw_target = function(req, res, next) {
352 | targetHeaders = req.headers;
353 | res.end();
354 | };
355 |
356 | proxyServer = createServer(3000, mw_proxy);
357 | targetServer = createServer(8000, mw_target);
358 |
359 | http.get('http://localhost:3000/api/', function(res) {
360 | done();
361 | });
362 | });
363 |
364 | afterEach(function() {
365 | proxyServer.close();
366 | targetServer.close();
367 | });
368 |
369 | it('should proxy host header to target server', function() {
370 | expect(targetHeaders.host).to.equal('foobar.dev');
371 | });
372 | });
373 |
374 | describe('option.onError - Error handling', function() {
375 | var proxyServer, targetServer;
376 | var response, responseBody;
377 |
378 | describe('default', function() {
379 | beforeEach(function(done) {
380 | var mw_proxy = proxyMiddleware('/api', {target: 'http://localhost:666'}); // unreachable host on port:666
381 | var mw_target = function(req, res, next) {next();};
382 |
383 | proxyServer = createServer(3000, mw_proxy);
384 | targetServer = createServer(8000, mw_target);
385 |
386 | http.get('http://localhost:3000/api/', function(res) {
387 | response = res;
388 | done();
389 | });
390 | });
391 |
392 | afterEach(function() {
393 | proxyServer.close();
394 | targetServer.close();
395 | });
396 |
397 | it('should handle errors when host is not reachable', function() {
398 | expect(response.statusCode).to.equal(504);
399 | });
400 | });
401 |
402 | describe('custom', function() {
403 | beforeEach(function(done) {
404 | var customOnError = function(err, req, res) {
405 | res.writeHead(418); // different error code
406 | res.end('I\'m a teapot'); // no response body
407 | };
408 |
409 | var mw_proxy = proxyMiddleware('/api', {target: 'http://localhost:666', onError: customOnError}); // unreachable host on port:666
410 | var mw_target = function(req, res, next) {next();};
411 |
412 | proxyServer = createServer(3000, mw_proxy);
413 | targetServer = createServer(8000, mw_target);
414 |
415 | http.get('http://localhost:3000/api/', function(res) {
416 | response = res;
417 | res.on('data', function(chunk) {
418 | responseBody = chunk.toString();
419 | done();
420 | });
421 | });
422 | });
423 |
424 | afterEach(function() {
425 | proxyServer.close();
426 | targetServer.close();
427 | });
428 |
429 | it('should respond with custom http status code', function() {
430 | expect(response.statusCode).to.equal(418);
431 | });
432 |
433 | it('should respond with custom status message', function() {
434 | expect(responseBody).to.equal('I\'m a teapot');
435 | });
436 | });
437 | });
438 |
439 | describe('option.onProxyRes', function() {
440 | var proxyServer, targetServer;
441 | var response, responseBody;
442 |
443 | beforeEach(function(done) {
444 | var fnOnProxyRes = function(proxyRes, req, res) {
445 | proxyRes.headers['x-added'] = 'foobar'; // add custom header to response
446 | delete proxyRes.headers['x-removed'];
447 | };
448 |
449 | var mw_proxy = proxyMiddleware('/api', {
450 | target: 'http://localhost:8000',
451 | onProxyRes: fnOnProxyRes
452 | });
453 | var mw_target = function(req, res, next) {
454 | res.setHeader('x-removed', 'remove-header');
455 | res.write(req.url); // respond with req.url
456 | res.end();
457 | };
458 |
459 | proxyServer = createServer(3000, mw_proxy);
460 | targetServer = createServer(8000, mw_target);
461 |
462 | http.get('http://localhost:3000/api/foo/bar', function(res) {
463 | response = res;
464 | res.on('data', function(chunk) {
465 | responseBody = chunk.toString();
466 | done();
467 | });
468 | });
469 | });
470 |
471 | afterEach(function() {
472 | proxyServer.close();
473 | targetServer.close();
474 | });
475 |
476 | it('should add `x-added` as custom header to response"', function() {
477 | expect(response.headers['x-added']).to.equal('foobar');
478 | });
479 |
480 | it('should remove `x-removed` field from response header"', function() {
481 | expect(response.headers['x-removed']).to.equal(undefined);
482 | });
483 | });
484 |
485 | describe('option.onProxyReq', function() {
486 | var proxyServer, targetServer;
487 | var receivedRequest;
488 |
489 | beforeEach(function(done) {
490 | var fnOnProxyReq = function(proxyReq, req, res) {
491 | proxyReq.setHeader('x-added', 'foobar'); // add custom header to request
492 | };
493 |
494 | var mw_proxy = proxyMiddleware('/api', {
495 | target: 'http://localhost:8000',
496 | onProxyReq: fnOnProxyReq
497 | });
498 |
499 | var mw_target = function(req, res, next) {
500 | receivedRequest = req;
501 | res.write(req.url); // respond with req.url
502 | res.end();
503 | };
504 |
505 | proxyServer = createServer(3000, mw_proxy);
506 | targetServer = createServer(8000, mw_target);
507 |
508 | http.get('http://localhost:3000/api/foo/bar', function() {
509 | done();
510 | });
511 | });
512 |
513 | afterEach(function() {
514 | proxyServer.close();
515 | targetServer.close();
516 | });
517 |
518 | it('should add `x-added` as custom header to request"', function() {
519 | expect(receivedRequest.headers['x-added']).to.equal('foobar');
520 | });
521 | });
522 |
523 | describe('option.pathRewrite', function() {
524 | var proxyServer, targetServer;
525 | var responseBody;
526 |
527 | beforeEach(function(done) {
528 | var mw_proxy = proxyMiddleware('/api', {
529 | target: 'http://localhost:8000',
530 | pathRewrite: {
531 | '^/api': '/rest',
532 | '^/remove': ''
533 | }
534 | });
535 | var mw_target = function(req, res, next) {
536 | res.write(req.url); // respond with req.url
537 | res.end();
538 | };
539 |
540 | proxyServer = createServer(3000, mw_proxy);
541 | targetServer = createServer(8000, mw_target);
542 |
543 | http.get('http://localhost:3000/api/foo/bar', function(res) {
544 | res.on('data', function(chunk) {
545 | responseBody = chunk.toString();
546 | done();
547 | });
548 | });
549 | });
550 |
551 | afterEach(function() {
552 | proxyServer.close();
553 | targetServer.close();
554 | });
555 |
556 | it('should have rewritten path from "/api/foo/bar" to "/rest/foo/bar"', function() {
557 | expect(responseBody).to.equal('/rest/foo/bar');
558 | });
559 | });
560 |
561 | describe('shorthand usage', function() {
562 | var proxyServer, targetServer;
563 | var responseBody;
564 |
565 | beforeEach(function(done) {
566 | var mw_proxy = proxyMiddleware('http://localhost:8000/api');
567 | var mw_target = function(req, res, next) {
568 | res.write(req.url); // respond with req.url
569 | res.end();
570 | };
571 |
572 | proxyServer = createServer(3000, mw_proxy);
573 | targetServer = createServer(8000, mw_target);
574 |
575 | http.get('http://localhost:3000/api/foo/bar', function(res) {
576 | res.on('data', function(chunk) {
577 | responseBody = chunk.toString();
578 | done();
579 | });
580 | });
581 | });
582 |
583 | afterEach(function() {
584 | proxyServer.close();
585 | targetServer.close();
586 | });
587 |
588 | it('should have proxy with shorthand configuration', function() {
589 | expect(responseBody).to.equal('/api/foo/bar');
590 | });
591 | });
592 |
593 | describe('express with path + proxy', function() {
594 | var proxyServer, targetServer;
595 | var responseBody;
596 |
597 | beforeEach(function(done) {
598 | var mw_proxy = proxyMiddleware('http://localhost:8000');
599 | var mw_target = function(req, res, next) {
600 | res.write(req.url); // respond with req.url
601 | res.end();
602 | };
603 |
604 | proxyServer = createServer(3000, mw_proxy, '/api');
605 | targetServer = createServer(8000, mw_target);
606 |
607 | http.get('http://localhost:3000/api/foo/bar', function(res) {
608 | res.on('data', function(chunk) {
609 | responseBody = chunk.toString();
610 | done();
611 | });
612 | });
613 | });
614 |
615 | afterEach(function() {
616 | proxyServer.close();
617 | targetServer.close();
618 | });
619 |
620 | it('should proxy to target with the baseUrl', function() {
621 | expect(responseBody).to.equal('/api/foo/bar');
622 | });
623 |
624 | });
625 |
626 | describe('option.logLevel & option.logProvider', function() {
627 | var proxyServer, targetServer;
628 | var responseBody;
629 | var logMessage;
630 |
631 | beforeEach(function(done) {
632 | var customLogger = function(message) {
633 | logMessage = message;
634 | };
635 |
636 | var mw_proxy = proxyMiddleware('http://localhost:8000/api', {
637 | logLevel: 'info',
638 | logProvider: function(provider) {
639 | provider.debug = customLogger;
640 | provider.info = customLogger;
641 | return provider;
642 | }
643 | });
644 | var mw_target = function(req, res, next) {
645 | res.write(req.url); // respond with req.url
646 | res.end();
647 | };
648 |
649 | proxyServer = createServer(3000, mw_proxy);
650 | targetServer = createServer(8000, mw_target);
651 |
652 | http.get('http://localhost:3000/api/foo/bar', function(res) {
653 | res.on('data', function(chunk) {
654 | responseBody = chunk.toString();
655 | done();
656 | });
657 | });
658 | });
659 |
660 | afterEach(function() {
661 | proxyServer.close();
662 | targetServer.close();
663 | });
664 |
665 | it('should have logged messages', function() {
666 | expect(logMessage).not.to.equal(undefined);
667 | });
668 | });
669 |
670 | });
671 | });
672 |
673 |
--------------------------------------------------------------------------------