Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# http-proxy-middleware
[![Build Status](https://img.shields.io/travis/chimurai/http-proxy-middleware/master.svg?style=flat-square)](https://travis-ci.org/chimurai/http-proxy-middleware)
[![Coveralls](https://img.shields.io/coveralls/chimurai/http-proxy-middleware.svg?style=flat-square)](https://coveralls.io/r/chimurai/http-proxy-middleware)
[![dependency Status](https://img.shields.io/david/chimurai/http-proxy-middleware.svg?style=flat-square)](https://david-dm.org/chimurai/http-proxy-middleware#info=devDependencies)
[![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)
[![devDependency Status](https://img.shields.io/david/dev/chimurai/http-proxy-middleware.svg?style=flat-square)](https://david-dm.org/chimurai/http-proxy-middleware#info=devDependencies)

The one-liner proxy middleware for [connect](https://github.com/senchalabs/connect), [express](https://github.com/strongloop/express) and [browser-sync](https://github.com/BrowserSync/browser-sync)
Expand Down Expand Up @@ -86,6 +86,8 @@ The following options are provided by the underlying [http-proxy](https://www.np
* **option.xfwd**: true/false, adds x-forward headers
* **option.toProxy**: passes the absolute URL as the `path` (useful for proxying to proxies)
* **option.hostRewrite**: rewrites the location hostname on (301/302/307/308) redirects.
* **option.ssl: object to be passed to https.createServer()
* **option.ws: true/false**: if you want to proxy websockets

Undocumented options are provided by the underlying [http-proxy](https://github.com/nodejitsu/node-http-proxy/blob/master/lib/http-proxy.js#L32).
* **option.headers**: object, adds [request headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields). (Example: `{host:'www.example.org'}`
Expand Down Expand Up @@ -140,6 +142,7 @@ $ node examples/connect
* `examples/connect` - [connect proxy middleware example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/connect)
* `examples/express` - [express proxy middleware example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/express)
* `examples/browser-sync` - [browser-sync proxy middleware example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/browser-sync)
* `examples/websocket` - [websocket proxy example](https://github.com/chimurai/http-proxy-middleware/tree/master/examples/websocket) with express

## Tests

Expand Down
38 changes: 38 additions & 0 deletions examples/websocket/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Module dependencies.
*/
var express = require('../../node_modules/express/index'); // require('express');
var proxyMiddleware = require('../../index'); // require('http-proxy-middleware');

// configure proxy middleware
// context: '/' will proxy all requests
var proxy = proxyMiddleware('/', {
target: 'http://echo.websocket.org',
// target: 'ws://echo.websocket.org', // alternative way to provide target with ws:// protocol
// pathRewrite: {
// '^/websocket' : '/socket', // rewrite path.
// '^/removepath' : '' // remove path.
// },
changeOrigin: true, // for vhosted sites, changes host header to match to target's host
ws: true // enable websocket proxy

});

var app = express();
app.use(proxy); // add the proxy to express

app.listen(3000);

console.log('listening on port 3000');
console.log('try:');
console.log(' ws://localhost:3000 requests will be proxied to ws://echo.websocket.org');

/**
* Example:
* Open http://localhost:3000 in WebSocket compatible browser. // don't mind the 404 page...
* In browser console:
* 1. `var socket = new WebSocket('ws://localhost:3000');` // create new WebSocket
* 2. `socket.onmessage = function (msg) {console.log(msg)};` // listen to socket messages
* 3. `socket.send('hello world')`; // send message
* > {data: "hello world"} // server should echo back your message.
**/
43 changes: 36 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
var httpProxy = require('http-proxy');
var handlers = require('./lib/handlers');
var contextMatcher = require('./lib/context-matcher');
var pathRewriter = require('./lib/path-rewriter');
var PathRewriter = require('./lib/path-rewriter');

var httpProxyMiddleware = function (context, opts) {

var isWsUpgradeListened = false;
var proxyOptions = opts || {};
var pathRewriter;

// Legacy option.proxyHost
// set options.headers.host when option.proxyHost is provided
Expand All @@ -22,13 +23,14 @@ var httpProxyMiddleware = function (context, opts) {

// create proxy
var proxy = httpProxy.createProxyServer(proxyOptions);
console.log('[HPM] Proxy created:', context, ' -> ', proxyOptions.target);

// handle option.pathRewrite
if (proxyOptions.pathRewrite) {
var rewriter = pathRewriter.create(proxyOptions.pathRewrite);
pathRewriter = PathRewriter.create(proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided

// handle option.pathRewrite
if (pathRewriter) {
proxy.on('proxyReq', function (proxyReq, req, res, options) {
handlers.proxyPathRewrite(proxyReq, rewriter);
proxyReq.path = pathRewriter(proxyReq.path);
});
}

Expand All @@ -37,7 +39,11 @@ var httpProxyMiddleware = function (context, opts) {
handlers.proxyError(err, req, res, proxyOptions);
});

console.log('[HPM] Proxy created:', context, ' -> ', proxyOptions.target);
// Listen for the `close` event on `proxy`.
proxy.on('close', function (req, socket, head) {
// view disconnected websocket connections
console.log('[HPM] Client disconnected');
});

return middleware;

Expand All @@ -47,6 +53,29 @@ var httpProxyMiddleware = function (context, opts) {
} else {
next();
}

if (proxyOptions.ws === true) {
catchUpgradeRequest(req);
}
}

function catchUpgradeRequest (req) {
// make sure only 1 handle listens to server's upgrade request.
if (isWsUpgradeListened === true) {
return;
}

isWsUpgradeListened = true;

req.connection.server.on('upgrade', function (req, socket, head) {
if (contextMatcher.match(context, req.url)) {
if (pathRewriter) {
req.url = pathRewriter(req.url);
}
proxy.ws(req, socket, head);
console.log('[HPM] Upgrading to WebSocket');
}
});
}

};
Expand Down
8 changes: 1 addition & 7 deletions lib/handlers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module.exports = {
proxyError : proxyError,
proxyPathRewrite : proxyPathRewrite
proxyError : proxyError
}

function proxyError (err, req, res, proxyOptions) {
Expand All @@ -11,8 +10,3 @@ function proxyError (err, req, res, proxyOptions) {

console.log('[HPM] Proxy error:', err.code, targetUri);
};

function proxyPathRewrite(proxyReq, fnRewriter) {
proxyReq.path = fnRewriter(proxyReq.path);
}

5 changes: 5 additions & 0 deletions lib/path-rewriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ module.exports = {
* Create rewrite function, to cache parsed rewrite rules.
*/
function createPathRewriter (config) {

if (config === undefined || config === null) {
return;
}

var rules = parsePathRewriteRules(config);

return rewritePath;
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"connect",
"express",
"browser-sync",
"gulp"
"gulp",
"websocket"
],
"author": "Steven Chim",
"license": "MIT",
Expand All @@ -37,7 +38,8 @@
"istanbul": "^0.3.17",
"istanbul-coveralls": "^1.0.3",
"mocha": "^2.2.5",
"mocha-lcov-reporter": "0.0.2"
"mocha-lcov-reporter": "0.0.2",
"ws": "^0.7.2"
},
"dependencies": {
"http-proxy": "^1.11.1",
Expand Down
9 changes: 6 additions & 3 deletions test/path-rewriter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@ describe('Path rewriting', function () {
};
});

it('should return undefined when no config is provided', function () {
expect((badFn())()).to.equal(undefined);
expect((badFn(null)())).to.equal(undefined);
expect((badFn(undefined)())).to.equal(undefined);
});

it('should throw when bad config is provided', function () {
expect(badFn()).to.throw(Error);
expect(badFn(null)).to.throw(Error);
expect(badFn(undefined)).to.throw(Error);
expect(badFn(123)).to.throw(Error);
expect(badFn("abc")).to.throw(Error);
expect(badFn(function(){})).to.throw(Error);
Expand Down
73 changes: 73 additions & 0 deletions test/websocket.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
var expect = require('chai').expect;
var proxyMiddleware = require('../index');
var http = require('http');
var express = require('express');
var WebSocket = require('ws');
var WebSocketServer = require('ws').Server;

describe('websocket proxy', function () {
var proxyServer, ws, wss;
var targetHeaders;
var responseMessage;

beforeEach(function () {
proxyServer = createServer(3000, proxyMiddleware('/', {
target:'http://localhost:8000',
ws: true,
pathRewrite: {
'^/socket' : ''
}
}));

wss = new WebSocketServer({ port: 8000 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
ws.send(message); // echo received message
});
});
});

beforeEach(function (done) {
// need to make a normal http request,
// so http-proxy-middleware can catch the upgrade request
http.get('http://localhost:3000/', function () {
// do a second http request to make
// sure only 1 listener subscribes to upgrade request
http.get('http://localhost:3000/', function () {
ws = new WebSocket('ws://localhost:3000/socket');

ws.on('message', function incoming(message) {
responseMessage = message;
done();
});

ws.on('open', function open() {
ws.send('foobar');
});
});
});

});

afterEach(function () {
proxyServer.close();
wss.close();
ws = null;
});

it('should proxy to path', function () {
expect(responseMessage).to.equal('foobar');
});
});

function createServer (portNumber, middleware) {
var app = express();

if (middleware) {
app.use(middleware);
}

var server = app.listen(portNumber);

return server;
}