Skip to content

Commit

Permalink
Merge pull request #8 from chimurai/websocket
Browse files Browse the repository at this point in the history
feat: websocket proxy
  • Loading branch information
chimurai committed Jul 28, 2015
2 parents e6ef751 + d752cd0 commit 111244a
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 20 deletions.
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;
}

0 comments on commit 111244a

Please sign in to comment.