From 79c3bab4f3868f391f92d13a572cffcc374f8a2a Mon Sep 17 00:00:00 2001 From: Steven Chim Date: Tue, 25 Aug 2015 22:16:40 +0200 Subject: [PATCH 1/4] support external websocket upgrade (#19) --- index.js | 28 +++++++---- test/websocket.spec.js | 102 +++++++++++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 34 deletions(-) diff --git a/index.js b/index.js index 93b9a8da..afaedf9d 100644 --- a/index.js +++ b/index.js @@ -42,6 +42,13 @@ var httpProxyMiddleware = function (context, opts) { console.log('[HPM] Client disconnected'); }); + // https://github.com/chimurai/http-proxy-middleware/issues/19 + // expose function to upgrade externally + middleware.upgrade = function (req, socket, head) { + handleUpgrade(req, socket, head); + isWsUpgradeListened = true; + }; + return middleware; function middleware (req, res, next) { @@ -63,27 +70,28 @@ var httpProxyMiddleware = function (context, opts) { } if (proxyOptions.ws === true) { - catchUpgradeRequest(req); + catchUpgradeRequest(req.connection.server); } } - function catchUpgradeRequest (req) { + function catchUpgradeRequest (server) { // make sure only 1 handle listens to server's upgrade request. if (isWsUpgradeListened === true) { return; } + server.on('upgrade', handleUpgrade); isWsUpgradeListened = true; + } - req.connection.server.on('upgrade', function (req, socket, head) { - if (contextMatcher.match(config.context, req.url)) { - if (pathRewriter) { - req.url = pathRewriter(req.url); - } - proxy.ws(req, socket, head); - console.log('[HPM] Upgrading to WebSocket'); + function handleUpgrade (req, socket, head) { + if (contextMatcher.match(config.context, req.url)) { + if (pathRewriter) { + req.url = pathRewriter(req.url); } - }); + proxy.ws(req, socket, head); + console.log('[HPM] Upgrading to WebSocket'); + } } function getProxyErrorHandler () { diff --git a/test/websocket.spec.js b/test/websocket.spec.js index a26ccb20..46bc544e 100644 --- a/test/websocket.spec.js +++ b/test/websocket.spec.js @@ -5,21 +5,23 @@ var express = require('express'); var WebSocket = require('ws'); var WebSocketServer = require('ws').Server; -describe('option.ws - WebSocket proxy', function () { +describe('WebSocket proxy', function () { var proxyServer, ws, wss; var targetHeaders; var responseMessage; + var proxy; beforeEach(function () { - proxyServer = createServer(3000, proxyMiddleware('/', { - target:'http://localhost:8000', - ws: true, - pathRewrite: { - '^/socket' : '' - } - })); + proxy = proxyMiddleware('/', { + target:'http://localhost:8000', + ws: true, + pathRewrite: {'^/socket' : ''} + }); + + proxyServer = createServer(3000, proxy); wss = new WebSocketServer({ port: 8000 }); + wss.on('connection', function connection(ws) { ws.on('message', function incoming(message) { ws.send(message); // echo received message @@ -27,37 +29,89 @@ describe('option.ws - WebSocket proxy', function () { }); }); - 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 + describe('option.ws', function () { + 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 () { - ws = new WebSocket('ws://localhost:3000/socket'); + // 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('message', function incoming(message) { + responseMessage = message; + done(); + }); - ws.on('open', function open() { - ws.send('foobar'); + ws.on('open', function open() { + ws.send('foobar'); + }); }); }); }); + it('should proxy to path', function () { + expect(responseMessage).to.equal('foobar'); + }); }); + describe('option.ws with external server "upgrade"', function () { + beforeEach(function (done) { + proxyServer.on('upgrade', proxy.upgrade); + + ws = new WebSocket('ws://localhost:3000/socket'); + + ws.on('message', function incoming(message) { + responseMessage = message; + done(); + }); + + ws.on('open', function open() { + ws.send('foobar'); + }); + }); + + it('should proxy to path', function () { + expect(responseMessage).to.equal('foobar'); + }); + }); + + // describe('option.ws with external server "upgrade" and shorthand usage', function () { + + // beforeEach(function () { + // proxyServer.close(); + // // override + // proxy = proxyMiddleware('ws://localhost:8000', {pathRewrite: {'^/socket' : ''}}); + // proxyServer = createServer(3000, proxy); + // }); + + // beforeEach(function (done) { + // proxyServer.on('upgrade', proxy.upgrade); + + // ws = new WebSocket('ws://localhost:3000/socket'); + + // ws.on('message', function incoming(message) { + // responseMessage = message; + // done(); + // }); + + // ws.on('open', function open() { + // ws.send('foobar'); + // }); + // }); + + // it('should proxy to path', function () { + // expect(responseMessage).to.equal('foobar'); + // }); + // }); + afterEach(function () { proxyServer.close(); wss.close(); ws = null; }); - it('should proxy to path', function () { - expect(responseMessage).to.equal('foobar'); - }); }); function createServer (portNumber, middleware) { From 1106e5b440e7a708c904ab10e84ea3818ba65a04 Mon Sep 17 00:00:00 2001 From: Steven Chim Date: Tue, 25 Aug 2015 22:18:28 +0200 Subject: [PATCH 2/4] fixed: websocket shorthand - proxyMiddleware('ws://localhost:8000'); --- lib/config-factory.js | 7 ++++- test/config-factory.spec.js | 15 ++++++++++ test/websocket.spec.js | 56 ++++++++++++++++++------------------- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/lib/config-factory.js b/lib/config-factory.js index 6fcc1e1e..ba73c601 100644 --- a/lib/config-factory.js +++ b/lib/config-factory.js @@ -18,8 +18,13 @@ function createConfig (context, opts) { var oUrl = url.parse(context); var target = [oUrl.protocol, '//', oUrl.host].join(''); - config.context = oUrl.pathname; + config.context = oUrl.pathname || '/'; config.options = _.assign(config.options, {target:target}, opts); + + if (oUrl.protocol === 'ws:') { + config.options.ws = true; + } + } else { config.context = context; config.options = _.assign(config.options, opts); diff --git a/test/config-factory.spec.js b/test/config-factory.spec.js index 2906a157..5d6ab570 100644 --- a/test/config-factory.spec.js +++ b/test/config-factory.spec.js @@ -55,6 +55,21 @@ describe('configFactory', function () { }); }); + describe('shorthand api for websocket url', function () { + beforeEach(function () { + result = configFactory.createConfig('ws://www.example.org:8000'); + }); + + it('should return on config object with context', function () { + expect(result.context).to.equal('/'); + }); + + it('should return on options with ws = true', function () { + console.log(result) + expect(result.options.ws).to.equal(true); + }); + }); + describe('shorthand api with globbing', function () { beforeEach(function () { result = configFactory.createConfig('http://www.example.org:8000/api/*.json'); diff --git a/test/websocket.spec.js b/test/websocket.spec.js index 46bc544e..3d0beee7 100644 --- a/test/websocket.spec.js +++ b/test/websocket.spec.js @@ -77,34 +77,34 @@ describe('WebSocket proxy', function () { }); }); - // describe('option.ws with external server "upgrade" and shorthand usage', function () { - - // beforeEach(function () { - // proxyServer.close(); - // // override - // proxy = proxyMiddleware('ws://localhost:8000', {pathRewrite: {'^/socket' : ''}}); - // proxyServer = createServer(3000, proxy); - // }); - - // beforeEach(function (done) { - // proxyServer.on('upgrade', proxy.upgrade); - - // ws = new WebSocket('ws://localhost:3000/socket'); - - // ws.on('message', function incoming(message) { - // responseMessage = message; - // done(); - // }); - - // ws.on('open', function open() { - // ws.send('foobar'); - // }); - // }); - - // it('should proxy to path', function () { - // expect(responseMessage).to.equal('foobar'); - // }); - // }); + describe('option.ws with external server "upgrade" and shorthand usage', function () { + + beforeEach(function () { + proxyServer.close(); + // override + proxy = proxyMiddleware('ws://localhost:8000', {pathRewrite: {'^/socket' : ''}}); + proxyServer = createServer(3000, proxy); + }); + + beforeEach(function (done) { + proxyServer.on('upgrade', proxy.upgrade); + + ws = new WebSocket('ws://localhost:3000/socket'); + + ws.on('message', function incoming(message) { + responseMessage = message; + done(); + }); + + ws.on('open', function open() { + ws.send('foobar'); + }); + }); + + it('should proxy to path', function () { + expect(responseMessage).to.equal('foobar'); + }); + }); afterEach(function () { proxyServer.close(); From afc4c41c27e6c78d4b1cb107f827782e861e7a9c Mon Sep 17 00:00:00 2001 From: Steven Chim Date: Tue, 25 Aug 2015 22:40:31 +0200 Subject: [PATCH 3/4] docs: external websocket http upgrade --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 7400c634..b3a67546 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,32 @@ proxyMiddleware('http://www.example.org:8000/api', {changeOrigin:true}); // proxyMiddleware('/api', {target: 'http://www.example.org:8000', changeOrigin: true}); ``` + +### WebSocket +```javascript +// verbose api +proxyMiddleware('/', {target:'http://echo.websocket.org', ws: true}); + +// shorthand +proxyMiddleware('http://echo.websocket.org', {ws:true}); + +// shorter shorthand +proxyMiddleware('ws://echo.websocket.org'); +``` + +#### External WebSocket upgrade +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. +```javascript +var proxy = proxyMiddleware('ws://echo.websocket.org', {changeOrigin:true}); + +var app = express(); + app.use(proxy); + +var server = app.listen(3000); + server.on('upgrade', proxy.upgrade); // <-- subscribe to http 'upgrade' +``` + + ### Options * **option.pathRewrite**: object, rewrite target's url path. Object-keys will be used as _RegExp_ to match paths. ```javascript From 9294ddc5e6b98b44711ff0a1f0322a46339e2e48 Mon Sep 17 00:00:00 2001 From: Steven Chim Date: Tue, 25 Aug 2015 22:43:16 +0200 Subject: [PATCH 4/4] fixed: shorthand wss: - proxyMiddleware('wss://localhost:3000'); --- lib/config-factory.js | 2 +- test/config-factory.spec.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/config-factory.js b/lib/config-factory.js index ba73c601..4802d19f 100644 --- a/lib/config-factory.js +++ b/lib/config-factory.js @@ -21,7 +21,7 @@ function createConfig (context, opts) { config.context = oUrl.pathname || '/'; config.options = _.assign(config.options, {target:target}, opts); - if (oUrl.protocol === 'ws:') { + if (oUrl.protocol === 'ws:' || oUrl.protocol === 'wss:') { config.options.ws = true; } diff --git a/test/config-factory.spec.js b/test/config-factory.spec.js index 5d6ab570..d6973198 100644 --- a/test/config-factory.spec.js +++ b/test/config-factory.spec.js @@ -65,7 +65,20 @@ describe('configFactory', function () { }); it('should return on options with ws = true', function () { - console.log(result) + expect(result.options.ws).to.equal(true); + }); + }); + + describe('shorthand api for secure websocket url', function () { + beforeEach(function () { + result = configFactory.createConfig('wss://www.example.org:8000'); + }); + + it('should return on config object with context', function () { + expect(result.context).to.equal('/'); + }); + + it('should return on options with ws = true', function () { expect(result.options.ws).to.equal(true); }); });