├── index.js ├── .jscsrc ├── test ├── unit │ ├── _libs.js │ ├── handlers.spec.js │ ├── path-rewriter.spec.js │ ├── router.spec.js │ ├── config-factory.spec.js │ ├── logger.spec.js │ └── context-matcher.spec.js └── e2e │ ├── _utils.js │ ├── express-router.spec.js │ ├── path-rewriter.spec.js │ ├── router.spec.js │ ├── websocket.spec.js │ └── http-proxy-middleware.spec.js ├── .editorconfig ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── recipes ├── virtual-hosts.md ├── corporate-proxy.md ├── logLevel.md ├── basic.md ├── websocket.md ├── delay.md ├── shorthand.md ├── logProvider.md ├── pathRewrite.md ├── proxy-events.md ├── router.md ├── context-matching.md ├── modify-post.md ├── README.md └── servers.md ├── .gitignore ├── examples ├── express │ └── index.js ├── connect │ └── index.js ├── browser-sync │ └── index.js ├── README.md └── websocket │ ├── index.js │ └── index.html ├── LICENSE ├── lib ├── router.js ├── path-rewriter.js ├── handlers.js ├── context-matcher.js ├── config-factory.js ├── logger.js └── index.js ├── package.json ├── CONTRIBUTING.md ├── CHANGELOG.md └── README.md /index.js: -------------------------------------------------------------------------------- 1 | var HPM = require('./lib'); 2 | 3 | module.exports = function(context, opts) { 4 | return new HPM(context, opts); 5 | }; 6 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "validateIndentation": 4, 4 | "maximumLineLength": { 5 | "value": 120, 6 | "allExcept": ["comments", "regex"] 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /test/unit/_libs.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configFactory: require('../../lib/config-factory'), 3 | contextMatcher: require('../../lib/context-matcher'), 4 | handlers: require('../../lib/handlers'), 5 | Logger: require('../../lib/logger'), 6 | pathRewriter: require('../../lib/path-rewriter'), 7 | router: require('../../lib/router') 8 | }; 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 4 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | # Matches the exact files either package.json or .travis.yml 17 | [{package.json,.travis.yml}] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '6' 5 | - '5' 6 | - '4' 7 | - '0.12' 8 | - '0.11' 9 | - '0.10' 10 | before_install: 11 | # https://github.com/npm/npm/issues/11283 12 | - npm set progress=false 13 | after_success: npm run coveralls 14 | env: 15 | global: 16 | # COVERALLS_REPO_TOKEN= 17 | secure: "I8mDH0n2DQHAPvUFEV/ZNmrMNYTJxgywg8+P3yuCAWwQkZeXQi0DWG+6VUpOylaRhL/4kCdZK9gnJD2MfrqvNqVLDUqBK3tTmZVoyqqJEdE0UdEHcPncAPmxWrG98sDjqFN9r4nWeizHvyA1fNRQHRN56Zy+DcQWjgEhHJaYeNA=" 18 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | 3 | ### Actual behavior 4 | 5 | ### Setup 6 | 7 | * http-proxy-middleware: _version_ 8 | * server: _connect/express/browser-sync..._ + _version_ 9 | * other relevant modules 10 | 11 | #### proxy middleware configuration 12 | ```javascript 13 | var apiProxy = proxy('/api', {target:'http://www.example.org', changeOrigin:true}); 14 | ``` 15 | #### server mounting 16 | ```javascript 17 | var app = express(); 18 | 19 | app.use(apiProxy); 20 | app.listen(3000); 21 | ``` 22 | -------------------------------------------------------------------------------- /test/e2e/_utils.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var proxyMiddleware = require('../../index'); 3 | 4 | module.exports = { 5 | createServer: createServer, 6 | proxyMiddleware: proxyMiddleware 7 | }; 8 | 9 | function createServer(portNumber, middleware, path) { 10 | var app = express(); 11 | 12 | if (middleware, path) { 13 | app.use(path, middleware); 14 | } else if (middleware) { 15 | app.use(middleware); 16 | } 17 | 18 | var server = app.listen(portNumber); 19 | 20 | return server; 21 | } 22 | -------------------------------------------------------------------------------- /recipes/virtual-hosts.md: -------------------------------------------------------------------------------- 1 | # Name-based Virtual Hosts 2 | 3 | This example will create a basic proxy middleware for [virtual hosted sites](https://en.wikipedia.org/wiki/Virtual_hosting#Name-based). 4 | 5 | When `changeOrigin` is set to `true`; Host [HTTP header](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields) will be set to match target's host. 6 | 7 | The `changeOrigin` option is provided by [http-proxy](https://github.com/nodejitsu/node-http-proxy). 8 | 9 | ```javascript 10 | var proxy = require("http-proxy-middleware"); 11 | 12 | var options = { 13 | target: 'http://localhost:3000', 14 | changeOrigin:true 15 | }; 16 | 17 | var apiProxy = proxy('/api', options); 18 | ``` 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /recipes/corporate-proxy.md: -------------------------------------------------------------------------------- 1 | # Corporate Proxy Support 2 | 3 | This example will create a basic proxy middleware with corporate proxy support. 4 | 5 | Provide a custom `http.agent` with [https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) to connect to the corporate proxy server. 6 | 7 | ```javascript 8 | var HttpsProxyAgent = require('https-proxy-agent'); 9 | var proxy = require("http-proxy-middleware"); 10 | 11 | // corporate proxy to connect to 12 | var proxyServer = process.env.HTTPS_PROXY || 13 | process.env.HTTP_PROXY; 14 | 15 | var options = { 16 | target: 'http://localhost:3000', 17 | agent: new HttpsProxyAgent(proxyServer) 18 | }; 19 | 20 | var apiProxy = proxy('/api', options); 21 | ``` 22 | -------------------------------------------------------------------------------- /recipes/logLevel.md: -------------------------------------------------------------------------------- 1 | # Log Level 2 | 3 | Control the amount of logging of http-proxy-middleware. 4 | 5 | Possible values: 6 | * `debug` 7 | * `info` 8 | * `warn` (default) 9 | * `error` 10 | * `silent` 11 | 12 | ## Level: debug 13 | 14 | Log everyting. 15 | 16 | ```javascript 17 | var proxy = require("http-proxy-middleware"); 18 | 19 | var options = { 20 | target: 'http://localhost:3000', 21 | logLevel: 'debug' 22 | }; 23 | 24 | var apiProxy = proxy('/api', options); 25 | ``` 26 | 27 | ## Level: silent 28 | 29 | Suppress all logging. 30 | 31 | ```javascript 32 | var proxy = require("http-proxy-middleware"); 33 | 34 | var options = { 35 | target: 'http://localhost:3000', 36 | logLevel: 'silent' 37 | }; 38 | 39 | var apiProxy = proxy('/api', options); 40 | ``` 41 | -------------------------------------------------------------------------------- /examples/express/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var express = require('express'); 5 | var proxy = require('../../index'); // require('http-proxy-middleware'); 6 | 7 | /** 8 | * Configure proxy middleware 9 | */ 10 | var jsonPlaceholderProxy = proxy({ 11 | target: 'http://jsonplaceholder.typicode.com', 12 | changeOrigin: true, // for vhosted sites, changes host header to match to target's host 13 | logLevel: 'debug' 14 | }); 15 | 16 | var app = express(); 17 | 18 | /** 19 | * Add the proxy to express 20 | */ 21 | app.use('/users', jsonPlaceholderProxy); 22 | 23 | app.listen(3000); 24 | 25 | console.log('[DEMO] Server: listening on port 3000'); 26 | console.log('[DEMO] Opening: http://localhost:3000/users'); 27 | 28 | require('opn')('http://localhost:3000/users'); 29 | -------------------------------------------------------------------------------- /examples/connect/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var http = require('http'); 5 | var connect = require('connect'); 6 | var proxy = require('../../index'); // require('http-proxy-middleware'); 7 | 8 | /** 9 | * Configure proxy middleware 10 | */ 11 | var jsonPlaceholderProxy = proxy({ 12 | target: 'http://jsonplaceholder.typicode.com', 13 | changeOrigin: true, // for vhosted sites, changes host header to match to target's host 14 | logLevel: 'debug' 15 | }); 16 | 17 | var app = connect(); 18 | 19 | /** 20 | * Add the proxy to connect 21 | */ 22 | app.use('/users', jsonPlaceholderProxy); 23 | 24 | http.createServer(app).listen(3000); 25 | 26 | console.log('[DEMO] Server: listening on port 3000'); 27 | console.log('[DEMO] Opening: http://localhost:3000/users'); 28 | 29 | require('opn')('http://localhost:3000/users'); 30 | -------------------------------------------------------------------------------- /examples/browser-sync/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var browserSync = require('browser-sync').create(); 5 | var proxy = require('../../index'); // require('http-proxy-middleware'); 6 | 7 | /** 8 | * Configure proxy middleware 9 | */ 10 | var jsonPlaceholderProxy = proxy('/users', { 11 | target: 'http://jsonplaceholder.typicode.com', 12 | changeOrigin: true, // for vhosted sites, changes host header to match to target's host 13 | logLevel: 'debug' 14 | }); 15 | 16 | /** 17 | * Add the proxy to browser-sync 18 | */ 19 | browserSync.init({ 20 | server: { 21 | baseDir: './', 22 | port: 3000, 23 | middleware: [jsonPlaceholderProxy], 24 | }, 25 | startPath: '/users' 26 | }); 27 | 28 | console.log('[DEMO] Server: listening on port 3000'); 29 | console.log('[DEMO] Opening: http://localhost:3000/users'); 30 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | View working examples of `http-proxy-middleware` implemented in popular servers. 4 | 5 | To run and view the [examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples); Clone the `http-proxy-middleware` repo and install the dependencies: 6 | 7 | ```bash 8 | $ git clone https://github.com/chimurai/http-proxy-middleware.git 9 | $ cd http-proxy-middleware 10 | $ npm install 11 | ``` 12 | 13 | Run the example from root folder: 14 | 15 | ```bash 16 | $ node examples/browser-sync 17 | ``` 18 | 19 | ```bash 20 | $ node examples/connect 21 | ``` 22 | 23 | ```bash 24 | $ node examples/express 25 | ``` 26 | 27 | ```bash 28 | $ node examples/websocket 29 | ``` 30 | 31 | ## Server recipes 32 | 33 | You can find more server implementations with `http-proxy-middleware` in the [server recipes](https://github.com/chimurai/http-proxy-middleware/tree/master/recipes/servers.md) 34 | -------------------------------------------------------------------------------- /recipes/basic.md: -------------------------------------------------------------------------------- 1 | # Basic usage 2 | 3 | This example will create a basic proxy middleware. 4 | 5 | ```javascript 6 | var proxy = require("http-proxy-middleware"); 7 | 8 | var apiProxy = proxy('/api', {target: 'http://localhost:3000'}); 9 | // \____/ \________________________________/ 10 | // | | 11 | // context options 12 | ``` 13 | 14 | ## Alternative configuration 15 | 16 | The proxy behavior of the following examples are **exactly** the same; Just different ways to configure it. 17 | 18 | ```javascript 19 | app.use(proxy('/api', {target: 'http://localhost:3000', changeOrigin:true})); 20 | ``` 21 | 22 | ```javascript 23 | app.use(proxy('http://localhost:3000/api', {changeOrigin:true})); 24 | ``` 25 | 26 | ```javascript 27 | app.use('/api', proxy('http://localhost:3000', {changeOrigin:true})); 28 | ``` 29 | 30 | ```javascript 31 | app.use('/api', proxy({target: 'http://localhost:3000', changeOrigin:true})); 32 | ``` 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Steven Chim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /recipes/websocket.md: -------------------------------------------------------------------------------- 1 | # WebSocket 2 | 3 | This example will create a proxy middleware with websocket support. 4 | 5 | ```javascript 6 | var proxy = require("http-proxy-middleware"); 7 | 8 | var socketProxy = proxy('/socket', {target: 'http://localhost:3000', ws: true}); 9 | ``` 10 | 11 | ## WebSocket - Path Rewrite 12 | 13 | This example will create a proxy middleware with websocket support and pathRewrite. 14 | 15 | ```javascript 16 | var proxy = require("http-proxy-middleware"); 17 | 18 | var options = { 19 | target: 'http://localhost:3000', 20 | ws: true, 21 | pathRewrite: { 22 | '^/socket' : '' 23 | } 24 | }; 25 | 26 | var socketProxy = proxy('/socket', options); 27 | ``` 28 | 29 | ## WebSocket - Server update subscription 30 | 31 | This example will create a proxy middleware with websocket support. 32 | 33 | Subscribe to server's upgrade event. 34 | 35 | ```javascript 36 | var proxy = require("http-proxy-middleware"); 37 | 38 | var socketProxy = proxy('/socket', {target: 'http://localhost:3000', ws: true}); 39 | 40 | server.on('upgrade', proxy.upgrade); // <-- subscribe to http 'upgrade' 41 | ``` 42 | -------------------------------------------------------------------------------- /lib/router.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var logger = require('./logger.js').getInstance(); 3 | 4 | module.exports = { 5 | getTarget: getTarget 6 | }; 7 | 8 | function getTarget(req, config) { 9 | var newTarget; 10 | var router = config.router; 11 | 12 | if (_.isPlainObject(router)) { 13 | newTarget = getTargetFromProxyTable(req, router); 14 | } else if (_.isFunction(router)) { 15 | newTarget = router(req); 16 | } 17 | 18 | return newTarget; 19 | } 20 | 21 | function getTargetFromProxyTable(req, table) { 22 | var result; 23 | var host = req.headers.host; 24 | var path = req.url; 25 | 26 | var hostAndPath = host + path; 27 | 28 | _.forIn(table, function(value, key) { 29 | if (containsPath(key)) { 30 | 31 | if (hostAndPath.indexOf(key) > -1) { // match 'localhost:3000/api' 32 | result = table[key]; 33 | logger.debug('[HPM] Router table match: "%s"', key); 34 | return false; 35 | } 36 | } else { 37 | 38 | if (key === host) { // match 'localhost:3000' 39 | result = table[key]; 40 | logger.debug('[HPM] Router table match: "%s"', host); 41 | return false; 42 | } 43 | 44 | } 45 | 46 | }); 47 | 48 | return result; 49 | } 50 | 51 | function containsPath(v) { 52 | return v.indexOf('/') > -1; 53 | } 54 | -------------------------------------------------------------------------------- /recipes/delay.md: -------------------------------------------------------------------------------- 1 | # Delay proxied request/response 2 | 3 | Sometimes we need the ability to delay request to backend server or response from it back to client to test long execution time of particular url or all of them. With DevTool's [network throttling](https://developers.google.com/web/tools/chrome-devtools/profile/network-performance/network-conditions?hl=en) we can test slowdown of all request, not separately. 4 | But we can handle each request individually via our proxy, and add delay to its execution time. 5 | 6 | Let's assume that we want slow down the access to backend's `/api/get-me-something` resource. Delay request time by 2 seconds and increase response time by 5 seconds. 7 | 8 | For achieving it just put additional route handler to your app before proxy handler: 9 | 10 | ```javascript 11 | const myProxy = proxy({ 12 | target: 'http://www.example.com', 13 | changeOrigin: true 14 | }); 15 | const proxyDelay = function (req, res, next) { 16 | if (req.originalUrl === '/api/get-me-something') { 17 | // Delay request by 2 seconds 18 | setTimeout(next, 2000); 19 | 20 | // Delay response completion by 5 seconds 21 | const endOriginal = res.end; 22 | res.end = function (...args) { 23 | setTimeout(function () { 24 | endOriginal.apply(res, args); 25 | }, 5000); 26 | }; 27 | } else { 28 | next(); 29 | } 30 | }; 31 | 32 | app.use('/api', proxyDelay, myProxy); 33 | ``` 34 | 35 | And you will see result in devtools similar to this: 36 | 37 | ![http-proxy-delay](https://cloud.githubusercontent.com/assets/576077/15839924/49ebe256-2bfb-11e6-8591-ef0101670885.png) 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-proxy-middleware", 3 | "version": "0.17.3", 4 | "description": "The one-liner node.js proxy middleware for connect, express and browser-sync", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js", 8 | "lib" 9 | ], 10 | "scripts": { 11 | "clean": "rm -rf coverage", 12 | "test": "mocha --recursive --colors --reporter spec", 13 | "cover": "npm run clean && istanbul cover ./node_modules/mocha/bin/_mocha -- --recursive", 14 | "coveralls": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --recursive --reporter spec && istanbul-coveralls && npm run clean" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/chimurai/http-proxy-middleware.git" 19 | }, 20 | "keywords": [ 21 | "reverse", 22 | "proxy", 23 | "middleware", 24 | "http", 25 | "https", 26 | "connect", 27 | "express", 28 | "browser-sync", 29 | "gulp", 30 | "grunt-contrib-connect", 31 | "websocket", 32 | "ws", 33 | "cors" 34 | ], 35 | "author": "Steven Chim", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/chimurai/http-proxy-middleware/issues" 39 | }, 40 | "homepage": "https://github.com/chimurai/http-proxy-middleware", 41 | "devDependencies": { 42 | "browser-sync": "^2.18.2", 43 | "chai": "^3.5.0", 44 | "connect": "^3.5.0", 45 | "coveralls": "^2.11.15", 46 | "express": "^4.14.0", 47 | "istanbul": "^0.4.5", 48 | "istanbul-coveralls": "^1.0.3", 49 | "mocha": "^3.2.0", 50 | "mocha-lcov-reporter": "1.2.0", 51 | "opn": "^4.0.2", 52 | "ws": "^1.1.1" 53 | }, 54 | "dependencies": { 55 | "http-proxy": "^1.16.2", 56 | "is-glob": "^3.1.0", 57 | "lodash": "^4.17.2", 58 | "micromatch": "^2.3.11" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/websocket/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var express = require('express'); 5 | var proxy = require('../../index'); // require('http-proxy-middleware'); 6 | 7 | /** 8 | * Configure proxy middleware 9 | */ 10 | var wsProxy = proxy('/', { 11 | target: 'http://echo.websocket.org', 12 | // pathRewrite: { 13 | // '^/websocket' : '/socket', // rewrite path. 14 | // '^/removepath' : '' // remove path. 15 | // }, 16 | changeOrigin: true, // for vhosted sites, changes host header to match to target's host 17 | ws: true, // enable websocket proxy 18 | logLevel: 'debug' 19 | }); 20 | 21 | var app = express(); 22 | app.use('/', express.static(__dirname)); // demo page 23 | app.use(wsProxy); // add the proxy to express 24 | 25 | var server = app.listen(3000); 26 | server.on('upgrade', wsProxy.upgrade); // optional: upgrade externally 27 | 28 | console.log('[DEMO] Server: listening on port 3000'); 29 | console.log('[DEMO] Opening: http://localhost:3000'); 30 | 31 | require('opn')('http://localhost:3000'); 32 | 33 | /** 34 | * Example: 35 | * Open http://localhost:3000 in WebSocket compatible browser. 36 | * In browser console: 37 | * 1. `var socket = new WebSocket('ws://localhost:3000');` // create new WebSocket 38 | * 2. `socket.onmessage = function (msg) {console.log(msg)};` // listen to socket messages 39 | * 3. `socket.send('hello world');` // send message 40 | * > {data: "hello world"} // server should echo back your message. 41 | **/ 42 | -------------------------------------------------------------------------------- /recipes/shorthand.md: -------------------------------------------------------------------------------- 1 | # Shorthand 2 | 3 | This example will create a proxy middleware using the shorthand notation. 4 | 5 | The http-proxy-middleware `context` and `config.target` will be set automatically. 6 | 7 | ```javascript 8 | var proxy = require("http-proxy-middleware"); 9 | 10 | var apiProxy = proxy('http://localhost:3000/api'); 11 | 12 | // equivalent: 13 | // var apiProxy = proxy('/api', {target:'http://localhost:3000'}); 14 | ``` 15 | 16 | ## Shorthand - Wildcard context 17 | 18 | This example will create a proxy middleware with shorthand wildcard context. 19 | 20 | ```javascript 21 | var proxy = require("http-proxy-middleware"); 22 | 23 | var apiProxy = proxy('http://localhost:3000/api/books/*/**.json'); 24 | // equals: 25 | // var apiProxy = proxy('/api/books/*/**.json', {target:'http://localhost:3000'}); 26 | ``` 27 | 28 | 29 | ## Shorthand with additional configuration 30 | 31 | This example will create a proxy middleware with shorthand and additional configuration. 32 | 33 | ```javascript 34 | var proxy = require("http-proxy-middleware"); 35 | 36 | var apiProxy = proxy('http://localhost:3000/api', {changeOrigin: true}); 37 | // equals: 38 | // var apiProxy = proxy('/api', {target:'http://localhost:3000', {changeOrigin:true}}); 39 | ``` 40 | 41 | ## Shorthand - WebSocket 42 | 43 | This example will create a proxy middleware with shorthand and additional configuration for WebSocket support. 44 | 45 | ```javascript 46 | var proxy = require("http-proxy-middleware"); 47 | 48 | var apiProxy = proxy('http://localhost:3000/api', {ws: true}); 49 | // equals: 50 | // var apiProxy = proxy('/api', {target:'http://localhost:3000', ws: true}); 51 | ``` 52 | 53 | ## Shorthand - WebSocket only 54 | 55 | This example will create a proxy middleware with websocket shorthand only configuration. 56 | 57 | ```javascript 58 | var proxy = require("http-proxy-middleware"); 59 | 60 | var apiProxy = proxy('ws://localhost:3000/api'); 61 | // equals: 62 | // var apiProxy = proxy('/api', {target:'ws://localhost:3000', ws: true}); 63 | ``` 64 | -------------------------------------------------------------------------------- /recipes/logProvider.md: -------------------------------------------------------------------------------- 1 | # Log Provider 2 | 3 | Configure your own logger with the `logProvider` option. 4 | 5 | In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging. 6 | 7 | ```javascript 8 | var winston = require('winston'); 9 | var proxy = require("http-proxy-middleware"); 10 | 11 | var options = { 12 | target: 'http://localhost:3000', 13 | logProvider: function (provider) { 14 | return winston; 15 | } 16 | }; 17 | 18 | var apiProxy = proxy('/api', options); 19 | ``` 20 | 21 | ## Winston 22 | 23 | Configure your own logger with the `logProvider` option. 24 | 25 | In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging. Map the logging api if needed. 26 | 27 | ```javascript 28 | 29 | var winston = require('winston'); 30 | var proxy = require("http-proxy-middleware"); 31 | 32 | var logProvider = function (provider) { 33 | return { 34 | log : winston.log, 35 | debug : winston.debug, 36 | info : winston.info, 37 | warn : winston.warn, 38 | error : winston.error 39 | }; 40 | }; 41 | 42 | var options = { 43 | target: 'http://localhost:3000', 44 | logProvider: logProvider 45 | }; 46 | 47 | var apiProxy = proxy('/api', options); 48 | ``` 49 | 50 | # Winston Multi Transport 51 | 52 | Configure your own logger with the `logProvider` option. 53 | 54 | In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging. 55 | 56 | ```javascript 57 | var winston = require('winston'); 58 | var proxy = require("http-proxy-middleware"); 59 | 60 | var logProvider = function (provider) { 61 | var logger = new (winston.Logger)({ 62 | transports: [ 63 | new (winston.transports.Console)(), 64 | new (winston.transports.File)({ filename: 'somefile.log' }) 65 | ] 66 | }); 67 | 68 | return logger; 69 | }; 70 | 71 | var options = { 72 | target: 'http://localhost:3000', 73 | logProvider: logProvider 74 | }; 75 | 76 | var apiProxy = proxy('/api', options); 77 | ``` 78 | -------------------------------------------------------------------------------- /test/e2e/express-router.spec.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var expect = require('chai').expect; 3 | var http = require('http'); 4 | var proxy = require('../../index'); 5 | 6 | describe('Usage in Express', function() { 7 | 8 | var app; 9 | var server; 10 | 11 | beforeEach(function() { 12 | app = express(); 13 | }); 14 | 15 | afterEach(function() { 16 | server && server.close(); 17 | }); 18 | 19 | // https://github.com/chimurai/http-proxy-middleware/issues/94 20 | describe('Express Sub Route', function() { 21 | 22 | beforeEach(function() { 23 | 24 | // sub route config 25 | var sub = new express.Router(); 26 | 27 | function filter(pathname, req) { 28 | var urlFilter = new RegExp('^/sub/api'); 29 | var match = urlFilter.test(pathname); 30 | return match; 31 | } 32 | 33 | /** 34 | * Mount proxy without 'path' in sub route 35 | */ 36 | var proxyConfig = {target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, logLevel: 'silent'}; 37 | sub.use(proxy(filter, proxyConfig)); 38 | 39 | sub.get('/hello', jsonMiddleware({'content': 'foobar'})); 40 | 41 | // configure sub route on /sub junction 42 | app.use('/sub', sub); 43 | 44 | // start server 45 | server = app.listen(3000); 46 | }); 47 | 48 | it('should still return a response when route does not match proxyConfig', function(done) { 49 | var responseBody; 50 | http.get('http://localhost:3000/sub/hello', function(res) { 51 | res.on('data', function(chunk) { 52 | responseBody = chunk.toString(); 53 | expect(responseBody).to.equal('{"content":"foobar"}'); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | }); 60 | 61 | function jsonMiddleware(data) { 62 | return function(req, res) { 63 | res.json(data); 64 | }; 65 | } 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /recipes/pathRewrite.md: -------------------------------------------------------------------------------- 1 | # pathRewrite 2 | 3 | Modify request paths before requests are send to the target. 4 | 5 | 6 | 7 | - [rewrite paths](#rewrite-paths) 8 | - [remove paths](#remove-paths) 9 | - [add paths](#add-paths) 10 | - [custom rewrite function](#custom-rewrite-function) 11 | 12 | 13 | 14 | 15 | ## rewrite paths 16 | 17 | Rewrite paths 18 | 19 | ```javascript 20 | var proxy = require('http-proxy-middleware'); 21 | 22 | var options = { 23 | target: 'http://localhost:3000', 24 | pathRewrite: { 25 | '^/api/old-path' : '/api/new-path', // rewrite path 26 | }, 27 | }; 28 | 29 | var apiProxy = proxy('/api', options); 30 | 31 | // `/old/api/foo/bar` -> `http://localhost:3000/new/api/foo/bar` 32 | ``` 33 | 34 | ## remove paths 35 | 36 | Remove base path 37 | 38 | ```javascript 39 | var proxy = require('http-proxy-middleware'); 40 | 41 | var options = { 42 | target: 'http://localhost:3000', 43 | pathRewrite: { 44 | '^/api/' : '/' // remove base path 45 | } 46 | }; 47 | 48 | var apiProxy = proxy('/api', options); 49 | 50 | // `/api/lorum/ipsum` -> `http://localhost:3000/lorum/ipsum` 51 | ``` 52 | 53 | ## add paths 54 | 55 | Add base path 56 | 57 | ```javascript 58 | var proxy = require('http-proxy-middleware'); 59 | 60 | var options = { 61 | target: 'http://localhost:3000', 62 | pathRewrite: { 63 | '^/' : '/extra/' // add base path 64 | } 65 | }; 66 | 67 | var apiProxy = proxy('/api', options); 68 | 69 | // `/api/lorum/ipsum` -> `http://localhost:3000/extra/api/lorum/ipsum` 70 | ``` 71 | 72 | ## custom rewrite function 73 | 74 | Implement you own path rewrite logic. 75 | 76 | The unmodified path will be used, when rewrite function returns `undefined` 77 | 78 | ```javascript 79 | var proxy = require('http-proxy-middleware'); 80 | 81 | var rewriteFn = function (path, req) { 82 | return path.replace('/api/foo', '/api/bar'); 83 | } 84 | 85 | var options = { 86 | target: 'http://localhost:3000', 87 | pathRewrite: rewriteFn 88 | }; 89 | 90 | var apiProxy = proxy('/api', options); 91 | 92 | // `/api/foo/lorum/ipsum` -> `http://localhost:3000/api/bar/lorum/ipsum` 93 | ``` 94 | -------------------------------------------------------------------------------- /lib/path-rewriter.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var logger = require('./logger').getInstance(); 3 | 4 | module.exports = { 5 | create: createPathRewriter 6 | }; 7 | 8 | /** 9 | * Create rewrite function, to cache parsed rewrite rules. 10 | * 11 | * @returns {function} Function to rewrite paths; This function should accept `path` (request.url) as parameter 12 | */ 13 | function createPathRewriter(rewriteConfig) { 14 | var rulesCache; 15 | 16 | if (!isValidRewriteConfig(rewriteConfig)) { 17 | return; 18 | } 19 | 20 | if (_.isFunction(rewriteConfig)) { 21 | var customRewriteFn = rewriteConfig; 22 | return customRewriteFn; 23 | } else { 24 | rulesCache = parsePathRewriteRules(rewriteConfig); 25 | return rewritePath; 26 | } 27 | 28 | function rewritePath(path) { 29 | var result = path; 30 | 31 | _.forEach(rulesCache, function(rule) { 32 | if (rule.regex.test(path)) { 33 | result = result.replace(rule.regex, rule.value); 34 | logger.debug('[HPM] Rewriting path from "%s" to "%s"', path, result); 35 | return false; 36 | } 37 | }); 38 | 39 | return result; 40 | } 41 | } 42 | 43 | function isValidRewriteConfig(rewriteConfig) { 44 | if (_.isFunction(rewriteConfig)) { 45 | return true; 46 | } else if (!_.isEmpty(rewriteConfig) && _.isPlainObject(rewriteConfig)) { 47 | return true; 48 | } else if (_.isUndefined(rewriteConfig) || 49 | _.isNull(rewriteConfig) || 50 | _.isEqual(rewriteConfig, {})) { 51 | return false; 52 | } else { 53 | throw new Error('[HPM] Invalid pathRewrite config. Expecting object with pathRewrite config or a rewrite function'); 54 | } 55 | } 56 | 57 | function parsePathRewriteRules(rewriteConfig) { 58 | var rules = []; 59 | 60 | if (_.isPlainObject(rewriteConfig)) { 61 | _.forIn(rewriteConfig, function(value, key) { 62 | rules.push({ 63 | regex: new RegExp(key), 64 | value: rewriteConfig[key] 65 | }); 66 | logger.info('[HPM] Proxy rewrite rule created: "%s" ~> "%s"', key, rewriteConfig[key]); 67 | }); 68 | } 69 | 70 | return rules; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /lib/handlers.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var logger = require('./logger').getInstance(); 3 | 4 | module.exports = { 5 | init: init, 6 | getHandlers: getProxyEventHandlers 7 | }; 8 | 9 | function init(proxy, opts) { 10 | var handlers = getProxyEventHandlers(opts); 11 | 12 | _.forIn(handlers, function(handler, eventName) { 13 | proxy.on(eventName, handlers[eventName]); 14 | }); 15 | 16 | logger.debug('[HPM] Subscribed to http-proxy events: ', _.keys(handlers)); 17 | } 18 | 19 | function getProxyEventHandlers(opts) { 20 | // https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events 21 | var proxyEvents = ['error', 'proxyReq', 'proxyReqWs', 'proxyRes', 'open', 'close']; 22 | var handlers = {}; 23 | 24 | _.forEach(proxyEvents, function(event) { 25 | // all handlers for the http-proxy events are prefixed with 'on'. 26 | // loop through options and try to find these handlers 27 | // and add them to the handlers object for subscription in init(). 28 | var eventName = _.camelCase('on ' + event); 29 | var fnHandler = _.get(opts, eventName); 30 | 31 | if (_.isFunction(fnHandler)) { 32 | handlers[event] = fnHandler; 33 | } 34 | }); 35 | 36 | // add default error handler in absence of error handler 37 | if (!_.isFunction(handlers.error)) { 38 | handlers.error = defaultErrorHandler; 39 | } 40 | 41 | // add default close handler in absence of close handler 42 | if (!_.isFunction(handlers.close)) { 43 | handlers.close = logClose; 44 | } 45 | 46 | return handlers; 47 | }; 48 | 49 | function defaultErrorHandler(err, req, res) { 50 | var host = (req.headers && req.headers.host); 51 | var code = err.code; 52 | 53 | if (res.writeHead && !res.headersSent) { 54 | if (/HPE_INVALID/.test(code)) { 55 | res.writeHead(502); 56 | } else { 57 | switch(code) { 58 | case 'ECONNRESET': 59 | case 'ENOTFOUND': 60 | case 'ECONNREFUSED': 61 | res.writeHead(504); 62 | break; 63 | default: res.writeHead(500); 64 | } 65 | } 66 | } 67 | 68 | res.end('Error occured while trying to proxy to: ' + host + req.url); 69 | } 70 | 71 | function logClose(req, socket, head) { 72 | // view disconnected websocket connections 73 | logger.info('[HPM] Client disconnected'); 74 | } 75 | -------------------------------------------------------------------------------- /recipes/proxy-events.md: -------------------------------------------------------------------------------- 1 | # Proxy Events 2 | 3 | Subscribe to [`http-proxy`](https://github.com/nodejitsu/node-http-proxy) [![GitHub stars](https://img.shields.io/github/stars/nodejitsu/node-http-proxy.svg?style=social&label=Star)](https://github.com/nodejitsu/node-http-proxy) events: `error`, `proxyReq`, `proxyReqWs`, `proxyRes`, `open`, `close`. 4 | 5 | ## onError 6 | 7 | Subscribe to http-proxy's [error event](https://www.npmjs.com/package/http-proxy#listening-for-proxy-events). 8 | 9 | ```javascript 10 | var proxy = require("http-proxy-middleware"); 11 | 12 | var onError = function (err, req, res) { 13 | console.log('Something went wrong.'); 14 | console.log('And we are reporting a custom error message.'); 15 | }; 16 | 17 | var options = {target:'http://localhost:3000', onError: onError}; 18 | 19 | var apiProxy = proxy('/api', options); 20 | ``` 21 | 22 | ## onProxyReq 23 | 24 | Subscribe to http-proxy's [proxyReq event](https://www.npmjs.com/package/http-proxy#listening-for-proxy-events). 25 | 26 | ```javascript 27 | var proxy = require("http-proxy-middleware"); 28 | 29 | var onProxyReq = function (proxyReq, req, res) { 30 | // add new header to request 31 | proxyReq.setHeader('x-added', 'foobar'); 32 | }; 33 | 34 | var options = {target:'http://localhost:3000', onProxyReq: onProxyReq}; 35 | 36 | var apiProxy = proxy('/api', options); 37 | ``` 38 | 39 | ## onProxyReqWs 40 | 41 | Subscribe to http-proxy's [proxyReqWs event](https://www.npmjs.com/package/http-proxy#listening-for-proxy-events). 42 | 43 | ```javascript 44 | var proxy = require("http-proxy-middleware"); 45 | 46 | var onProxyReqWs = function (proxyReq, req, socket, options, head) { 47 | // add custom header 48 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar'); 49 | }; 50 | 51 | var options = {target:'http://localhost:3000', onProxyReqWs: onProxyReqWs}; 52 | 53 | var apiProxy = proxy('/api', options); 54 | ``` 55 | 56 | ## onProxyRes 57 | 58 | Subscribe to http-proxy's [proxyRes event](https://www.npmjs.com/package/http-proxy#listening-for-proxy-events). 59 | 60 | ```javascript 61 | var proxy = require("http-proxy-middleware"); 62 | 63 | var onProxyRes = function (proxyRes, req, res) { 64 | // add new header to response 65 | proxyRes.headers['x-added'] = 'foobar'; 66 | 67 | // remove header from response 68 | delete proxyRes.headers['x-removed']; 69 | }; 70 | 71 | var options = {target:'http://localhost:3000', onProxyRes: onProxyRes}; 72 | 73 | var apiProxy = proxy('/api', options); 74 | ``` 75 | -------------------------------------------------------------------------------- /recipes/router.md: -------------------------------------------------------------------------------- 1 | # router 2 | 3 | Allows you to route to a different `target` by using a table of a custom function. 4 | 5 | 6 | 7 | - [Custom router function](#custom-router-function) 8 | - [Proxy Table](#proxy-table) 9 | 10 | 11 | 12 | 13 | ## Custom router function 14 | 15 | Write your own router to dynamically route to a different `target`. 16 | The `req` object is provided to retrieve contextual data. 17 | 18 | ```javascript 19 | var express = require('express'); 20 | var proxy = require("http-proxy-middleware"); 21 | 22 | var customRouter = function(req) { 23 | return 'http://www.example.org' // protocol + host 24 | }; 25 | 26 | var options = { 27 | target: 'http://localhost:8000', 28 | router: customRouter 29 | }; 30 | 31 | var myProxy = proxy(options); 32 | 33 | var app = express(); 34 | app.use(myProxy); // add the proxy to express 35 | 36 | app.listen(3000); 37 | ``` 38 | 39 | 40 | ## Proxy Table 41 | 42 | Use a Proxy Table to proxy requests to a different `target` based on: 43 | * Host [HTTP header](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields). 44 | * Request path 45 | * Host HTTP header + path 46 | 47 | ```javascript 48 | var express = require('express'); 49 | var proxy = require("http-proxy-middleware"); 50 | 51 | var proxyTable = { 52 | "integration.localhost:3000" : "http://localhost:8001", // host only 53 | "staging.localhost:3000" : "http://localhost:8002", // host only 54 | "localhost:3000/api" : "http://localhost:8003", // host + path 55 | "/rest" : "http://localhost:8004" // path only 56 | }; 57 | 58 | var options = { 59 | target: 'http://localhost:8000', 60 | router: proxyTable 61 | }; 62 | 63 | var myProxy = proxy(options); 64 | 65 | var app = express(); 66 | app.use(myProxy); // add the proxy to express 67 | 68 | app.listen(3000); 69 | ``` 70 | 71 | ### Example 72 | 73 | In the example above; all requests will be proxied to `http://localhost:8000`. 74 | 75 | When request's `Host HTTP header` and/or `path` match a configuration in the proxyTable, they will be send to matching target. 76 | 77 | ``` 78 | http://localhost:3000/lorum/ipsum -> http://localhost:8000/lorum/ipsum 79 | http://integration.localhost:3000/lorum/ipsum -> http://localhost:8001/lorum/ipsum 80 | http://staging.localhost:3000/rest/foo/bar -> http://localhost:8002/rest/foo/bar 81 | http://localhost:3000/api/houses/123 -> http://localhost:8003/api/houses/123 82 | http://localhost:3000/rest/books/123 -> http://localhost:8004/rest/books/123 83 | ``` 84 | -------------------------------------------------------------------------------- /lib/context-matcher.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var url = require('url'); 3 | var isGlob = require('is-glob'); 4 | var micromatch = require('micromatch'); 5 | 6 | module.exports = { 7 | match: matchContext 8 | }; 9 | 10 | function matchContext(context, uri, req) { 11 | 12 | // single path 13 | if (isStringPath(context)) { 14 | return matchSingleStringPath(context, uri); 15 | } 16 | 17 | // single glob path 18 | if (isGlobPath(context)) { 19 | return matchSingleGlobPath(context, uri); 20 | } 21 | 22 | // multi path 23 | if (Array.isArray(context)) { 24 | if (context.every(isStringPath)) { 25 | return matchMultiPath(context, uri); 26 | } 27 | if (context.every(isGlobPath)) { 28 | return matchMultiGlobPath(context, uri); 29 | } 30 | 31 | throw new Error('[HPM] Invalid context. Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"]'); 32 | } 33 | 34 | // custom matching 35 | if (_.isFunction(context)) { 36 | var pathname = getUrlPathName(uri); 37 | return context(pathname, req); 38 | } 39 | 40 | throw new Error('[HPM] Invalid context. Expecting something like: "/api" or ["/api", "/ajax"]'); 41 | } 42 | 43 | /** 44 | * @param {String} context '/api' 45 | * @param {String} uri 'http://example.org/api/b/c/d.html' 46 | * @return {Boolean} 47 | */ 48 | function matchSingleStringPath(context, uri) { 49 | var pathname = getUrlPathName(uri); 50 | return pathname.indexOf(context) === 0; 51 | } 52 | 53 | function matchSingleGlobPath(pattern, uri) { 54 | var pathname = getUrlPathName(uri); 55 | var matches = micromatch(pathname, pattern); 56 | return matches && (matches.length > 0); 57 | } 58 | 59 | function matchMultiGlobPath(patternList, uri) { 60 | return matchSingleGlobPath(patternList, uri); 61 | } 62 | 63 | /** 64 | * @param {String} context ['/api', '/ajax'] 65 | * @param {String} uri 'http://example.org/api/b/c/d.html' 66 | * @return {Boolean} 67 | */ 68 | function matchMultiPath(contextList, uri) { 69 | for (var i = 0; i < contextList.length; i++) { 70 | var context = contextList[i]; 71 | if (matchSingleStringPath(context, uri)) { 72 | return true; 73 | } 74 | } 75 | return false; 76 | } 77 | 78 | /** 79 | * Parses URI and returns RFC 3986 path 80 | * 81 | * @param {String} uri from req.url 82 | * @return {String} RFC 3986 path 83 | */ 84 | function getUrlPathName(uri) { 85 | return uri && url.parse(uri).pathname; 86 | } 87 | 88 | function isStringPath(context) { 89 | return _.isString(context) && !isGlob(context); 90 | } 91 | 92 | function isGlobPath(context) { 93 | return isGlob(context); 94 | } 95 | -------------------------------------------------------------------------------- /recipes/context-matching.md: -------------------------------------------------------------------------------- 1 | # Context matching 2 | 3 | Determine which requests should be proxied. 4 | 5 | Context matching is optional and is useful in cases where you are not able to use the regular [middleware mounting](http://expressjs.com/en/4x/api.html#app.use). 6 | 7 | The [RFC 3986 `path`](https://tools.ietf.org/html/rfc3986#section-3.3) is used for context matching. 8 | 9 | ``` 10 | foo://example.com:8042/over/there?name=ferret#nose 11 | \_/ \______________/\_________/ \_________/ \__/ 12 | | | | | | 13 | scheme authority path query fragment 14 | ``` 15 | 16 | 17 | `http-proxy-middleware` offers several ways to do this: 18 | 19 | 20 | 21 | - [Path](#path) 22 | - [Multi Path](#multi-path) 23 | - [Wildcard](#wildcard) 24 | - [Multi Wildcard](#multi-wildcard) 25 | - [Wildcard / Exclusion](#wildcard--exclusion) 26 | - [Custom filtering](#custom-filtering) 27 | 28 | 29 | 30 | 31 | ## Path 32 | 33 | This will match paths starting with `/api` 34 | 35 | ```javascript 36 | var proxy = require("http-proxy-middleware"); 37 | 38 | var apiProxy = proxy('/api', {target: 'http://localhost:3000'}); 39 | 40 | // `/api/foo/bar` -> `http://localhost:3000/api/foo/bar` 41 | ``` 42 | 43 | ## Multi Path 44 | 45 | This will match paths starting with `/api` or `/rest` 46 | 47 | ```javascript 48 | var proxy = require("http-proxy-middleware"); 49 | 50 | var apiProxy = proxy(['/api', '/rest'], {target: 'http://localhost:3000'}); 51 | 52 | // `/api/foo/bar` -> `http://localhost:3000/api/foo/bar` 53 | // `/rest/lorum/ipsum` -> `http://localhost:3000/rest/lorum/ipsum` 54 | ``` 55 | 56 | ## Wildcard 57 | 58 | This will match paths starting with `/api/` and should also end with `.json` 59 | 60 | ```javascript 61 | var proxy = require("http-proxy-middleware"); 62 | 63 | var apiProxy = proxy('/api/**/*.json', {target: 'http://localhost:3000'}); 64 | ``` 65 | 66 | ## Multi Wildcard 67 | 68 | Multiple wildcards can be used. 69 | 70 | ```javascript 71 | var proxy = require("http-proxy-middleware"); 72 | 73 | var apiProxy = proxy(['/api/**/*.json', '/rest/**'], {target: 'http://localhost:3000'}); 74 | ``` 75 | 76 | ## Wildcard / Exclusion 77 | 78 | This example will create a proxy with wildcard context matching. 79 | 80 | ```javascript 81 | var proxy = require("http-proxy-middleware"); 82 | 83 | var apiProxy = proxy(['foo/*.js', '!bar.js'], {target: 'http://localhost:3000'}); 84 | ``` 85 | 86 | ## Custom filtering 87 | 88 | Write your custom context matching function to have full control on the matching behavior. 89 | The request `pathname` and `req` object are provided to determine which requests should be proxied or not. 90 | 91 | ```javascript 92 | var proxy = require("http-proxy-middleware"); 93 | 94 | var filter = function (pathname, req) { 95 | return (pathname.match('^/api') && req.method === 'GET'); 96 | }; 97 | 98 | var apiProxy = proxy(filter, {target: 'http://localhost:3000'}); 99 | ``` 100 | -------------------------------------------------------------------------------- /examples/websocket/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http-proxy-middleware - WebSocket example 6 | 19 | 20 | 21 | 22 | 23 |

WebSocket demo

24 | 25 |

26 | Proxy ws://localhost:3000 to ws://echo.websocket.org 27 |

28 | 29 |
30 |

31 | 32 | 33 | 34 | 35 |

36 |
37 |
38 |

39 | 40 | 41 | 42 |

43 |

44 | 45 | 46 |

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