Skip to content
Permalink
Browse files

feat(handlers) - add options: onProxyReqWs, onOpen, onClose

  • Loading branch information
chimurai committed Mar 9, 2016
1 parent e88d6e0 commit 6b18dc54cd6b45fc94199db5f9ce86fd9ef3d500
Showing with 151 additions and 43 deletions.
  1. +28 −6 README.md
  2. +5 −29 index.js
  3. +52 −3 lib/handlers.js
  4. +66 −5 test/handlers.spec.js
@@ -191,7 +191,7 @@ var server = app.listen(3000);
}
```

* **option.onError**: function, subscribe to http-proxy's error event for custom error handling.
* **option.onError**: function, subscribe to http-proxy's `error` event for custom error handling.
```javascript
function onError(err, req, res) {
res.writeHead(500, {
@@ -201,15 +201,15 @@ var server = app.listen(3000);
}
```

* **option.onProxyRes**: function, subscribe to http-proxy's proxyRes event.
* **option.onProxyRes**: function, subscribe to http-proxy's `proxyRes` event.
```javascript
function onProxyRes(proxyRes, req, res) {
proxyRes.headers['x-added'] = 'foobar'; // add new header to response
delete proxyRes.headers['x-removed']; // remove header from response
}
```

* **option.onProxyReq**: function, subscribe to http-proxy's proxyReq event.
* **option.onProxyReq**: function, subscribe to http-proxy's `proxyReq` event.
```javascript
function onProxyReq(proxyReq, req, res) {
// add custom header to request
@@ -218,6 +218,30 @@ var server = app.listen(3000);
}
```

* **option.onProxyReqWs**: function, subscribe to http-proxy's `proxyReqWs` event.
```javascript
function onProxyReqWs(proxyReq, req, socket, options, head) {
// add custom header
proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');
}
```

* **option.onOpen**: function, subscribe to http-proxy's `open` event.
```javascript
function onOpen(proxySocket) {
// listen for messages coming FROM the target here
proxySocket.on('data', hybiParseAndLogMessage);
}
```

* **option.onClose**: function, subscribe to http-proxy's `close` event.
```javascript
function onClose(res, socket, head) {
// view disconnected websocket connections
console.log('Client disconnected');
}
```

* (DEPRECATED) **option.proxyHost**: Use `option.changeOrigin = true` instead.

The following options are provided by the underlying [http-proxy](https://www.npmjs.com/package/http-proxy).
@@ -238,9 +262,7 @@ The following options are provided by the underlying [http-proxy](https://www.np
* **option.hostRewrite**: rewrites the location hostname on (301/302/307/308) redirects.
* **option.autoRewrite**: rewrites the location host/port on (301/302/307/308) redirects based on requested host/port. Default: false.
* **option.protocolRewrite**: rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null.

Undocumented options are provided by the underlying http-proxy
* **option.headers**: object, adds [request headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields). (Example: `{host:'www.example.org'}`) [source](https://github.com/nodejitsu/node-http-proxy/blob/master/examples/http/proxy-http-to-https.js#L41)
* **option.headers**: object, adds [request headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields). (Example: `{host:'www.example.org'}`)

## Recipes

@@ -19,27 +19,11 @@ var httpProxyMiddleware = function(context, opts) {

var pathRewriter = PathRewriter.create(proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided

// Custom listener for the `proxyRes` event on `proxy`.
if (_.isFunction(proxyOptions.onProxyRes)) {
proxy.on('proxyRes', proxyOptions.onProxyRes);
}

// Custom listener for the `proxyReq` event on `proxy`.
if (_.isFunction(proxyOptions.onProxyReq)) {
proxy.on('proxyReq', proxyOptions.onProxyReq);
}

// Custom listener for the `error` event on `proxy`.
var onProxyError = getProxyErrorHandler();
// handle error and close connection properly
proxy.on('error', onProxyError);
proxy.on('error', proxyErrorLogger);
// attach handler to http-proxy events
handlers.init(proxy, proxyOptions);

// Listen for the `close` event on `proxy`.
proxy.on('close', function(req, socket, head) {
// view disconnected websocket connections
logger.info('[HPM] Client disconnected');
});
// log errors for debug purpose
proxy.on('error', logError);

// https://github.com/chimurai/http-proxy-middleware/issues/19
// expose function to upgrade externally
@@ -128,15 +112,7 @@ var httpProxyMiddleware = function(context, opts) {
}
}

function getProxyErrorHandler() {
if (_.isFunction(proxyOptions.onError)) {
return proxyOptions.onError; // custom error listener
}

return handlers.proxyError; // otherwise fall back to default
}

function proxyErrorLogger(err, req, res) {
function logError(err, req, res) {
var hostname = (req.hostname || req.host) || (req.headers && req.headers.host); // (node0.10 || node 4/5) || (websocket)
var targetUri = (proxyOptions.target.host || proxyOptions.target) + req.url;

@@ -1,13 +1,62 @@
var _ = require('lodash');
var logger = require('./logger').getInstance();

module.exports = {
proxyError: proxyError
init: init,
getHandlers: getProxyEventHandlers
};

function proxyError(err, req, res) {
function init(proxy, opts) {
var handlers = getProxyEventHandlers(opts);

_.forIn(handlers, function(handler, eventName) {
proxy.on(eventName, handlers[eventName]);
});

logger.debug('[HPM] Subscribed to http-proxy events: ', _.keys(handlers));
}

function getProxyEventHandlers(opts) {
// https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events
var proxyEvents = ['error', 'proxyReq', 'proxyReqWs', 'proxyRes', 'open', 'close'];
var handlers = {};

_.forEach(proxyEvents, function(event) {
// all handlers for the http-proxy events are prefixed with 'on'.
// loop through options and try to find these handlers
// and add them to the handlers object for subscription in init().
var eventName = _.camelCase('on ' + event);
var fnHandler = _.get(opts, eventName);

if (_.isFunction(fnHandler)) {
handlers[event] = fnHandler;
}
});

// add default error handler in absence of error handler
if (!_.isFunction(handlers.error)) {
handlers.error = defaultErrorHandler;
}

// add default close handler in absence of close handler
if (!_.isFunction(handlers.close)) {
handlers.close = logClose;
}

return handlers;
};

function defaultErrorHandler(err, req, res) {
var host = (req.headers && req.headers.host);

if (res.writeHead && !res.headersSent) {
res.writeHead(500);
}

res.end('Error occured while trying to proxy to: ' + host + req.url);
};
}

function logClose(req, socket, head) {
// view disconnected websocket connections
logger.info('[HPM] Client disconnected');
}
@@ -1,7 +1,61 @@
var expect = require('chai').expect;
var handlers = require('../lib/handlers');

describe('handlers.proxyError(err, req, res, proxyOptions)', function() {
describe('handlers factory', function() {
var handlersMap;

it('should return default handlers when no handlers are provided', function() {
handlersMap = handlers.getHandlers();
expect(handlersMap.error).to.be.a('function');
expect(handlersMap.close).to.be.a('function');
});

describe('custom handlers', function() {
beforeEach(function() {
var fnCustom = function() {
return 42;
};

var proxyOptions = {
target: 'http://www.example.org',
onError: fnCustom,
onOpen: fnCustom,
onClose: fnCustom,
onProxyReq: fnCustom,
onProxyReqWs: fnCustom,
onProxyRes: fnCustom,
onDummy: fnCustom,
foobar: fnCustom
};

handlersMap = handlers.getHandlers(proxyOptions);
});

it('should only return http-proxy handlers', function() {
expect(handlersMap.error).to.be.a('function');
expect(handlersMap.open).to.be.a('function');
expect(handlersMap.close).to.be.a('function');
expect(handlersMap.proxyReq).to.be.a('function');
expect(handlersMap.proxyReqWs).to.be.a('function');
expect(handlersMap.proxyRes).to.be.a('function');
expect(handlersMap.dummy).to.be.undefined;
expect(handlersMap.foobar).to.be.undefined;
expect(handlersMap.target).to.be.undefined;
});

it('should use the provided custom handlers', function() {
expect(handlersMap.error()).to.equal(42);
expect(handlersMap.open()).to.equal(42);
expect(handlersMap.close()).to.equal(42);
expect(handlersMap.proxyReq()).to.equal(42);
expect(handlersMap.proxyReqWs()).to.equal(42);
expect(handlersMap.proxyRes()).to.equal(42);
});

});
});

describe('default proxy error handler', function() {

var mockError = {
code: 'ECONNREFUSED'
@@ -35,30 +89,37 @@ describe('handlers.proxyError(err, req, res, proxyOptions)', function() {
headersSent: false
};

var proxyError;

beforeEach(function() {
var handlersMap = handlers.getHandlers();
proxyError = handlersMap.error;
});

afterEach(function() {
httpErrorCode = undefined;
errorMessage = undefined;
});

it('should set the http status code to: 500', function() {
handlers.proxyError(mockError, mockReq, mockRes, proxyOptions);
proxyError(mockError, mockReq, mockRes, proxyOptions);
expect(httpErrorCode).to.equal(500);
});

it('should end the response and return error message', function() {
handlers.proxyError(mockError, mockReq, mockRes, proxyOptions);
proxyError(mockError, mockReq, mockRes, proxyOptions);
expect(errorMessage).to.equal('Error occured while trying to proxy to: localhost:3000/api');
});

it('should not set the http status code to: 500 if headers have already been sent', function() {
mockRes.headersSent = true;
handlers.proxyError(mockError, mockReq, mockRes, proxyOptions);
proxyError(mockError, mockReq, mockRes, proxyOptions);
expect(httpErrorCode).to.equal(undefined);
});

it('should end the response and return error message', function() {
mockRes.headersSent = true;
handlers.proxyError(mockError, mockReq, mockRes, proxyOptions);
proxyError(mockError, mockReq, mockRes, proxyOptions);
expect(errorMessage).to.equal('Error occured while trying to proxy to: localhost:3000/api');
});

0 comments on commit 6b18dc5

Please sign in to comment.
You can’t perform that action at this time.