├── index.js
├── 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
└── lint.js
├── .editorconfig
├── .travis.yml
├── ISSUE_TEMPLATE.md
├── lib
├── errors.js
├── router.js
├── path-rewriter.js
├── handlers.js
├── context-matcher.js
├── config-factory.js
├── logger.js
└── index.js
├── recipes
├── virtual-hosts.md
├── corporate-proxy.md
├── logLevel.md
├── https.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
├── 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 |
--------------------------------------------------------------------------------
/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 = 2
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 | - '8'
5 | - '7'
6 | - '6'
7 | - '5'
8 | - '4'
9 | before_install:
10 | # https://github.com/npm/npm/issues/11283
11 | - npm set progress=false
12 | after_success: npm run coveralls
13 | env:
14 | global:
15 | # COVERALLS_REPO_TOKEN=
16 | secure: "I8mDH0n2DQHAPvUFEV/ZNmrMNYTJxgywg8+P3yuCAWwQkZeXQi0DWG+6VUpOylaRhL/4kCdZK9gnJD2MfrqvNqVLDUqBK3tTmZVoyqqJEdE0UdEHcPncAPmxWrG98sDjqFN9r4nWeizHvyA1fNRQHRN56Zy+DcQWjgEhHJaYeNA="
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/errors.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 |
3 | module.exports = {
4 | ERR_CONFIG_FACTORY_TARGET_MISSING: '[HPM] Missing "target" option. Example: {target: "http://www.example.org"}',
5 | ERR_CONTEXT_MATCHER_GENERIC: '[HPM] Invalid context. Expecting something like: "/api" or ["/api", "/ajax"]',
6 | ERR_CONTEXT_MATCHER_INVALID_ARRAY: '[HPM] Invalid context. Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"]',
7 | ERR_PATH_REWRITER_CONFIG: '[HPM] Invalid pathRewrite config. Expecting object with pathRewrite config or a rewrite function'
8 | }
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/test/lint.js:
--------------------------------------------------------------------------------
1 | // Skip StandardJS on older node versions: < node@4.0.0
2 | // https://travis-ci.org/chimurai/http-proxy-middleware/builds/212791414
3 |
4 | var execSync = require('child_process').execSync
5 | var command = 'standard -v'
6 |
7 | if (!process.mainModule.children.length) { // workaround? prevent duplicate linting...
8 | if (isLegacyNodeJs()) {
9 | console.log('StandardJS: Skipping StandardJS on older Node versions')
10 | } else {
11 | execSync(command, {stdio: [0, 1, 2]}) // https://stackoverflow.com/a/31104898/3841188
12 | }
13 | }
14 |
15 | function isLegacyNodeJs () {
16 | var majorVersion = parseInt(process.versions.node[0], 10)
17 | var isModernNodeJs = (majorVersion > 0)
18 | return !isModernNodeJs
19 | }
20 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/recipes/https.md:
--------------------------------------------------------------------------------
1 | # HTTPS
2 |
3 | How to proxy requests to various types of HTTPS servers.
4 |
5 | All options are provided by [http-proxy](https://github.com/nodejitsu/node-http-proxy).
6 |
7 | ## Basic proxy to an HTTPS server
8 |
9 | ```javascript
10 | var proxy = require('http-proxy-middleware');
11 |
12 | var apiProxy = proxy('/api', {
13 | target: 'https://example.org',
14 | changeOrigin: true,
15 | });
16 | ```
17 |
18 | ## Proxy to an HTTPS server using a PKCS12 client certificate
19 |
20 | ```javascript
21 | var fs = require('fs');
22 | var proxy = require('http-proxy-middleware');
23 |
24 | var apiProxy = proxy('/api', {
25 | target: {
26 | protocol: 'https:',
27 | host: 'example.org',
28 | port: 443,
29 | pfx: fs.readFileSync('path/to/certificate.p12'),
30 | passphrase: 'password',
31 | },
32 | changeOrigin: true,
33 | });
34 | ```
35 |
--------------------------------------------------------------------------------
/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 | if (hostAndPath.indexOf(key) > -1) { // match 'localhost:3000/api'
31 | result = table[key]
32 | logger.debug('[HPM] Router table match: "%s"', key)
33 | return false
34 | }
35 | } else {
36 | if (key === host) { // match 'localhost:3000'
37 | result = table[key]
38 | logger.debug('[HPM] Router table match: "%s"', host)
39 | return false
40 | }
41 | }
42 | })
43 |
44 | return result
45 | }
46 |
47 | function containsPath (v) {
48 | return v.indexOf('/') > -1
49 | }
50 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | var app
8 | var server
9 |
10 | beforeEach(function () {
11 | app = express()
12 | })
13 |
14 | afterEach(function () {
15 | server && server.close()
16 | })
17 |
18 | // https://github.com/chimurai/http-proxy-middleware/issues/94
19 | describe('Express Sub Route', function () {
20 | beforeEach(function () {
21 | // sub route config
22 | var sub = new express.Router()
23 |
24 | function filter (pathname, req) {
25 | var urlFilter = new RegExp('^/sub/api')
26 | var match = urlFilter.test(pathname)
27 | return match
28 | }
29 |
30 | /**
31 | * Mount proxy without 'path' in sub route
32 | */
33 | var proxyConfig = {target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, logLevel: 'silent'}
34 | sub.use(proxy(filter, proxyConfig))
35 |
36 | sub.get('/hello', jsonMiddleware({'content': 'foobar'}))
37 |
38 | // configure sub route on /sub junction
39 | app.use('/sub', sub)
40 |
41 | // start server
42 | server = app.listen(3000)
43 | })
44 |
45 | it('should still return a response when route does not match proxyConfig', function (done) {
46 | var responseBody
47 | http.get('http://localhost:3000/sub/hello', function (res) {
48 | res.on('data', function (chunk) {
49 | responseBody = chunk.toString()
50 | expect(responseBody).to.equal('{"content":"foobar"}')
51 | done()
52 | })
53 | })
54 | })
55 | })
56 |
57 | function jsonMiddleware (data) {
58 | return function (req, res) {
59 | res.json(data)
60 | }
61 | }
62 | })
63 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "http-proxy-middleware",
3 | "version": "0.17.4",
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 | "lint": "node test/lint.js",
13 | "test": "npm run lint && mocha --recursive --colors --reporter spec",
14 | "cover": "npm run clean && istanbul cover ./node_modules/mocha/bin/_mocha -- --recursive",
15 | "coveralls": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --recursive --reporter spec && istanbul-coveralls && npm run clean"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/chimurai/http-proxy-middleware.git"
20 | },
21 | "keywords": [
22 | "reverse",
23 | "proxy",
24 | "middleware",
25 | "http",
26 | "https",
27 | "connect",
28 | "express",
29 | "browser-sync",
30 | "gulp",
31 | "grunt-contrib-connect",
32 | "websocket",
33 | "ws",
34 | "cors"
35 | ],
36 | "author": "Steven Chim",
37 | "license": "MIT",
38 | "bugs": {
39 | "url": "https://github.com/chimurai/http-proxy-middleware/issues"
40 | },
41 | "homepage": "https://github.com/chimurai/http-proxy-middleware",
42 | "devDependencies": {
43 | "browser-sync": "^2.18.2",
44 | "chai": "^4.1.2",
45 | "connect": "^3.5.0",
46 | "coveralls": "^3.0.0",
47 | "express": "^4.14.0",
48 | "istanbul": "^0.4.5",
49 | "istanbul-coveralls": "^1.0.3",
50 | "mocha": "^4.0.1",
51 | "mocha-lcov-reporter": "1.3.0",
52 | "opn": "^5.1.0",
53 | "standard": "^10.0.2",
54 | "ws": "^3.2.0"
55 | },
56 | "dependencies": {
57 | "http-proxy": "^1.16.2",
58 | "is-glob": "^4.0.0",
59 | "lodash": "^4.17.2",
60 | "micromatch": "^3.1.0"
61 | },
62 | "engines": {
63 | "node" : ">=4.0.0"
64 | },
65 | "standard": {
66 | "env": [
67 | "mocha"
68 | ]
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/path-rewriter.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash')
2 | var logger = require('./logger').getInstance()
3 | var ERRORS = require('./errors')
4 |
5 | module.exports = {
6 | create: createPathRewriter
7 | }
8 |
9 | /**
10 | * Create rewrite function, to cache parsed rewrite rules.
11 | *
12 | * @param {Object} rewriteConfig
13 | * @return {Function} Function to rewrite paths; This function should accept `path` (request.url) as parameter
14 | */
15 | function createPathRewriter (rewriteConfig) {
16 | var rulesCache
17 |
18 | if (!isValidRewriteConfig(rewriteConfig)) {
19 | return
20 | }
21 |
22 | if (_.isFunction(rewriteConfig)) {
23 | var customRewriteFn = rewriteConfig
24 | return customRewriteFn
25 | } else {
26 | rulesCache = parsePathRewriteRules(rewriteConfig)
27 | return rewritePath
28 | }
29 |
30 | function rewritePath (path) {
31 | var result = path
32 |
33 | _.forEach(rulesCache, function (rule) {
34 | if (rule.regex.test(path)) {
35 | result = result.replace(rule.regex, rule.value)
36 | logger.debug('[HPM] Rewriting path from "%s" to "%s"', path, result)
37 | return false
38 | }
39 | })
40 |
41 | return result
42 | }
43 | }
44 |
45 | function isValidRewriteConfig (rewriteConfig) {
46 | if (_.isFunction(rewriteConfig)) {
47 | return true
48 | } else if (!_.isEmpty(rewriteConfig) && _.isPlainObject(rewriteConfig)) {
49 | return true
50 | } else if (_.isUndefined(rewriteConfig) ||
51 | _.isNull(rewriteConfig) ||
52 | _.isEqual(rewriteConfig, {})) {
53 | return false
54 | } else {
55 | throw new Error(ERRORS.ERR_PATH_REWRITER_CONFIG)
56 | }
57 | }
58 |
59 | function parsePathRewriteRules (rewriteConfig) {
60 | var rules = []
61 |
62 | if (_.isPlainObject(rewriteConfig)) {
63 | _.forIn(rewriteConfig, function (value, key) {
64 | rules.push({
65 | regex: new RegExp(key),
66 | value: rewriteConfig[key]
67 | })
68 | logger.info('[HPM] Proxy rewrite rule created: "%s" ~> "%s"', key, rewriteConfig[key])
69 | })
70 | }
71 |
72 | return rules
73 | }
74 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | var ERRORS = require('./errors')
6 |
7 | module.exports = {
8 | match: matchContext
9 | }
10 |
11 | function matchContext (context, uri, req) {
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(ERRORS.ERR_CONTEXT_MATCHER_INVALID_ARRAY)
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(ERRORS.ERR_CONTEXT_MATCHER_GENERIC)
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} contextList ['/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 | describe('router with proxyTable', function () {
65 | beforeEach(function setupServers () {
66 | proxyServer = createServer(6000, proxyMiddleware('/', {
67 | target: 'http://localhost:6001',
68 | router: {
69 | 'alpha.localhost:6000': 'http://localhost:6001',
70 | 'beta.localhost:6000': 'http://localhost:6002',
71 | 'localhost:6000/api': 'http://localhost:6003'
72 | }
73 | }))
74 | })
75 |
76 | afterEach(function () {
77 | proxyServer.close()
78 | })
79 |
80 | it('should proxy to option.target', function (done) {
81 | http.get('http://localhost:6000', function (res) {
82 | res.on('data', function (chunk) {
83 | var responseBody = chunk.toString()
84 | expect(responseBody).to.equal('A')
85 | done()
86 | })
87 | })
88 | })
89 |
90 | it('should proxy when host is "alpha.localhost"', function (done) {
91 | var options = {hostname: 'localhost', port: 6000, path: '/'}
92 | options.headers = {host: 'alpha.localhost:6000'}
93 | http.get(options, function (res) {
94 | res.on('data', function (chunk) {
95 | var responseBody = chunk.toString()
96 | expect(responseBody).to.equal('A')
97 | done()
98 | })
99 | })
100 | })
101 |
102 | it('should proxy when host is "beta.localhost"', function (done) {
103 | var options = {hostname: 'localhost', port: 6000, path: '/'}
104 | options.headers = {host: 'beta.localhost:6000'}
105 | http.get(options, function (res) {
106 | res.on('data', function (chunk) {
107 | var responseBody = chunk.toString()
108 | expect(responseBody).to.equal('B')
109 | done()
110 | })
111 | })
112 | })
113 |
114 | it('should proxy with host & path config: "localhost:6000/api"', function (done) {
115 | var options = {hostname: 'localhost', port: 6000, path: '/api'}
116 | http.get(options, function (res) {
117 | res.on('data', function (chunk) {
118 | var responseBody = chunk.toString()
119 | expect(responseBody).to.equal('C')
120 | done()
121 | })
122 | })
123 | })
124 | })
125 | })
126 |
--------------------------------------------------------------------------------
/lib/config-factory.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash')
2 | var url = require('url')
3 | var ERRORS = require('./errors')
4 | var logger = require('./logger').getInstance()
5 |
6 | module.exports = {
7 | createConfig: createConfig
8 | }
9 |
10 | function createConfig (context, opts) {
11 | // structure of config object to be returned
12 | var config = {
13 | context: undefined,
14 | options: {}
15 | }
16 |
17 | // app.use('/api', proxy({target:'http://localhost:9000'}));
18 | if (isContextless(context, opts)) {
19 | config.context = '/'
20 | config.options = _.assign(config.options, context)
21 |
22 | // app.use('/api', proxy('http://localhost:9000'));
23 | // app.use(proxy('http://localhost:9000/api'));
24 | } else if (isStringShortHand(context)) {
25 | var oUrl = url.parse(context)
26 | var target = [oUrl.protocol, '//', oUrl.host].join('')
27 |
28 | config.context = oUrl.pathname || '/'
29 | config.options = _.assign(config.options, {target: target}, opts)
30 |
31 | if (oUrl.protocol === 'ws:' || oUrl.protocol === 'wss:') {
32 | config.options.ws = true
33 | }
34 | // app.use('/api', proxy({target:'http://localhost:9000'}));
35 | } else {
36 | config.context = context
37 | config.options = _.assign(config.options, opts)
38 | }
39 |
40 | configureLogger(config.options)
41 |
42 | if (!config.options.target) {
43 | throw new Error(ERRORS.ERR_CONFIG_FACTORY_TARGET_MISSING)
44 | }
45 |
46 | // Legacy option.proxyHost
47 | config.options = mapLegacyProxyHostOption(config.options)
48 |
49 | // Legacy option.proxyTable > option.router
50 | config.options = mapLegacyProxyTableOption(config.options)
51 |
52 | return config
53 | }
54 |
55 | /**
56 | * Checks if a String only target/config is provided.
57 | * This can be just the host or with the optional path.
58 | *
59 | * @example
60 | * app.use('/api', proxy('http://localhost:9000'));
61 | app.use(proxy('http://localhost:9000/api'));
62 | *
63 | * @param {String} context [description]
64 | * @return {Boolean} [description]
65 | */
66 | function isStringShortHand (context) {
67 | if (_.isString(context)) {
68 | return !!(url.parse(context).host)
69 | }
70 | }
71 |
72 | /**
73 | * Checks if a Object only config is provided, without a context.
74 | * In this case the all paths will be proxied.
75 | *
76 | * @example
77 | * app.use('/api', proxy({target:'http://localhost:9000'}));
78 | *
79 | * @param {Object} context [description]
80 | * @param {*} opts [description]
81 | * @return {Boolean} [description]
82 | */
83 | function isContextless (context, opts) {
84 | return (_.isPlainObject(context) && _.isEmpty(opts))
85 | }
86 |
87 | function mapLegacyProxyHostOption (options) {
88 | // set options.headers.host when option.proxyHost is provided
89 | if (options.proxyHost) {
90 | logger.warn('*************************************')
91 | logger.warn('[HPM] Deprecated "option.proxyHost"')
92 | logger.warn(' Use "option.changeOrigin" or "option.headers.host" instead')
93 | logger.warn(' "option.proxyHost" will be removed in future release.')
94 | logger.warn('*************************************')
95 |
96 | options.headers = options.headers || {}
97 | options.headers.host = options.proxyHost
98 | }
99 |
100 | return options
101 | }
102 |
103 | // Warn deprecated proxyTable api usage
104 | function mapLegacyProxyTableOption (options) {
105 | if (options.proxyTable) {
106 | logger.warn('*************************************')
107 | logger.warn('[HPM] Deprecated "option.proxyTable"')
108 | logger.warn(' Use "option.router" instead')
109 | logger.warn(' "option.proxyTable" will be removed in future release.')
110 | logger.warn('*************************************')
111 |
112 | options.router = _.clone(options.proxyTable)
113 | _.omit(options, 'proxyTable')
114 | }
115 |
116 | return options
117 | }
118 |
119 | function configureLogger (options) {
120 | if (options.logLevel) {
121 | logger.setLevel(options.logLevel)
122 | }
123 |
124 | if (options.logProvider) {
125 | logger.setProvider(options.logProvider)
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/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 | * @param {String} originalPath
151 | * @param {String} newPath
152 | * @param {String} originalTarget
153 | * @param {String} newTarget
154 | * @return {String}
155 | */
156 | function getArrow (originalPath, newPath, originalTarget, newTarget) {
157 | var arrow = ['>']
158 | var isNewTarget = (originalTarget !== newTarget) // router
159 | var isNewPath = (originalPath !== newPath) // pathRewrite
160 |
161 | if (isNewPath && !isNewTarget) {
162 | arrow.unshift('~')
163 | } else if (!isNewPath && isNewTarget) {
164 | arrow.unshift('=')
165 | } else if (isNewPath && isNewTarget) {
166 | arrow.unshift('≈')
167 | } else {
168 | arrow.unshift('-')
169 | }
170 |
171 | return arrow.join('')
172 | }
173 |
--------------------------------------------------------------------------------
/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 responseMessage
18 | var proxy
19 |
20 | beforeEach(function () {
21 | proxy = proxyMiddleware('/', {
22 | target: 'http://localhost:8000',
23 | ws: true,
24 | pathRewrite: {'^/socket': ''}
25 | })
26 |
27 | proxyServer = createServer(3000, proxy)
28 |
29 | wss = new WebSocketServer({port: 8000})
30 |
31 | wss.on('connection', function connection (ws) {
32 | ws.on('message', function incoming (message) {
33 | ws.send(message) // echo received message
34 | })
35 | })
36 | })
37 |
38 | describe('option.ws', function () {
39 | beforeEach(function (done) {
40 | // need to make a normal http request,
41 | // so http-proxy-middleware can catch the upgrade request
42 | http.get('http://localhost:3000/', function () {
43 | // do a second http request to make
44 | // sure only 1 listener subscribes to upgrade request
45 | http.get('http://localhost:3000/', function () {
46 | ws = new WebSocket('ws://localhost:3000/socket')
47 |
48 | ws.on('message', function incoming (message) {
49 | responseMessage = message
50 | done()
51 | })
52 |
53 | ws.on('open', function open () {
54 | ws.send('foobar')
55 | })
56 | })
57 | })
58 | })
59 |
60 | it('should proxy to path', function () {
61 | expect(responseMessage).to.equal('foobar')
62 | })
63 | })
64 |
65 | describe('option.ws with external server "upgrade"', function () {
66 | beforeEach(function (done) {
67 | proxyServer.on('upgrade', proxy.upgrade)
68 |
69 | ws = new WebSocket('ws://localhost:3000/socket')
70 |
71 | ws.on('message', function incoming (message) {
72 | responseMessage = message
73 | done()
74 | })
75 |
76 | ws.on('open', function open () {
77 | ws.send('foobar')
78 | })
79 | })
80 |
81 | it('should proxy to path', function () {
82 | expect(responseMessage).to.equal('foobar')
83 | })
84 | })
85 |
86 | describe('option.ws with external server "upgrade" and shorthand usage', function () {
87 | beforeEach(function () {
88 | proxyServer.close()
89 | // override
90 | proxy = proxyMiddleware('ws://localhost:8000', {pathRewrite: {'^/socket': ''}})
91 | proxyServer = createServer(3000, proxy)
92 | })
93 |
94 | beforeEach(function (done) {
95 | proxyServer.on('upgrade', proxy.upgrade)
96 |
97 | ws = new WebSocket('ws://localhost:3000/socket')
98 |
99 | ws.on('message', function incoming (message) {
100 | responseMessage = message
101 | done()
102 | })
103 |
104 | ws.on('open', function open () {
105 | ws.send('foobar')
106 | })
107 | })
108 |
109 | it('should proxy to path', function () {
110 | expect(responseMessage).to.equal('foobar')
111 | })
112 | })
113 |
114 | describe('with router and pathRewrite', function () {
115 | beforeEach(function () {
116 | proxyServer.close()
117 | // override
118 | proxy = proxyMiddleware('ws://notworkinghost:6789', {router: {'/socket': 'ws://localhost:8000'}, pathRewrite: {'^/socket': ''}})
119 | proxyServer = createServer(3000, proxy)
120 | })
121 |
122 | beforeEach(function (done) {
123 | proxyServer.on('upgrade', proxy.upgrade)
124 |
125 | ws = new WebSocket('ws://localhost:3000/socket')
126 |
127 | ws.on('message', function incoming (message) {
128 | responseMessage = message
129 | done()
130 | })
131 |
132 | ws.on('open', function open () {
133 | ws.send('foobar')
134 | })
135 | })
136 |
137 | it('should proxy to path', function () {
138 | expect(responseMessage).to.equal('foobar')
139 | })
140 | })
141 |
142 | afterEach(function () {
143 | proxyServer.close()
144 | wss.close()
145 | ws = null
146 | })
147 | })
148 |
--------------------------------------------------------------------------------
/test/unit/handlers.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | // https://github.com/feross/standard/issues/690#issuecomment-278533482
3 |
4 | var expect = require('chai').expect
5 | var handlers = require('./_libs').handlers
6 |
7 | describe('handlers factory', function () {
8 | var handlersMap
9 |
10 | it('should return default handlers when no handlers are provided', function () {
11 | handlersMap = handlers.getHandlers()
12 | expect(handlersMap.error).to.be.a('function')
13 | expect(handlersMap.close).to.be.a('function')
14 | })
15 |
16 | describe('custom handlers', function () {
17 | beforeEach(function () {
18 | var fnCustom = function () {
19 | return 42
20 | }
21 |
22 | var proxyOptions = {
23 | target: 'http://www.example.org',
24 | onError: fnCustom,
25 | onOpen: fnCustom,
26 | onClose: fnCustom,
27 | onProxyReq: fnCustom,
28 | onProxyReqWs: fnCustom,
29 | onProxyRes: fnCustom,
30 | onDummy: fnCustom,
31 | foobar: fnCustom
32 | }
33 |
34 | handlersMap = handlers.getHandlers(proxyOptions)
35 | })
36 |
37 | it('should only return http-proxy handlers', function () {
38 | expect(handlersMap.error).to.be.a('function')
39 | expect(handlersMap.open).to.be.a('function')
40 | expect(handlersMap.close).to.be.a('function')
41 | expect(handlersMap.proxyReq).to.be.a('function')
42 | expect(handlersMap.proxyReqWs).to.be.a('function')
43 | expect(handlersMap.proxyRes).to.be.a('function')
44 | expect(handlersMap.dummy).to.be.undefined
45 | expect(handlersMap.foobar).to.be.undefined
46 | expect(handlersMap.target).to.be.undefined
47 | })
48 |
49 | it('should use the provided custom handlers', function () {
50 | expect(handlersMap.error()).to.equal(42)
51 | expect(handlersMap.open()).to.equal(42)
52 | expect(handlersMap.close()).to.equal(42)
53 | expect(handlersMap.proxyReq()).to.equal(42)
54 | expect(handlersMap.proxyReqWs()).to.equal(42)
55 | expect(handlersMap.proxyRes()).to.equal(42)
56 | })
57 | })
58 | })
59 |
60 | describe('default proxy error handler', function () {
61 | var mockError = {
62 | code: 'ECONNREFUSED'
63 | }
64 |
65 | var mockReq = {
66 | headers: {
67 | host: 'localhost:3000'
68 | },
69 | url: '/api'
70 | }
71 |
72 | var proxyOptions = {
73 | target: {
74 | host: 'localhost.dev'
75 | }
76 | }
77 |
78 | var httpErrorCode
79 | var errorMessage
80 |
81 | var mockRes = {
82 | writeHead: function (v) {
83 | httpErrorCode = v
84 | return v
85 | },
86 | end: function (v) {
87 | errorMessage = v
88 | return v
89 | },
90 | headersSent: false
91 | }
92 |
93 | var proxyError
94 |
95 | beforeEach(function () {
96 | var handlersMap = handlers.getHandlers()
97 | proxyError = handlersMap.error
98 | })
99 |
100 | afterEach(function () {
101 | httpErrorCode = undefined
102 | errorMessage = undefined
103 | })
104 |
105 | var codes = [
106 | ['HPE_INVALID_FOO', 502],
107 | ['HPE_INVALID_BAR', 502],
108 | ['ECONNREFUSED', 504],
109 | ['ENOTFOUND', 504],
110 | ['ECONNREFUSED', 504],
111 | ['any', 500]
112 | ]
113 | codes.forEach(function (item) {
114 | var msg = item[0]
115 | var code = item[1]
116 | it('should set the http status code for ' + msg + ' to: ' + code, function () {
117 | proxyError({ code: msg }, mockReq, mockRes, proxyOptions)
118 | expect(httpErrorCode).to.equal(code)
119 | })
120 | })
121 |
122 | it('should end the response and return error message', function () {
123 | proxyError(mockError, mockReq, mockRes, proxyOptions)
124 | expect(errorMessage).to.equal('Error occured while trying to proxy to: localhost:3000/api')
125 | })
126 |
127 | it('should not set the http status code to: 500 if headers have already been sent', function () {
128 | mockRes.headersSent = true
129 | proxyError(mockError, mockReq, mockRes, proxyOptions)
130 | expect(httpErrorCode).to.equal(undefined)
131 | })
132 |
133 | it('should end the response and return error message', function () {
134 | mockRes.headersSent = true
135 | proxyError(mockError, mockReq, mockRes, proxyOptions)
136 | expect(errorMessage).to.equal('Error occured while trying to proxy to: localhost:3000/api')
137 | })
138 | })
139 |
--------------------------------------------------------------------------------
/test/unit/path-rewriter.spec.js:
--------------------------------------------------------------------------------
1 |
2 | var expect = require('chai').expect
3 | var pathRewriter = require('./_libs').pathRewriter
4 |
5 | describe('Path rewriting', function () {
6 | var rewriter
7 | var result
8 | var config
9 |
10 | describe('Rewrite rules configuration and usage', function () {
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 | beforeEach(function () {
60 | config = {
61 | '^/': '/extra/base/path/'
62 | }
63 | })
64 |
65 | beforeEach(function () {
66 | rewriter = pathRewriter.create(config)
67 | })
68 |
69 | it('should add base path to requests', function () {
70 | result = rewriter('/api/books/123')
71 | expect(result).to.equal('/extra/base/path/api/books/123')
72 | })
73 | })
74 |
75 | describe('Rewrite function', function () {
76 | var rewriter
77 |
78 | beforeEach(function () {
79 | rewriter = function (fn) {
80 | var rewriteFn = pathRewriter.create(fn)
81 | var requestPath = '/123/456'
82 | return rewriteFn(requestPath)
83 | }
84 | })
85 |
86 | it('should return unmodified path', function () {
87 | var rewriteFn = function (path) {
88 | return path
89 | }
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 |
99 | expect(rewriter(rewriteFn)).to.equal('/foo/bar')
100 | })
101 |
102 | it('should return replaced path', function () {
103 | var rewriteFn = function (path) {
104 | return path.replace('/456', '/789')
105 | }
106 |
107 | expect(rewriter(rewriteFn)).to.equal('/123/789')
108 | })
109 | })
110 |
111 | describe('Invalid configuration', function () {
112 | var badFn
113 |
114 | beforeEach(function () {
115 | badFn = function (config) {
116 | return function () {
117 | pathRewriter.create(config)
118 | }
119 | }
120 | })
121 |
122 | it('should return undefined when no config is provided', function () {
123 | expect((badFn())()).to.equal(undefined)
124 | expect((badFn(null)())).to.equal(undefined)
125 | expect((badFn(undefined)())).to.equal(undefined)
126 | })
127 |
128 | it('should throw when bad config is provided', function () {
129 | expect(badFn(123)).to.throw(Error)
130 | expect(badFn('abc')).to.throw(Error)
131 | expect(badFn([])).to.throw(Error)
132 | expect(badFn([1, 2, 3])).to.throw(Error)
133 | })
134 |
135 | it('should not throw when empty Object config is provided', function () {
136 | expect(badFn({})).to.not.throw(Error)
137 | })
138 |
139 | it('should not throw when function config is provided', function () {
140 | expect(badFn(function () {})).to.not.throw(Error)
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, proxyOptionWithRouter
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 | describe('router.getTarget from function', function () {
21 | var request
22 |
23 | beforeEach(function () {
24 | proxyOptionWithRouter = {
25 | target: 'http://localhost:6000',
26 | router: function (req) {
27 | request = req
28 | return 'http://foobar.com:666'
29 | }
30 | }
31 |
32 | result = router.getTarget(req, proxyOptionWithRouter)
33 | })
34 |
35 | describe('custom dynamic router function', function () {
36 | it('should provide the request object for dynamic routing', function () {
37 | expect(request.headers.host).to.equal('localhost')
38 | expect(request.url).to.equal('/')
39 | })
40 | it('should return new target', function () {
41 | expect(result).to.equal('http://foobar.com:666')
42 | })
43 | })
44 | })
45 |
46 | describe('router.getTarget from table', function () {
47 | beforeEach(function () {
48 | proxyOptionWithRouter = {
49 | target: 'http://localhost:6000',
50 | router: {
51 | 'alpha.localhost': 'http://localhost:6001',
52 | 'beta.localhost': 'http://localhost:6002',
53 | 'gamma.localhost/api': 'http://localhost:6003',
54 | 'gamma.localhost': 'http://localhost:6004',
55 | '/rest': 'http://localhost:6005',
56 | '/some/specific/path': 'http://localhost:6006',
57 | '/some': 'http://localhost:6007'
58 | }
59 | }
60 | })
61 |
62 | describe('without router config', function () {
63 | it('should return the normal target when router not present in config', function () {
64 | result = router.getTarget(req, config)
65 | expect(result).to.equal(undefined)
66 | })
67 | })
68 |
69 | describe('with just the host in router config', function () {
70 | it('should target http://localhost:6001 when for router:"alpha.localhost"', function () {
71 | req.headers.host = 'alpha.localhost'
72 | result = router.getTarget(req, proxyOptionWithRouter)
73 | expect(result).to.equal('http://localhost:6001')
74 | })
75 |
76 | it('should target http://localhost:6002 when for router:"beta.localhost"', function () {
77 | req.headers.host = 'beta.localhost'
78 | result = router.getTarget(req, proxyOptionWithRouter)
79 | expect(result).to.equal('http://localhost:6002')
80 | })
81 | })
82 |
83 | describe('with host and host + path config', function () {
84 | it('should target http://localhost:6004 without path', function () {
85 | req.headers.host = 'gamma.localhost'
86 | result = router.getTarget(req, proxyOptionWithRouter)
87 | expect(result).to.equal('http://localhost:6004')
88 | })
89 |
90 | it('should target http://localhost:6003 exact path match', function () {
91 | req.headers.host = 'gamma.localhost'
92 | req.url = '/api'
93 | result = router.getTarget(req, proxyOptionWithRouter)
94 | expect(result).to.equal('http://localhost:6003')
95 | })
96 |
97 | it('should target http://localhost:6004 when contains path', function () {
98 | req.headers.host = 'gamma.localhost'
99 | req.url = '/api/books/123'
100 | result = router.getTarget(req, proxyOptionWithRouter)
101 | expect(result).to.equal('http://localhost:6003')
102 | })
103 | })
104 |
105 | describe('with just the path', function () {
106 | it('should target http://localhost:6005 with just a path as router config', function () {
107 | req.url = '/rest'
108 | result = router.getTarget(req, proxyOptionWithRouter)
109 | expect(result).to.equal('http://localhost:6005')
110 | })
111 |
112 | it('should target http://localhost:6005 with just a path as router config', function () {
113 | req.url = '/rest/deep/path'
114 | result = router.getTarget(req, proxyOptionWithRouter)
115 | expect(result).to.equal('http://localhost:6005')
116 | })
117 |
118 | it('should target http://localhost:6000 path in not present in router config', function () {
119 | req.url = '/unknow-path'
120 | result = router.getTarget(req, proxyOptionWithRouter)
121 | expect(result).to.equal(undefined)
122 | })
123 | })
124 |
125 | describe('matching order of router config', function () {
126 | it('should return first matching target when similar paths are configured', function () {
127 | req.url = '/some/specific/path'
128 | result = router.getTarget(req, proxyOptionWithRouter)
129 | expect(result).to.equal('http://localhost:6006')
130 | })
131 | })
132 | })
133 | })
134 |
--------------------------------------------------------------------------------
/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 | describe('classic config', function () {
10 | var context = '/api'
11 | var options = {target: 'http://www.example.org'}
12 |
13 | beforeEach(function () {
14 | result = createConfig(context, options)
15 | })
16 |
17 | it('should return config object', function () {
18 | expect(result).to.have.all.keys('context', 'options')
19 | })
20 |
21 | it('should return config object with context', function () {
22 | expect(result.context).to.equal(context)
23 | })
24 |
25 | it('should return config object with options', function () {
26 | expect(result.options).to.deep.equal(options)
27 | })
28 | })
29 |
30 | describe('shorthand String', function () {
31 | describe('shorthand String config', function () {
32 | beforeEach(function () {
33 | result = createConfig('http://www.example.org:8000/api')
34 | })
35 |
36 | it('should return config object', function () {
37 | expect(result).to.have.all.keys('context', 'options')
38 | })
39 |
40 | it('should return config object with context', function () {
41 | expect(result.context).to.equal('/api')
42 | })
43 |
44 | it('should return config object with options', function () {
45 | expect(result.options).to.deep.equal({target: 'http://www.example.org:8000'})
46 | })
47 | })
48 |
49 | describe('shorthand String config for whole domain', function () {
50 | beforeEach(function () {
51 | result = createConfig('http://www.example.org:8000')
52 | })
53 |
54 | it('should return config object with context', function () {
55 | expect(result.context).to.equal('/')
56 | })
57 | })
58 |
59 | describe('shorthand String config for websocket url', function () {
60 | beforeEach(function () {
61 | result = createConfig('ws://www.example.org:8000')
62 | })
63 |
64 | it('should return config object with context', function () {
65 | expect(result.context).to.equal('/')
66 | })
67 |
68 | it('should return options with ws = true', function () {
69 | expect(result.options.ws).to.equal(true)
70 | })
71 | })
72 |
73 | describe('shorthand String config for secure websocket url', function () {
74 | beforeEach(function () {
75 | result = createConfig('wss://www.example.org:8000')
76 | })
77 |
78 | it('should return config object with context', function () {
79 | expect(result.context).to.equal('/')
80 | })
81 |
82 | it('should return options with ws = true', function () {
83 | expect(result.options.ws).to.equal(true)
84 | })
85 | })
86 |
87 | describe('shorthand String config with globbing', function () {
88 | beforeEach(function () {
89 | result = createConfig('http://www.example.org:8000/api/*.json')
90 | })
91 |
92 | it('should return config object with context', function () {
93 | expect(result.context).to.equal('/api/*.json')
94 | })
95 | })
96 |
97 | describe('shorthand String config with options', function () {
98 | beforeEach(function () {
99 | result = createConfig('http://www.example.org:8000/api', {changeOrigin: true})
100 | })
101 |
102 | it('should return config object with additional options', function () {
103 | expect(result.options).to.deep.equal({target: 'http://www.example.org:8000', changeOrigin: true})
104 | })
105 | })
106 | })
107 |
108 | describe('shorthand Object config', function () {
109 | beforeEach(function () {
110 | result = createConfig({target: 'http://www.example.org:8000'})
111 | })
112 |
113 | it('should set the proxy path to everything', function () {
114 | expect(result.context).to.equal('/')
115 | })
116 |
117 | it('should return config object', function () {
118 | expect(result.options).to.deep.equal({target: 'http://www.example.org:8000'})
119 | })
120 | })
121 |
122 | describe('missing option.target', function () {
123 | var fn
124 |
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 | beforeEach(function () {
138 | result = createConfig('http://localhost:3000/api', {target: 'http://localhost:8000'})
139 | })
140 |
141 | it('should use the target in the configuration as target', function () {
142 | expect(result.options.target).to.equal('http://localhost:8000')
143 | })
144 |
145 | it('should not use the host from the shorthand as target', function () {
146 | expect(result.options.target).not.to.equal('http://localhost:3000')
147 | })
148 | })
149 | })
150 | })
151 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [v0.17.4](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.4)
4 | - fix(ntlm authentication): fixed bug preventing proxying with ntlm authentication. ([#132](https://github.com/chimurai/http-proxy-middleware/pull/149)) (Thanks: [EladBezalel](https://github.com/EladBezalel), [oshri551](https://github.com/oshri551))
5 |
6 |
7 | ## [v0.17.3](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.3)
8 | - 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))
9 |
10 | ## [v0.17.2](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.2)
11 | - 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))
12 | - feat(pathRewrite): path can be empty string. ([#110](https://github.com/chimurai/http-proxy-middleware/pull/110)) ([sunnylqm](https://github.com/sunnylqm))
13 | - 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))
14 | - chore(package.json): reduce package size. ([#109](https://github.com/chimurai/http-proxy-middleware/pull/109))
15 |
16 | ## [v0.17.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.1)
17 | - fix(Express sub Router): 404 on non-proxy routes ([#94](https://github.com/chimurai/http-proxy-middleware/issues/94))
18 |
19 | ## [v0.17.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.17.0)
20 | - fix(context matching): Use [RFC 3986 path](https://tools.ietf.org/html/rfc3986#section-3.3) in context matching. (excludes query parameters)
21 |
22 | ## [v0.16.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.16.0)
23 | - deprecated(proxyTable): renamed `proxyTable` to `router`.
24 | - feat(router): support for custom `router` function.
25 |
26 | ## [v0.15.2](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.15.2)
27 | - fix(websocket): fixes websocket upgrade.
28 |
29 | ## [v0.15.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.15.1)
30 | - feat(pathRewrite): expose `req` object to pathRewrite function.
31 | - fix(websocket): fixes websocket upgrade when both config.ws and external .upgrade() are used.
32 |
33 | ## [v0.15.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.15.0)
34 | - feat(pathRewrite): support for custom pathRewrite function.
35 |
36 | ## [v0.14.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.14.0)
37 | - feat(proxy): support proxy creation without context.
38 | - fix(connect mounting): use connect's `path` configuration to mount proxy.
39 |
40 | ## [v0.13.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.13.0)
41 | - feat(context): custom context matcher; when simple `path` matching is not sufficient.
42 |
43 | ## [v0.12.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.12.0)
44 | - add option `onProxyReqWs` (subscribe to http-proxy `proxyReqWs` event)
45 | - add option `onOpen` (subscribe to http-proxy `open` event)
46 | - add option `onClose` (subscribe to http-proxy `close` event)
47 |
48 | ## [v0.11.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.11.0)
49 | - improved logging
50 |
51 | ## [v0.10.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.10.0)
52 | - feat(proxyTable) - added proxyTable support for WebSockets.
53 | - fixed(proxyTable) - ensure original path (not rewritten path) is being used when `proxyTable` is used in conjunction with `pathRewrite`.
54 |
55 | ## [v0.9.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.9.1)
56 | - fix server crash when socket error not handled correctly.
57 |
58 | ## [v0.9.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.9.0)
59 | - support subscribing to http-proxy `proxyReq` event ([trbngr](https://github.com/trbngr))
60 | - add `logLevel` and `logProvider` support
61 |
62 | ## [v0.8.2](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.8.2)
63 | - fix proxyError handler ([mTazelaar](https://github.com/mTazelaar))
64 |
65 | ## [v0.8.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.8.1)
66 | - fix pathRewrite when `agent` is configured
67 |
68 | ## [v0.8.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.8.0)
69 | - support external websocket upgrade
70 | - fix websocket shorthand
71 |
72 | ## [v0.7.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.7.0)
73 | - support shorthand syntax
74 | - fix express/connect mounting
75 |
76 | ## [v0.6.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.6.0)
77 | - support proxyTable
78 |
79 | ## [v0.5.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.5.0)
80 | - support subscribing to http-proxy `error` event
81 | - support subscribing to http-proxy `proxyRes` event
82 |
83 | ## [v0.4.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.4.0)
84 | - support websocket
85 |
86 | ## [v0.3.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.3.0)
87 | - support wildcard / glob
88 |
89 | ## [v0.2.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.2.0)
90 | - support multiple paths
91 |
92 | ## [v0.1.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.1.0)
93 | - support path rewrite
94 | - deprecate proxyHost option
95 |
96 | ## [v0.0.5](https://github.com/chimurai/http-proxy-middleware/releases/tag/v0.0.5)
97 | - initial release
98 |
--------------------------------------------------------------------------------
/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 | * @param {String} context [description]
77 | * @param {Object} req [description]
78 | * @return {Boolean}
79 | */
80 | function shouldProxy (context, req) {
81 | var path = (req.originalUrl || req.url)
82 | return contextMatcher.match(context, path, req)
83 | }
84 |
85 | /**
86 | * Apply option.router and option.pathRewrite
87 | * Order matters:
88 | * Router uses original path for routing;
89 | * NOT the modified path, after it has been rewritten by pathRewrite
90 | * @param {Object} req
91 | * @return {Object} proxy options
92 | */
93 | function prepareProxyRequest (req) {
94 | // https://github.com/chimurai/http-proxy-middleware/issues/17
95 | // https://github.com/chimurai/http-proxy-middleware/issues/94
96 | req.url = (req.originalUrl || req.url)
97 |
98 | // store uri before it gets rewritten for logging
99 | var originalPath = req.url
100 | var newProxyOptions = _.assign({}, proxyOptions)
101 |
102 | // Apply in order:
103 | // 1. option.router
104 | // 2. option.pathRewrite
105 | __applyRouter(req, newProxyOptions)
106 | __applyPathRewrite(req, pathRewriter)
107 |
108 | // debug logging for both http(s) and websockets
109 | if (proxyOptions.logLevel === 'debug') {
110 | var arrow = getArrow(originalPath, req.url, proxyOptions.target, newProxyOptions.target)
111 | logger.debug('[HPM] %s %s %s %s', req.method, originalPath, arrow, newProxyOptions.target)
112 | }
113 |
114 | return newProxyOptions
115 | }
116 |
117 | // Modify option.target when router present.
118 | function __applyRouter (req, options) {
119 | var newTarget
120 |
121 | if (options.router) {
122 | newTarget = Router.getTarget(req, options)
123 |
124 | if (newTarget) {
125 | logger.debug('[HPM] Router new target: %s -> "%s"', options.target, newTarget)
126 | options.target = newTarget
127 | }
128 | }
129 | }
130 |
131 | // rewrite path
132 | function __applyPathRewrite (req, pathRewriter) {
133 | if (pathRewriter) {
134 | var path = pathRewriter(req.url, req)
135 |
136 | if (typeof path === 'string') {
137 | req.url = path
138 | } else {
139 | logger.info('[HPM] pathRewrite: No rewritten path found. (%s)', req.url)
140 | }
141 | }
142 | }
143 |
144 | function logError (err, req, res) {
145 | var hostname = (req.headers && req.headers.host) || (req.hostname || req.host) // (websocket) || (node0.10 || node 4/5)
146 | var target = proxyOptions.target.host || proxyOptions.target
147 | var errorMessage = '[HPM] Error occurred while trying to proxy request %s from %s to %s (%s) (%s)'
148 | var errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors' // link to Node Common Systems Errors page
149 |
150 | logger.error(errorMessage, req.url, hostname, target, err.code, errReference)
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/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)
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 | // Start from key `10` in order to NOT overwrite the default 2 middleware provided
104 | // by `lite-server` or any future ones that might be added.
105 | // Reference: https://github.com/johnpapa/lite-server/blob/master/lib/config-defaults.js#L16
106 | middleware: {
107 | 10: apiProxy
108 | }
109 | }
110 | };
111 | ```
112 |
113 | ## grunt-contrib-connect
114 |
115 | https://github.com/gruntjs/grunt-contrib-connect
116 | [](https://github.com/gruntjs/grunt-contrib-connect)
117 |
118 | As an `Array`:
119 | ```javascript
120 | var proxy = require('http-proxy-middleware');
121 |
122 | var apiProxy = proxy('/api', {
123 | target: 'http://www.example.org',
124 | changeOrigin: true // for vhosted sites
125 | });
126 |
127 | grunt.initConfig({
128 | connect: {
129 | server: {
130 | options: {
131 | middleware: [apiProxy],
132 | },
133 | },
134 | },
135 | });
136 | ```
137 |
138 | As a `function`:
139 | ```javascript
140 | var proxy = require('http-proxy-middleware');
141 |
142 | var apiProxy = proxy('/api', {
143 | target: 'http://www.example.org',
144 | changeOrigin: true // for vhosted sites
145 | });
146 |
147 | grunt.initConfig({
148 | connect: {
149 | server: {
150 | options: {
151 | middleware: function(connect, options, middlewares) {
152 | // inject a custom middleware into the array of default middlewares
153 | middlewares.unshift(apiProxy);
154 |
155 | return middlewares;
156 | },
157 | },
158 | },
159 | },
160 | });
161 | ```
162 |
163 |
164 | ## grunt-browser-sync
165 |
166 | https://github.com/BrowserSync/grunt-browser-sync
167 | [](https://github.com/BrowserSync/grunt-browser-sync)
168 |
169 |
170 | ```javascript
171 | var proxy = require('http-proxy-middleware');
172 |
173 | var apiProxy = proxy('/api', {
174 | target: 'http://www.example.org',
175 | changeOrigin: true // for vhosted sites
176 | });
177 |
178 | grunt.initConfig({
179 |
180 | // BrowserSync Task
181 | browserSync: {
182 | default_options: {
183 | options: {
184 | files: [
185 | "css/*.css",
186 | "*.html"
187 | ],
188 | port: 9000,
189 | server: {
190 | baseDir: ['app'],
191 | middleware: apiProxy
192 | }
193 | }
194 | }
195 | }
196 |
197 | });
198 | ```
199 |
200 | ## gulp-connect
201 |
202 | https://github.com/avevlad/gulp-connect
203 | [](https://github.com/avevlad/gulp-connect)
204 |
205 | ```javascript
206 | var gulp = require('gulp');
207 | var connect = require('gulp-connect');
208 | var proxy = require('http-proxy-middleware');
209 |
210 | gulp.task('connect', function() {
211 | connect.server({
212 | root: ['./app'],
213 | middleware: function(connect, opt) {
214 |
215 | var apiProxy = proxy('/api', {
216 | target: 'http://www.example.org',
217 | changeOrigin: true // for vhosted sites
218 | });
219 |
220 | return [apiProxy];
221 | }
222 |
223 | });
224 | });
225 |
226 | gulp.task('default', ['connect']);
227 | ```
228 |
229 | ## gulp-webserver
230 |
231 | https://github.com/schickling/gulp-webserver
232 | [](https://github.com/schickling/gulp-webserver)
233 |
234 | ```javascript
235 | var gulp = require('gulp');
236 | var webserver = require('gulp-webserver');
237 | var proxy = require('http-proxy-middleware');
238 |
239 | gulp.task('webserver', function() {
240 | var apiProxy = proxy('/api', {
241 | target: 'http://www.example.org',
242 | changeOrigin: true // for vhosted sites
243 | });
244 |
245 | gulp.src('app')
246 | .pipe(webserver({
247 | livereload: true,
248 | directoryListing: true,
249 | open: true,
250 | middleware: [apiProxy]
251 | }));
252 | });
253 |
254 | gulp.task('default', ['webserver']);
255 | ```
256 |
--------------------------------------------------------------------------------
/test/unit/logger.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | // https://github.com/feross/standard/issues/690#issuecomment-278533482
3 |
4 | var expect = require('chai').expect
5 | var Logger = require('./_libs').Logger
6 | var getArrow = Logger.getArrow
7 |
8 | describe('Logger', function () {
9 | var logger
10 | var logMessage, debugMessage, infoMessage, warnMessage, errorMessage
11 |
12 | beforeEach(function () {
13 | logMessage = undefined
14 | debugMessage = undefined
15 | infoMessage = undefined
16 | warnMessage = undefined
17 | errorMessage = undefined
18 | })
19 |
20 | beforeEach(function () {
21 | logger = Logger.getInstance()
22 | })
23 |
24 | beforeEach(function () {
25 | logger.setProvider(function (provider) {
26 | provider.log = function (message) { logMessage = message }
27 | provider.debug = function (message) { debugMessage = message }
28 | provider.info = function (message) { infoMessage = message }
29 | provider.warn = function (message) { warnMessage = message }
30 | provider.error = function (message) { errorMessage = message }
31 |
32 | return provider
33 | })
34 | })
35 |
36 | describe('logging with different levels', function () {
37 | beforeEach(function () {
38 | logger.log('log')
39 | logger.debug('debug')
40 | logger.info('info')
41 | logger.warn('warn')
42 | logger.error('error')
43 | })
44 |
45 | describe('level: debug', function () {
46 | beforeEach(function () {
47 | logger.setLevel('debug')
48 | })
49 |
50 | it('should log .log() messages', function () {
51 | expect(logMessage).to.equal('log')
52 | })
53 | it('should log .debug() messages', function () {
54 | expect(debugMessage).to.equal('debug')
55 | })
56 | it('should log .info() messages', function () {
57 | expect(infoMessage).to.equal('info')
58 | })
59 | it('should log .warn() messages', function () {
60 | expect(warnMessage).to.equal('warn')
61 | })
62 | it('should log .error() messages', function () {
63 | expect(errorMessage).to.equal('error')
64 | })
65 | })
66 |
67 | describe('level: info', function () {
68 | beforeEach(function () {
69 | logger.setLevel('info')
70 | })
71 |
72 | it('should log .log() messages', function () {
73 | expect(logMessage).to.equal('log')
74 | })
75 | it('should not log .debug() messages', function () {
76 | expect(debugMessage).to.equal(undefined)
77 | })
78 | it('should log .info() messages', function () {
79 | expect(infoMessage).to.equal('info')
80 | })
81 | it('should log .warn() messages', function () {
82 | expect(warnMessage).to.equal('warn')
83 | })
84 | it('should log .error() messages', function () {
85 | expect(errorMessage).to.equal('error')
86 | })
87 | })
88 |
89 | describe('level: warn', function () {
90 | beforeEach(function () {
91 | logger.setLevel('warn')
92 | })
93 |
94 | it('should log .log() messages', function () {
95 | expect(logMessage).to.equal('log')
96 | })
97 | it('should not log .debug() messages', function () {
98 | expect(debugMessage).to.equal(undefined)
99 | })
100 | it('should not log .info() messages', function () {
101 | expect(infoMessage).to.equal(undefined)
102 | })
103 | it('should log .warn() messages', function () {
104 | expect(warnMessage).to.equal('warn')
105 | })
106 | it('should log .error() messages', function () {
107 | expect(errorMessage).to.equal('error')
108 | })
109 | })
110 |
111 | describe('level: error', function () {
112 | beforeEach(function () {
113 | logger.setLevel('error')
114 | })
115 |
116 | it('should log .log() messages', function () {
117 | expect(logMessage).to.equal('log')
118 | })
119 | it('should not log .debug() messages', function () {
120 | expect(debugMessage).to.equal(undefined)
121 | })
122 | it('should not log .info() messages', function () {
123 | expect(infoMessage).to.equal(undefined)
124 | })
125 | it('should log .warn() messages', function () {
126 | expect(warnMessage).to.equal(undefined)
127 | })
128 | it('should log .error() messages', function () {
129 | expect(errorMessage).to.equal('error')
130 | })
131 | })
132 |
133 | describe('level: silent', function () {
134 | beforeEach(function () {
135 | logger.setLevel('silent')
136 | })
137 |
138 | it('should log .log() messages', function () {
139 | expect(logMessage).to.equal('log')
140 | })
141 | it('should not log .debug() messages', function () {
142 | expect(debugMessage).to.equal(undefined)
143 | })
144 | it('should not log .info() messages', function () {
145 | expect(infoMessage).to.equal(undefined)
146 | })
147 | it('should not log .warn() messages', function () {
148 | expect(warnMessage).to.equal(undefined)
149 | })
150 | it('should not log .error() messages', function () {
151 | expect(errorMessage).to.equal(undefined)
152 | })
153 | })
154 |
155 | describe('Interpolation', function () {
156 | // make sure all messages are logged
157 | beforeEach(function () {
158 | logger.setLevel('debug')
159 | })
160 |
161 | beforeEach(function () {
162 | logger.log('log %s %s', 123, 456)
163 | logger.debug('debug %s %s', 123, 456)
164 | logger.info('info %s %s', 123, 456)
165 | logger.warn('warn %s %s', 123, 456)
166 | logger.error('error %s %s', 123, 456)
167 | })
168 |
169 | it('should interpolate .log() messages', function () {
170 | expect(logMessage).to.equal('log 123 456')
171 | })
172 | it('should interpolate .debug() messages', function () {
173 | expect(debugMessage).to.equal('debug 123 456')
174 | })
175 | it('should interpolate .info() messages', function () {
176 | expect(infoMessage).to.equal('info 123 456')
177 | })
178 | it('should interpolate .warn() messages', function () {
179 | expect(warnMessage).to.equal('warn 123 456')
180 | })
181 | it('should interpolate .error() messages', function () {
182 | expect(errorMessage).to.equal('error 123 456')
183 | })
184 | })
185 | })
186 |
187 | describe('Erroneous usage.', function () {
188 | var fn
189 |
190 | describe('Log provider is not a function', function () {
191 | beforeEach(function () {
192 | fn = function () {
193 | logger.setProvider({})
194 | }
195 | })
196 |
197 | it('should throw an error', function () {
198 | expect(fn).to.throw(Error)
199 | })
200 | })
201 |
202 | describe('Invalid logLevel', function () {
203 | beforeEach(function () {
204 | fn = function () {
205 | logger.setLevel('foo')
206 | }
207 | })
208 |
209 | it('should throw an error', function () {
210 | expect(fn).to.throw(Error)
211 | })
212 | })
213 | })
214 | })
215 |
216 | describe('getArrow', function () {
217 | var arrow
218 | // scenario = [originalPath, newPath, originalTarget, newTarget]
219 |
220 | describe('default arrow', function () {
221 | beforeEach(function () {
222 | arrow = getArrow('/api', '/api', 'localhost:1337', 'localhost:1337')
223 | })
224 |
225 | it('should return arrow: "->"', function () {
226 | expect(arrow).to.equal('->')
227 | })
228 | })
229 |
230 | describe('"pathRewrite" arrow', function () {
231 | beforeEach(function () {
232 | arrow = getArrow('/api', '/rest', 'localhost:1337', 'localhost:1337')
233 | })
234 |
235 | it('should return arrow: "~>"', function () {
236 | expect(arrow).to.equal('~>')
237 | })
238 | })
239 |
240 | describe('"router" arrow', function () {
241 | beforeEach(function () {
242 | arrow = getArrow('/api', '/api', 'localhost:1337', 'localhost:8888')
243 | })
244 |
245 | it('should return arrow: "=>"', function () {
246 | expect(arrow).to.equal('=>')
247 | })
248 | })
249 |
250 | describe('"pathRewrite" + "router" arrow', function () {
251 | beforeEach(function () {
252 | arrow = getArrow('/api', '/rest', 'localhost:1337', 'localhost:8888')
253 | })
254 |
255 | it('should return arrow: "≈>"', function () {
256 | expect(arrow).to.equal('≈>')
257 | })
258 | })
259 | })
260 |
--------------------------------------------------------------------------------
/test/unit/context-matcher.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | // https://github.com/feross/standard/issues/690#issuecomment-278533482
3 |
4 | var expect = require('chai').expect
5 | var contextMatcher = require('./_libs').contextMatcher
6 |
7 | describe('Context Matching', function () {
8 | describe('String path matching', function () {
9 | var result
10 |
11 | describe('Single path matching', function () {
12 | it('should match all paths', function () {
13 | result = contextMatcher.match('', 'http://localhost/api/foo/bar')
14 | expect(result).to.be.true
15 | })
16 |
17 | it('should match all paths starting with forward-slash', function () {
18 | result = contextMatcher.match('/', 'http://localhost/api/foo/bar')
19 | expect(result).to.be.true
20 | })
21 |
22 | it('should return true when the context is present in url', function () {
23 | result = contextMatcher.match('/api', 'http://localhost/api/foo/bar')
24 | expect(result).to.be.true
25 | })
26 |
27 | it('should return false when the context is not present in url', function () {
28 | result = contextMatcher.match('/abc', 'http://localhost/api/foo/bar')
29 | expect(result).to.be.false
30 | })
31 |
32 | it('should return false when the context is present half way in url', function () {
33 | result = contextMatcher.match('/foo', 'http://localhost/api/foo/bar')
34 | expect(result).to.be.false
35 | })
36 |
37 | it('should return false when the context does not start with /', function () {
38 | result = contextMatcher.match('api', 'http://localhost/api/foo/bar')
39 | expect(result).to.be.false
40 | })
41 | })
42 |
43 | describe('Multi path matching', function () {
44 | it('should return true when the context is present in url', function () {
45 | result = contextMatcher.match(['/api'], 'http://localhost/api/foo/bar')
46 | expect(result).to.be.true
47 | })
48 |
49 | it('should return true when the context is present in url', function () {
50 | result = contextMatcher.match(['/api', '/ajax'], 'http://localhost/ajax/foo/bar')
51 | expect(result).to.be.true
52 | })
53 |
54 | it('should return false when the context does not match url', function () {
55 | result = contextMatcher.match(['/api', '/ajax'], 'http://localhost/foo/bar')
56 | expect(result).to.be.false
57 | })
58 |
59 | it('should return false when empty array provided', function () {
60 | result = contextMatcher.match([], 'http://localhost/api/foo/bar')
61 | expect(result).to.be.false
62 | })
63 | })
64 | })
65 |
66 | describe('Wildcard path matching', function () {
67 | describe('Single glob', function () {
68 | var url
69 |
70 | beforeEach(function () {
71 | url = 'http://localhost/api/foo/bar.html'
72 | })
73 |
74 | describe('url-path matching', function () {
75 | it('should match any path', function () {
76 | expect(contextMatcher.match('**', url)).to.be.true
77 | expect(contextMatcher.match('/**', url)).to.be.true
78 | })
79 |
80 | it('should only match paths starting with "/api" ', function () {
81 | expect(contextMatcher.match('/api/**', url)).to.be.true
82 | expect(contextMatcher.match('/ajax/**', url)).to.be.false
83 | })
84 |
85 | it('should only match paths starting with "foo" folder in it ', function () {
86 | expect(contextMatcher.match('**/foo/**', url)).to.be.true
87 | expect(contextMatcher.match('**/invalid/**', url)).to.be.false
88 | })
89 | })
90 |
91 | describe('file matching', function () {
92 | it('should match any path, file and extension', function () {
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 | expect(contextMatcher.match('/**/*.*', url)).to.be.true
99 | })
100 |
101 | it('should only match .html files', function () {
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 | describe('Multiple patterns', function () {
143 | it('should return true when both path patterns match', function () {
144 | var pattern = ['/api/**', '/ajax/**']
145 | expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.json')).to.be.true
146 | expect(contextMatcher.match(pattern, 'http://localhost/ajax/foo/bar.json')).to.be.true
147 | expect(contextMatcher.match(pattern, 'http://localhost/rest/foo/bar.json')).to.be.false
148 | })
149 | it('should return true when both file extensions pattern match', function () {
150 | var pattern = ['/**/*.html', '/**/*.jpeg']
151 | expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.html')).to.be.true
152 | expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.jpeg')).to.be.true
153 | expect(contextMatcher.match(pattern, 'http://localhost/api/foo/bar.gif')).to.be.false
154 | })
155 | })
156 |
157 | describe('Negation patterns', function () {
158 | it('should not match file extension', function () {
159 | var url = 'http://localhost/api/foo/bar.html'
160 | expect(contextMatcher.match(['**', '!**/*.html'], url)).to.be.false
161 | expect(contextMatcher.match(['**', '!**/*.json'], url)).to.be.true
162 | })
163 | })
164 | })
165 | })
166 |
167 | describe('Use function for matching', function () {
168 | var testFunctionAsContext = function (val) {
169 | return contextMatcher.match(fn, 'http://localhost/api/foo/bar')
170 |
171 | function fn (path, req) {
172 | return val
173 | }
174 | }
175 |
176 | describe('truthy', function () {
177 | it('should match when function returns true', function () {
178 | expect(testFunctionAsContext(true)).to.be.ok
179 | expect(testFunctionAsContext('true')).to.be.ok
180 | })
181 | })
182 |
183 | describe('falsy', function () {
184 | it('should not match when function returns falsy value', function () {
185 | expect(testFunctionAsContext()).to.not.be.ok
186 | expect(testFunctionAsContext(undefined)).to.not.be.ok
187 | expect(testFunctionAsContext(false)).to.not.be.ok
188 | expect(testFunctionAsContext('')).to.not.be.ok
189 | })
190 | })
191 | })
192 |
193 | describe('Test invalid contexts', function () {
194 | var testContext
195 |
196 | beforeEach(function () {
197 | testContext = function (context) {
198 | return function () {
199 | contextMatcher.match(context, 'http://localhost/api/foo/bar')
200 | }
201 | }
202 | })
203 |
204 | describe('Throw error', function () {
205 | it('should throw error with undefined', function () {
206 | expect(testContext(undefined)).to.throw(Error)
207 | })
208 |
209 | it('should throw error with null', function () {
210 | expect(testContext(null)).to.throw(Error)
211 | })
212 |
213 | it('should throw error with object literal', function () {
214 | expect(testContext({})).to.throw(Error)
215 | })
216 |
217 | it('should throw error with integers', function () {
218 | expect(testContext(123)).to.throw(Error)
219 | })
220 |
221 | it('should throw error with mixed string and glob pattern', function () {
222 | expect(testContext(['/api', '!*.html'])).to.throw(Error)
223 | })
224 | })
225 |
226 | describe('Do not throw error', function () {
227 | it('should not throw error with string', function () {
228 | expect(testContext('/123')).not.to.throw(Error)
229 | })
230 |
231 | it('should not throw error with Array', function () {
232 | expect(testContext(['/123'])).not.to.throw(Error)
233 | })
234 | it('should not throw error with glob', function () {
235 | expect(testContext('/**')).not.to.throw(Error)
236 | })
237 |
238 | it('should not throw error with Array of globs', function () {
239 | expect(testContext(['/**', '!*.html'])).not.to.throw(Error)
240 | })
241 |
242 | it('should not throw error with Function', function () {
243 | expect(testContext(function () {})).not.to.throw(Error)
244 | })
245 | })
246 | })
247 | })
248 |
--------------------------------------------------------------------------------
/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 | [](https://standardjs.com)
8 |
9 | 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).
10 |
11 | Powered by the popular Nodejitsu [`http-proxy`](https://github.com/nodejitsu/node-http-proxy). [](https://github.com/nodejitsu/node-http-proxy)
12 |
13 | ## TL;DR
14 |
15 | Proxy `/api` requests to `http://www.example.org`
16 |
17 | ```javascript
18 | var express = require('express');
19 | var proxy = require('http-proxy-middleware');
20 |
21 | var app = express();
22 |
23 | app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
24 | app.listen(3000);
25 |
26 | // http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
27 | ```
28 |
29 | _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#options) can be used, along with some extra `http-proxy-middleware` [options](#options).
30 |
31 | :bulb: **Tip:** Set the option `changeOrigin` to `true` for [name-based virtual hosted sites](http://en.wikipedia.org/wiki/Virtual_hosting#Name-based).
32 |
33 | ## Table of Contents
34 |
35 |
36 |
37 | - [Install](#install)
38 | - [Core concept](#core-concept)
39 | - [Example](#example)
40 | - [Context matching](#context-matching)
41 | - [Options](#options)
42 | - [http-proxy-middleware options](#http-proxy-middleware-options)
43 | - [http-proxy events](#http-proxy-events)
44 | - [http-proxy options](#http-proxy-options)
45 | - [Shorthand](#shorthand)
46 | - [app.use\(path, proxy\)](#appusepath-proxy)
47 | - [WebSocket](#websocket)
48 | - [External WebSocket upgrade](#external-websocket-upgrade)
49 | - [Working examples](#working-examples)
50 | - [Recipes](#recipes)
51 | - [Compatible servers](#compatible-servers)
52 | - [Tests](#tests)
53 | - [Changelog](#changelog)
54 | - [License](#license)
55 |
56 |
57 |
58 |
59 | ## Install
60 |
61 | ```javascript
62 | $ npm install --save-dev http-proxy-middleware
63 | ```
64 |
65 | ## Core concept
66 |
67 | Proxy middleware configuration.
68 |
69 | #### proxy([context,] config)
70 |
71 | ```javascript
72 | var proxy = require('http-proxy-middleware');
73 |
74 | var apiProxy = proxy('/api', {target: 'http://www.example.org'});
75 | // \____/ \_____________________________/
76 | // | |
77 | // context options
78 |
79 | // 'apiProxy' is now ready to be used as middleware in a server.
80 | ```
81 | * **context**: Determine which requests should be proxied to the target host.
82 | (more on [context matching](#context-matching))
83 | * **options.target**: target host to proxy to. _(protocol + host)_
84 |
85 | (full list of [`http-proxy-middleware` configuration options](#options))
86 |
87 | #### proxy(uri [, config])
88 |
89 | ``` javascript
90 | // shorthand syntax for the example above:
91 | var apiProxy = proxy('http://www.example.org/api');
92 |
93 | ```
94 | More about the [shorthand configuration](#shorthand).
95 |
96 | ## Example
97 |
98 | An example with `express` server.
99 |
100 | ```javascript
101 | // include dependencies
102 | var express = require('express');
103 | var proxy = require('http-proxy-middleware');
104 |
105 | // proxy middleware options
106 | var options = {
107 | target: 'http://www.example.org', // target host
108 | changeOrigin: true, // needed for virtual hosted sites
109 | ws: true, // proxy websockets
110 | pathRewrite: {
111 | '^/api/old-path' : '/api/new-path', // rewrite path
112 | '^/api/remove/path' : '/path' // remove base path
113 | },
114 | router: {
115 | // when request.headers.host == 'dev.localhost:3000',
116 | // override target 'http://www.example.org' to 'http://localhost:8000'
117 | 'dev.localhost:3000' : 'http://localhost:8000'
118 | }
119 | };
120 |
121 | // create the proxy (without context)
122 | var exampleProxy = proxy(options);
123 |
124 | // mount `exampleProxy` in web server
125 | var app = express();
126 | app.use('/api', exampleProxy);
127 | app.listen(3000);
128 | ```
129 |
130 | ## Context matching
131 |
132 | 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.
133 |
134 | [RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is used for context matching.
135 |
136 | ```
137 | foo://example.com:8042/over/there?name=ferret#nose
138 | \_/ \______________/\_________/ \_________/ \__/
139 | | | | | |
140 | scheme authority path query fragment
141 | ```
142 |
143 | * **path matching**
144 | - `proxy({...})` - matches any path, all requests will be proxied.
145 | - `proxy('/', {...})` - matches any path, all requests will be proxied.
146 | - `proxy('/api', {...})` - matches paths starting with `/api`
147 |
148 | * **multiple path matching**
149 | - `proxy(['/api', '/ajax', '/someotherpath'], {...})`
150 |
151 | * **wildcard path matching**
152 |
153 | 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.
154 | - `proxy('**', {...})` matches any path, all requests will be proxied.
155 | - `proxy('**/*.html', {...})` matches any path which ends with `.html`
156 | - `proxy('/*.html', {...})` matches paths directly under path-absolute
157 | - `proxy('/api/**/*.html', {...})` matches requests ending with `.html` in the path of `/api`
158 | - `proxy(['/api/**', '/ajax/**'], {...})` combine multiple patterns
159 | - `proxy(['/api/**', '!**/bad.json'], {...})` exclusion
160 |
161 | * **custom matching**
162 |
163 | For full control you can provide a custom function to determine which requests should be proxied or not.
164 | ```javascript
165 | /**
166 | * @return {Boolean}
167 | */
168 | var filter = function (pathname, req) {
169 | return (pathname.match('^/api') && req.method === 'GET');
170 | };
171 |
172 | var apiProxy = proxy(filter, {target: 'http://www.example.org'})
173 | ```
174 |
175 | ## Options
176 |
177 | ### http-proxy-middleware options
178 |
179 | * **option.pathRewrite**: object/function, rewrite target's url path. Object-keys will be used as _RegExp_ to match paths.
180 | ```javascript
181 | // rewrite path
182 | pathRewrite: {'^/old/api' : '/new/api'}
183 |
184 | // remove path
185 | pathRewrite: {'^/remove/api' : ''}
186 |
187 | // add base path
188 | pathRewrite: {'^/' : '/basepath/'}
189 |
190 | // custom rewriting
191 | pathRewrite: function (path, req) { return path.replace('/api', '/base/api') }
192 | ```
193 |
194 | * **option.router**: object/function, re-target `option.target` for specific requests.
195 | ```javascript
196 | // Use `host` and/or `path` to match requests. First match will be used.
197 | // The order of the configuration matters.
198 | router: {
199 | 'integration.localhost:3000' : 'http://localhost:8001', // host only
200 | 'staging.localhost:3000' : 'http://localhost:8002', // host only
201 | 'localhost:3000/api' : 'http://localhost:8003', // host + path
202 | '/rest' : 'http://localhost:8004' // path only
203 | }
204 |
205 | // Custom router function
206 | router: function(req) {
207 | return 'http://localhost:8004';
208 | }
209 | ```
210 |
211 | * **option.logLevel**: string, ['debug', 'info', 'warn', 'error', 'silent']. Default: `'info'`
212 |
213 | * **option.logProvider**: function, modify or replace log provider. Default: `console`.
214 | ```javascript
215 | // simple replace
216 | function logProvider(provider) {
217 | // replace the default console log provider.
218 | return require('winston');
219 | }
220 | ```
221 |
222 | ```javascript
223 | // verbose replacement
224 | function logProvider(provider) {
225 | var logger = new (require('winston').Logger)();
226 |
227 | var myCustomProvider = {
228 | log: logger.log,
229 | debug: logger.debug,
230 | info: logger.info,
231 | warn: logger.warn,
232 | error: logger.error
233 | }
234 | return myCustomProvider;
235 | }
236 | ```
237 | * (DEPRECATED) **option.proxyHost**: Use `option.changeOrigin = true` instead.
238 | * (DEPRECATED) **option.proxyTable**: Use `option.router` instead.
239 |
240 |
241 | ### http-proxy events
242 |
243 | Subscribe to [http-proxy events](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events):
244 |
245 | * **option.onError**: function, subscribe to http-proxy's `error` event for custom error handling.
246 | ```javascript
247 | function onError(err, req, res) {
248 | res.writeHead(500, {
249 | 'Content-Type': 'text/plain'
250 | });
251 | res.end('Something went wrong. And we are reporting a custom error message.');
252 | }
253 | ```
254 |
255 | * **option.onProxyRes**: function, subscribe to http-proxy's `proxyRes` event.
256 | ```javascript
257 | function onProxyRes(proxyRes, req, res) {
258 | proxyRes.headers['x-added'] = 'foobar'; // add new header to response
259 | delete proxyRes.headers['x-removed']; // remove header from response
260 | }
261 | ```
262 |
263 | * **option.onProxyReq**: function, subscribe to http-proxy's `proxyReq` event.
264 | ```javascript
265 | function onProxyReq(proxyReq, req, res) {
266 | // add custom header to request
267 | proxyReq.setHeader('x-added', 'foobar');
268 | // or log the req
269 | }
270 | ```
271 |
272 | * **option.onProxyReqWs**: function, subscribe to http-proxy's `proxyReqWs` event.
273 | ```javascript
274 | function onProxyReqWs(proxyReq, req, socket, options, head) {
275 | // add custom header
276 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');
277 | }
278 | ```
279 |
280 | * **option.onOpen**: function, subscribe to http-proxy's `open` event.
281 | ```javascript
282 | function onOpen(proxySocket) {
283 | // listen for messages coming FROM the target here
284 | proxySocket.on('data', hybiParseAndLogMessage);
285 | }
286 | ```
287 |
288 | * **option.onClose**: function, subscribe to http-proxy's `close` event.
289 | ```javascript
290 | function onClose(res, socket, head) {
291 | // view disconnected websocket connections
292 | console.log('Client disconnected');
293 | }
294 | ```
295 |
296 | ### http-proxy options
297 |
298 | The following options are provided by the underlying [http-proxy](https://github.com/nodejitsu/node-http-proxy#options) library.
299 |
300 | * **option.target**: url string to be parsed with the url module
301 | * **option.forward**: url string to be parsed with the url module
302 | * **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)
303 | * **option.ssl**: object to be passed to https.createServer()
304 | * **option.ws**: true/false: if you want to proxy websockets
305 | * **option.xfwd**: true/false, adds x-forward headers
306 | * **option.secure**: true/false, if you want to verify the SSL Certs
307 | * **option.toProxy**: true/false, passes the absolute URL as the `path` (useful for proxying to proxies)
308 | * **option.prependPath**: true/false, Default: true - specify whether you want to prepend the target's path to the proxy path
309 | * **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).
310 | * **option.localAddress** : Local interface string to bind for outgoing connections
311 | * **option.changeOrigin**: true/false, Default: false - changes the origin of the host header to the target URL
312 | * **option.auth** : Basic authentication i.e. 'user:password' to compute an Authorization header.
313 | * **option.hostRewrite**: rewrites the location hostname on (301/302/307/308) redirects.
314 | * **option.autoRewrite**: rewrites the location host/port on (301/302/307/308) redirects based on requested host/port. Default: false.
315 | * **option.protocolRewrite**: rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null.
316 | * **option.cookieDomainRewrite**: rewrites domain of `set-cookie` headers. Possible values:
317 | * `false` (default): disable cookie rewriting
318 | * String: new domain, for example `cookieDomainRewrite: "new.domain"`. To remove the domain, use `cookieDomainRewrite: ""`.
319 | * Object: mapping of domains to new domains, use `"*"` to match all domains.
320 | For example keep one domain unchanged, rewrite one domain and remove other domains:
321 | ```
322 | cookieDomainRewrite: {
323 | "unchanged.domain": "unchanged.domain",
324 | "old.domain": "new.domain",
325 | "*": ""
326 | }
327 | ```
328 | * **option.headers**: object, adds [request headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields). (Example: `{host:'www.example.org'}`)
329 | * **option.proxyTimeout**: timeout (in millis) when proxy receives no response from target
330 |
331 |
332 |
333 | ## Shorthand
334 |
335 | 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.
336 |
337 | ```javascript
338 | proxy('http://www.example.org:8000/api');
339 | // proxy('/api', {target: 'http://www.example.org:8000'});
340 |
341 |
342 | proxy('http://www.example.org:8000/api/books/*/**.json');
343 | // proxy('/api/books/*/**.json', {target: 'http://www.example.org:8000'});
344 |
345 |
346 | proxy('http://www.example.org:8000/api', {changeOrigin:true});
347 | // proxy('/api', {target: 'http://www.example.org:8000', changeOrigin: true});
348 | ```
349 |
350 | ### app.use(path, proxy)
351 |
352 | If you want to use the server's `app.use` `path` parameter to match requests;
353 | Create and mount the proxy without the http-proxy-middleware `context` parameter:
354 | ```javascript
355 | app.use('/api', proxy({target:'http://www.example.org', changeOrigin:true}));
356 | ```
357 |
358 | `app.use` documentation:
359 | * express: http://expressjs.com/en/4x/api.html#app.use
360 | * connect: https://github.com/senchalabs/connect#mount-middleware
361 |
362 | ## WebSocket
363 |
364 | ```javascript
365 | // verbose api
366 | proxy('/', {target:'http://echo.websocket.org', ws:true});
367 |
368 | // shorthand
369 | proxy('http://echo.websocket.org', {ws:true});
370 |
371 | // shorter shorthand
372 | proxy('ws://echo.websocket.org');
373 | ```
374 |
375 | ### External WebSocket upgrade
376 |
377 | 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.
378 | ```javascript
379 | var wsProxy = proxy('ws://echo.websocket.org', {changeOrigin:true});
380 |
381 | var app = express();
382 | app.use(wsProxy);
383 |
384 | var server = app.listen(3000);
385 | server.on('upgrade', wsProxy.upgrade); // <-- subscribe to http 'upgrade'
386 | ```
387 |
388 |
389 | ## Working examples
390 |
391 | View and play around with [working examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples).
392 |
393 | * Browser-Sync ([example source](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/browser-sync/index.js))
394 | * express ([example source](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/express/index.js))
395 | * connect ([example source](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/connect/index.js))
396 | * WebSocket ([example source](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/websocket/index.js))
397 |
398 | ## Recipes
399 |
400 | View the [recipes](https://github.com/chimurai/http-proxy-middleware/tree/master/recipes) for common use cases.
401 |
402 | ## Compatible servers
403 |
404 | `http-proxy-middleware` is compatible with the following servers:
405 | * [connect](https://www.npmjs.com/package/connect)
406 | * [express](https://www.npmjs.com/package/express)
407 | * [browser-sync](https://www.npmjs.com/package/browser-sync)
408 | * [lite-server](https://www.npmjs.com/package/lite-server)
409 | * [grunt-contrib-connect](https://www.npmjs.com/package/grunt-contrib-connect)
410 | * [grunt-browser-sync](https://www.npmjs.com/package/grunt-browser-sync)
411 | * [gulp-connect](https://www.npmjs.com/package/gulp-connect)
412 | * [gulp-webserver](https://www.npmjs.com/package/gulp-webserver)
413 |
414 | Sample implementations can be found in the [server recipes](https://github.com/chimurai/http-proxy-middleware/tree/master/recipes/servers.md).
415 |
416 | ## Tests
417 |
418 | Run the test suite:
419 |
420 | ```bash
421 | # install dependencies
422 | $ npm install
423 |
424 | # linting
425 | $ npm run lint
426 |
427 | # unit tests
428 | $ npm test
429 |
430 | # code coverage
431 | $ npm run cover
432 | ```
433 |
434 | ## Changelog
435 |
436 | - [View changelog](https://github.com/chimurai/http-proxy-middleware/blob/master/CHANGELOG.md)
437 |
438 |
439 | ## License
440 |
441 | The MIT License (MIT)
442 |
443 | Copyright (c) 2015-2017 Steven Chim
444 |
--------------------------------------------------------------------------------
/test/e2e/http-proxy-middleware.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | // https://github.com/feross/standard/issues/690#issuecomment-278533482
3 |
4 | var utils = require('./_utils')
5 | var expect = require('chai').expect
6 | var http = require('http')
7 |
8 | describe('E2E http-proxy-middleware', function () {
9 | var createServer
10 | var proxyMiddleware
11 |
12 | beforeEach(function () {
13 | createServer = utils.createServer
14 | proxyMiddleware = utils.proxyMiddleware
15 | })
16 |
17 | describe('http-proxy-middleware creation', function () {
18 | it('should create a middleware', function () {
19 | var middleware
20 | middleware = proxyMiddleware('/api', {target: 'http://localhost:8000'})
21 | expect(middleware).to.be.a('function')
22 | })
23 | })
24 |
25 | describe('context matching', function () {
26 | describe('do not proxy', function () {
27 | var isSkipped
28 |
29 | beforeEach(function () {
30 | isSkipped = false
31 |
32 | var middleware
33 |
34 | var mockReq = {url: '/foo/bar', originalUrl: '/foo/bar'}
35 | var mockRes = {}
36 | var mockNext = function () {
37 | // mockNext will be called when request is not proxied
38 | isSkipped = true
39 | }
40 |
41 | middleware = proxyMiddleware('/api', {target: 'http://localhost:8000'})
42 | middleware(mockReq, mockRes, mockNext)
43 | })
44 |
45 | it('should not proxy requests when request url does not match context', function () {
46 | expect(isSkipped).to.be.true
47 | })
48 | })
49 | })
50 |
51 | describe('http-proxy-middleware in actual server', function () {
52 | describe('basic setup, requests to target', function () {
53 | var proxyServer, targetServer
54 | var targetHeaders
55 | var targetUrl
56 | var responseBody
57 |
58 | beforeEach(function (done) {
59 | var mwProxy = proxyMiddleware('/api', {target: 'http://localhost:8000'})
60 |
61 | var mwTarget = function (req, res, next) {
62 | targetUrl = req.url // store target url.
63 | targetHeaders = req.headers // store target headers.
64 | res.write('HELLO WEB') // respond with 'HELLO WEB'
65 | res.end()
66 | }
67 |
68 | proxyServer = createServer(3000, mwProxy)
69 | targetServer = createServer(8000, mwTarget)
70 |
71 | http.get('http://localhost:3000/api/b/c/dp?q=1&r=[2,3]#s"', function (res) {
72 | res.on('data', function (chunk) {
73 | responseBody = chunk.toString()
74 | done()
75 | })
76 | })
77 | })
78 |
79 | afterEach(function () {
80 | proxyServer.close()
81 | targetServer.close()
82 | })
83 |
84 | it('should have the same headers.host value', function () {
85 | expect(targetHeaders.host).to.equal('localhost:3000')
86 | })
87 |
88 | it('should have proxied the uri-path and uri-query, but not the uri-hash', function () {
89 | expect(targetUrl).to.equal('/api/b/c/dp?q=1&r=[2,3]')
90 | })
91 |
92 | it('should have response body: "HELLO WEB"', function () {
93 | expect(responseBody).to.equal('HELLO WEB')
94 | })
95 | })
96 |
97 | describe('custom context matcher/filter', function () {
98 | var proxyServer, targetServer
99 | var responseBody
100 |
101 | var filterPath, filterReq
102 |
103 | beforeEach(function (done) {
104 | var filter = function (path, req) {
105 | filterPath = path
106 | filterReq = req
107 | return true
108 | }
109 |
110 | var mwProxy = proxyMiddleware(filter, {target: 'http://localhost:8000'})
111 |
112 | var mwTarget = function (req, res, next) {
113 | res.write('HELLO WEB') // respond with 'HELLO WEB'
114 | res.end()
115 | }
116 |
117 | proxyServer = createServer(3000, mwProxy)
118 | targetServer = createServer(8000, mwTarget)
119 |
120 | http.get('http://localhost:3000/api/b/c/d', function (res) {
121 | res.on('data', function (chunk) {
122 | responseBody = chunk.toString()
123 | done()
124 | })
125 | })
126 | })
127 |
128 | afterEach(function () {
129 | proxyServer.close()
130 | targetServer.close()
131 | })
132 |
133 | it('should have response body: "HELLO WEB"', function () {
134 | expect(responseBody).to.equal('HELLO WEB')
135 | })
136 |
137 | it('should provide the url path in the first argument', function () {
138 | expect(filterPath).to.equal('/api/b/c/d')
139 | })
140 |
141 | it('should provide the req object in the second argument', function () {
142 | expect(filterReq.method).to.equal('GET')
143 | })
144 | })
145 |
146 | describe('multi path', function () {
147 | var proxyServer, targetServer
148 | var response, responseBody
149 |
150 | beforeEach(function () {
151 | var mwProxy = proxyMiddleware(['/api', '/ajax'], {target: 'http://localhost:8000'})
152 |
153 | var mwTarget = function (req, res, next) {
154 | res.write(req.url) // respond with req.url
155 | res.end()
156 | }
157 |
158 | proxyServer = createServer(3000, mwProxy)
159 | targetServer = createServer(8000, mwTarget)
160 | })
161 |
162 | afterEach(function () {
163 | proxyServer.close()
164 | targetServer.close()
165 | })
166 |
167 | describe('request to path A, configured', function () {
168 | beforeEach(function (done) {
169 | http.get('http://localhost:3000/api/some/endpoint', function (res) {
170 | response = res
171 | res.on('data', function (chunk) {
172 | responseBody = chunk.toString()
173 | done()
174 | })
175 | })
176 | })
177 |
178 | it('should proxy to path A', function () {
179 | expect(response.statusCode).to.equal(200)
180 | expect(responseBody).to.equal('/api/some/endpoint')
181 | })
182 | })
183 |
184 | describe('request to path B, configured', function () {
185 | beforeEach(function (done) {
186 | http.get('http://localhost:3000/ajax/some/library', function (res) {
187 | response = res
188 | res.on('data', function (chunk) {
189 | responseBody = chunk.toString()
190 | done()
191 | })
192 | })
193 | })
194 |
195 | it('should proxy to path B', function () {
196 | expect(response.statusCode).to.equal(200)
197 | expect(responseBody).to.equal('/ajax/some/library')
198 | })
199 | })
200 |
201 | describe('request to path C, not configured', function () {
202 | beforeEach(function (done) {
203 | http.get('http://localhost:3000/lorum/ipsum', function (res) {
204 | response = res
205 | res.on('data', function (chunk) {
206 | responseBody = chunk.toString()
207 | done()
208 | })
209 | })
210 | })
211 |
212 | it('should not proxy to this path', function () {
213 | expect(response.statusCode).to.equal(404)
214 | })
215 | })
216 | })
217 |
218 | describe('wildcard path matching', function () {
219 | var proxyServer, targetServer
220 | var response, responseBody
221 |
222 | beforeEach(function () {
223 | var mwProxy = proxyMiddleware('/api/**', {target: 'http://localhost:8000'})
224 |
225 | var mwTarget = function (req, res, next) {
226 | res.write(req.url) // respond with req.url
227 | res.end()
228 | }
229 |
230 | proxyServer = createServer(3000, mwProxy)
231 | targetServer = createServer(8000, mwTarget)
232 | })
233 |
234 | beforeEach(function (done) {
235 | http.get('http://localhost:3000/api/some/endpoint', function (res) {
236 | response = res
237 | res.on('data', function (chunk) {
238 | responseBody = chunk.toString()
239 | done()
240 | })
241 | })
242 | })
243 |
244 | afterEach(function () {
245 | proxyServer.close()
246 | targetServer.close()
247 | })
248 |
249 | it('should proxy to path', function () {
250 | expect(response.statusCode).to.equal(200)
251 | expect(responseBody).to.equal('/api/some/endpoint')
252 | })
253 | })
254 |
255 | describe('multi glob wildcard path matching', function () {
256 | var proxyServer, targetServer
257 | var responseA, responseBodyA
258 | var responseB
259 |
260 | beforeEach(function () {
261 | var mwProxy = proxyMiddleware(['**/*.html', '!**.json'], {target: 'http://localhost:8000'})
262 |
263 | var mwTarget = function (req, res, next) {
264 | res.write(req.url) // respond with req.url
265 | res.end()
266 | }
267 |
268 | proxyServer = createServer(3000, mwProxy)
269 | targetServer = createServer(8000, mwTarget)
270 | })
271 |
272 | beforeEach(function (done) {
273 | http.get('http://localhost:3000/api/some/endpoint/index.html', function (res) {
274 | responseA = res
275 | res.on('data', function (chunk) {
276 | responseBodyA = chunk.toString()
277 | done()
278 | })
279 | })
280 | })
281 |
282 | beforeEach(function (done) {
283 | http.get('http://localhost:3000/api/some/endpoint/data.json', function (res) {
284 | responseB = res
285 | res.on('data', function (chunk) {
286 | done()
287 | })
288 | })
289 | })
290 |
291 | afterEach(function () {
292 | proxyServer.close()
293 | targetServer.close()
294 | })
295 |
296 | it('should proxy to paths ending with *.html', function () {
297 | expect(responseA.statusCode).to.equal(200)
298 | expect(responseBodyA).to.equal('/api/some/endpoint/index.html')
299 | })
300 |
301 | it('should not proxy to paths ending with *.json', function () {
302 | expect(responseB.statusCode).to.equal(404)
303 | })
304 | })
305 |
306 | describe('option.headers - additional request headers', function () {
307 | var proxyServer, targetServer
308 | var targetHeaders
309 |
310 | beforeEach(function (done) {
311 | var mwProxy = proxyMiddleware('/api', {target: 'http://localhost:8000', headers: {host: 'foobar.dev'}})
312 |
313 | var mwTarget = function (req, res, next) {
314 | targetHeaders = req.headers
315 | res.end()
316 | }
317 |
318 | proxyServer = createServer(3000, mwProxy)
319 | targetServer = createServer(8000, mwTarget)
320 |
321 | http.get('http://localhost:3000/api/', function (res) {
322 | done()
323 | })
324 | })
325 |
326 | afterEach(function () {
327 | proxyServer.close()
328 | targetServer.close()
329 | })
330 |
331 | it('should send request header "host" to target server', function () {
332 | expect(targetHeaders.host).to.equal('foobar.dev')
333 | })
334 | })
335 |
336 | describe('legacy option.proxyHost', function () {
337 | var proxyServer, targetServer
338 | var targetHeaders
339 |
340 | beforeEach(function (done) {
341 | var mwProxy = proxyMiddleware('/api', {target: 'http://localhost:8000', proxyHost: 'foobar.dev'})
342 |
343 | var mwTarget = function (req, res, next) {
344 | targetHeaders = req.headers
345 | res.end()
346 | }
347 |
348 | proxyServer = createServer(3000, mwProxy)
349 | targetServer = createServer(8000, mwTarget)
350 |
351 | http.get('http://localhost:3000/api/', function (res) {
352 | done()
353 | })
354 | })
355 |
356 | afterEach(function () {
357 | proxyServer.close()
358 | targetServer.close()
359 | })
360 |
361 | it('should proxy host header to target server', function () {
362 | expect(targetHeaders.host).to.equal('foobar.dev')
363 | })
364 | })
365 |
366 | describe('option.onError - Error handling', function () {
367 | var proxyServer, targetServer
368 | var response, responseBody
369 |
370 | describe('default', function () {
371 | beforeEach(function (done) {
372 | var mwProxy = proxyMiddleware('/api', {target: 'http://localhost:666'}) // unreachable host on port:666
373 | var mwTarget = function (req, res, next) { next() }
374 |
375 | proxyServer = createServer(3000, mwProxy)
376 | targetServer = createServer(8000, mwTarget)
377 |
378 | http.get('http://localhost:3000/api/', function (res) {
379 | response = res
380 | done()
381 | })
382 | })
383 |
384 | afterEach(function () {
385 | proxyServer.close()
386 | targetServer.close()
387 | })
388 |
389 | it('should handle errors when host is not reachable', function () {
390 | expect(response.statusCode).to.equal(504)
391 | })
392 | })
393 |
394 | describe('custom', function () {
395 | beforeEach(function (done) {
396 | var customOnError = function (err, req, res) {
397 | if (err) {
398 | res.writeHead(418) // different error code
399 | res.end('I\'m a teapot') // no response body
400 | }
401 | }
402 |
403 | var mwProxy = proxyMiddleware('/api', {target: 'http://localhost:666', onError: customOnError}) // unreachable host on port:666
404 | var mwTarget = function (req, res, next) { next() }
405 |
406 | proxyServer = createServer(3000, mwProxy)
407 | targetServer = createServer(8000, mwTarget)
408 |
409 | http.get('http://localhost:3000/api/', function (res) {
410 | response = res
411 | res.on('data', function (chunk) {
412 | responseBody = chunk.toString()
413 | done()
414 | })
415 | })
416 | })
417 |
418 | afterEach(function () {
419 | proxyServer.close()
420 | targetServer.close()
421 | })
422 |
423 | it('should respond with custom http status code', function () {
424 | expect(response.statusCode).to.equal(418)
425 | })
426 |
427 | it('should respond with custom status message', function () {
428 | expect(responseBody).to.equal('I\'m a teapot')
429 | })
430 | })
431 | })
432 |
433 | describe('option.onProxyRes', function () {
434 | var proxyServer, targetServer
435 | var response
436 |
437 | beforeEach(function (done) {
438 | var fnOnProxyRes = function (proxyRes, req, res) {
439 | proxyRes.headers['x-added'] = 'foobar' // add custom header to response
440 | delete proxyRes.headers['x-removed']
441 | }
442 |
443 | var mwProxy = proxyMiddleware('/api', {
444 | target: 'http://localhost:8000',
445 | onProxyRes: fnOnProxyRes
446 | })
447 | var mwTarget = function (req, res, next) {
448 | res.setHeader('x-removed', 'remove-header')
449 | res.write(req.url) // respond with req.url
450 | res.end()
451 | }
452 |
453 | proxyServer = createServer(3000, mwProxy)
454 | targetServer = createServer(8000, mwTarget)
455 |
456 | http.get('http://localhost:3000/api/foo/bar', function (res) {
457 | response = res
458 | res.on('data', function (chunk) {
459 | done()
460 | })
461 | })
462 | })
463 |
464 | afterEach(function () {
465 | proxyServer.close()
466 | targetServer.close()
467 | })
468 |
469 | it('should add `x-added` as custom header to response"', function () {
470 | expect(response.headers['x-added']).to.equal('foobar')
471 | })
472 |
473 | it('should remove `x-removed` field from response header"', function () {
474 | expect(response.headers['x-removed']).to.equal(undefined)
475 | })
476 | })
477 |
478 | describe('option.onProxyReq', function () {
479 | var proxyServer, targetServer
480 | var receivedRequest
481 |
482 | beforeEach(function (done) {
483 | var fnOnProxyReq = function (proxyReq, req, res) {
484 | proxyReq.setHeader('x-added', 'foobar') // add custom header to request
485 | }
486 |
487 | var mwProxy = proxyMiddleware('/api', {
488 | target: 'http://localhost:8000',
489 | onProxyReq: fnOnProxyReq
490 | })
491 |
492 | var mwTarget = function (req, res, next) {
493 | receivedRequest = req
494 | res.write(req.url) // respond with req.url
495 | res.end()
496 | }
497 |
498 | proxyServer = createServer(3000, mwProxy)
499 | targetServer = createServer(8000, mwTarget)
500 |
501 | http.get('http://localhost:3000/api/foo/bar', function () {
502 | done()
503 | })
504 | })
505 |
506 | afterEach(function () {
507 | proxyServer.close()
508 | targetServer.close()
509 | })
510 |
511 | it('should add `x-added` as custom header to request"', function () {
512 | expect(receivedRequest.headers['x-added']).to.equal('foobar')
513 | })
514 | })
515 |
516 | describe('option.pathRewrite', function () {
517 | var proxyServer, targetServer
518 | var responseBody
519 |
520 | beforeEach(function (done) {
521 | var mwProxy = proxyMiddleware('/api', {
522 | target: 'http://localhost:8000',
523 | pathRewrite: {
524 | '^/api': '/rest',
525 | '^/remove': ''
526 | }
527 | })
528 | var mwTarget = function (req, res, next) {
529 | res.write(req.url) // respond with req.url
530 | res.end()
531 | }
532 |
533 | proxyServer = createServer(3000, mwProxy)
534 | targetServer = createServer(8000, mwTarget)
535 |
536 | http.get('http://localhost:3000/api/foo/bar', function (res) {
537 | res.on('data', function (chunk) {
538 | responseBody = chunk.toString()
539 | done()
540 | })
541 | })
542 | })
543 |
544 | afterEach(function () {
545 | proxyServer.close()
546 | targetServer.close()
547 | })
548 |
549 | it('should have rewritten path from "/api/foo/bar" to "/rest/foo/bar"', function () {
550 | expect(responseBody).to.equal('/rest/foo/bar')
551 | })
552 | })
553 |
554 | describe('shorthand usage', function () {
555 | var proxyServer, targetServer
556 | var responseBody
557 |
558 | beforeEach(function (done) {
559 | var mwProxy = proxyMiddleware('http://localhost:8000/api')
560 | var mwTarget = function (req, res, next) {
561 | res.write(req.url) // respond with req.url
562 | res.end()
563 | }
564 |
565 | proxyServer = createServer(3000, mwProxy)
566 | targetServer = createServer(8000, mwTarget)
567 |
568 | http.get('http://localhost:3000/api/foo/bar', function (res) {
569 | res.on('data', function (chunk) {
570 | responseBody = chunk.toString()
571 | done()
572 | })
573 | })
574 | })
575 |
576 | afterEach(function () {
577 | proxyServer.close()
578 | targetServer.close()
579 | })
580 |
581 | it('should have proxy with shorthand configuration', function () {
582 | expect(responseBody).to.equal('/api/foo/bar')
583 | })
584 | })
585 |
586 | describe('express with path + proxy', function () {
587 | var proxyServer, targetServer
588 | var responseBody
589 |
590 | beforeEach(function (done) {
591 | var mwProxy = proxyMiddleware('http://localhost:8000')
592 | var mwTarget = function (req, res, next) {
593 | res.write(req.url) // respond with req.url
594 | res.end()
595 | }
596 |
597 | proxyServer = createServer(3000, mwProxy, '/api')
598 | targetServer = createServer(8000, mwTarget)
599 |
600 | http.get('http://localhost:3000/api/foo/bar', function (res) {
601 | res.on('data', function (chunk) {
602 | responseBody = chunk.toString()
603 | done()
604 | })
605 | })
606 | })
607 |
608 | afterEach(function () {
609 | proxyServer.close()
610 | targetServer.close()
611 | })
612 |
613 | it('should proxy to target with the baseUrl', function () {
614 | expect(responseBody).to.equal('/api/foo/bar')
615 | })
616 | })
617 |
618 | describe('option.logLevel & option.logProvider', function () {
619 | var proxyServer, targetServer
620 | var logMessage
621 |
622 | beforeEach(function (done) {
623 | var customLogger = function (message) {
624 | logMessage = message
625 | }
626 |
627 | var mwProxy = proxyMiddleware('http://localhost:8000/api', {
628 | logLevel: 'info',
629 | logProvider: function (provider) {
630 | provider.debug = customLogger
631 | provider.info = customLogger
632 | return provider
633 | }
634 | })
635 | var mwTarget = function (req, res, next) {
636 | res.write(req.url) // respond with req.url
637 | res.end()
638 | }
639 |
640 | proxyServer = createServer(3000, mwProxy)
641 | targetServer = createServer(8000, mwTarget)
642 |
643 | http.get('http://localhost:3000/api/foo/bar', function (res) {
644 | res.on('data', function (chunk) {
645 | done()
646 | })
647 | })
648 | })
649 |
650 | afterEach(function () {
651 | proxyServer.close()
652 | targetServer.close()
653 | })
654 |
655 | it('should have logged messages', function () {
656 | expect(logMessage).not.to.equal(undefined)
657 | })
658 | })
659 | })
660 | })
661 |
--------------------------------------------------------------------------------