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
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 18 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 () {
Expand Down
7 changes: 6 additions & 1 deletion lib/config-factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:' || oUrl.protocol === 'wss:') {
config.options.ws = true;
}

} else {
config.context = context;
config.options = _.assign(config.options, opts);
Expand Down
28 changes: 28 additions & 0 deletions test/config-factory.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,34 @@ 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 () {
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);
});
});

describe('shorthand api with globbing', function () {
beforeEach(function () {
result = configFactory.createConfig('http://www.example.org:8000/api/*.json');
Expand Down
102 changes: 78 additions & 24 deletions test/websocket.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,105 @@ 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
});
});
});

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 () {
Expand All @@ -55,9 +112,6 @@ describe('option.ws - WebSocket proxy', function () {
ws = null;
});

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

function createServer (portNumber, middleware) {
Expand Down