Skip to content

Commit

Permalink
Merge pull request #212 from wpreul/develop
Browse files Browse the repository at this point in the history
Adding proxy tests and doing a little refactoring
  • Loading branch information
Eran Hammer-Lahav committed Nov 2, 2012
2 parents 7c3d9e4 + 54237dd commit c55fded
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 36 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ REPORTER = dot

test:
@#lib-cov
@NODE_ENV=test ./node_modules/.bin/mocha --recursive --reporter $(REPORTER) --ignore-leaks
@NODE_ENV=test ./node_modules/.bin/mocha --recursive --reporter $(REPORTER) --ignore-leaks --timeout 3000
@#$(MAKE) rm-lib-cov

tests: test
Expand All @@ -27,7 +27,7 @@ tap: lib-cov

unit:
@#lib-cov
@NODE_ENV=test ./node_modules/.bin/mocha --recursive -R xunit --ignore-leaks > results.xml
@NODE_ENV=test ./node_modules/.bin/mocha --recursive -R xunit --ignore-leaks > results.xml --timeout 3000
@#$(MAKE) rm-lib-cov

.PHONY: test tap test-cv test-cov-html unit lib-cov rm-lib-cov
10 changes: 2 additions & 8 deletions lib/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exports = module.exports = internals.Error = function (code, message, options) {

Utils.assert(this.constructor === internals.Error, 'Error must be instantiated using new');
Utils.assert(!options || !options.toResponse || typeof options.toResponse === 'function', 'options.toReponse must be a function');
Utils.assert(code >= 400 && code < 600, 'Error code must be 4xx or 5xx');
Utils.assert(code >= 400, 'Error code must be 4xx or 5xx');

Error.call(this);

Expand Down Expand Up @@ -158,10 +158,4 @@ internals.Error.toResponse = function (err) {
}

return response;
};






};
47 changes: 26 additions & 21 deletions lib/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,19 @@ var internals = {};

exports = module.exports = internals.Proxy = function (options, route) {

var self = this;

Utils.assert(options, 'Missing options');
Utils.assert(options.host || options.mapUri, 'Missing options.host and no options.mapUri');
Utils.assert(!!options.host ^ !!options.mapUri, 'Must have either options.host or options.mapUri');
Utils.assert(!options.passThrough || !route.cache.isMode('server'), 'Cannot use pass-through proxy mode with caching');
Utils.assert(!options.mapUri || typeof options.mapUri === 'function', 'options.mapUri must be a function');
Utils.assert(!options.postResponse || typeof options.postResponse === 'function', 'options.postResponse must be a function');
Utils.assert(!options.hasOwnProperty('isCustomPostResponse'), 'Cannot manually set options.isCustomPostResponse');

this.settings = Utils.clone(options); // Options can be reused
this.settings.protocol = this.settings.protocol || 'http';
this.settings.port = this.settings.port || (this.settings.protocol === 'http' ? 80 : 443);
this.settings.xforward = this.settings.xforward || false;
this.settings.passHeaders = this.settings.passThrough || false;
this.settings.mapUri = this.settings.mapUri || internals.mapUri; // function (request, settings, function (err, uri, query))
this.settings.isCustomPostResponse = !!this.settings.postResponse;
this.settings.postResponse = this.settings.postResponse || internals.postResponse; // function (request, settings, response, payload)
this.settings = {};
this.settings.mapUri = options.mapUri || internals.mapUri(options.protocol, options.host, options.port);
this.settings.xforward = options.xforward || false;
this.settings.passHeaders = options.passThrough || false;
this.settings.isCustomPostResponse = !!options.postResponse;
this.settings.postResponse = options.postResponse || internals.postResponse; // function (request, settings, response, payload)

return this;
};
Expand All @@ -42,7 +38,7 @@ internals.Proxy.prototype.handler = function () {

return function (request) {

self.settings.mapUri(request, self.settings, function (err, uri, query) {
self.settings.mapUri(request, function (err, uri, query) {

if (err) {
return request.reply(err);
Expand All @@ -57,7 +53,7 @@ internals.Proxy.prototype.handler = function () {
headers: {}
};

if (self.settings.passThrough) { // Never set with cache
if (self.settings.passHeaders) { // Never set with cache
options.headers = Utils.clone(req.headers);
delete options.headers.host;
}
Expand Down Expand Up @@ -116,22 +112,31 @@ internals.Proxy.prototype.handler = function () {
};


internals.mapUri = function (request, settings, callback) {
internals.mapUri = function (protocol, host, port) {

protocol = protocol || 'http';
port = port || (protocol === 'http' ? 80 : 443);
var baseUrl = protocol + '://' + host + ':' + port;

return function(request, callback) {

return callback(null, settings.protocol + '://' + settings.host + ':' + settings.port + request.path, request.query);
return callback(null, baseUrl + request.path, request.query);
};
};


internals.postResponse = function (request, settings, response, payload) {

if (response.statusCode >= 400) {
return request.reply(Err.internal('Error proxy response', { code: response.statusCode, payload: payload }));
var contentType = response.headers['content-type'];
var statusCode = response.statusCode;

if (statusCode >= 400) {
return request.reply(Err.passThrough(statusCode, payload, contentType));
}

if (response.headers['content-type']) {
request.reply.type(response.headers['content-type']);
if (contentType) {
request.reply.type(contentType);
}

return request.reply(payload);
};

};
98 changes: 93 additions & 5 deletions test/integration/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ describe('Proxy', function () {
function startServer(done) {

var listening = false;
var config = null;

var routeCache = {
mode: 'server',
expiresIn: 500
};

Expand All @@ -32,13 +30,20 @@ describe('Proxy', function () {
var dummyServer = new Hapi.Server('0.0.0.0', 18093);
dummyServer.addRoutes([{ method: 'GET', path: '/profile', config: { handler: profile } },
{ method: 'GET', path: '/item', config: { handler: activeItem } },
{ method: 'POST', path: '/item', config: { handler: item } }]);
{ method: 'POST', path: '/item', config: { handler: item } },
{ method: 'GET', path: '/unauthorized', config: { handler: unauthorized }},
{ method: 'POST', path: '/echo', config: { handler: echoPostBody } }
]);

_server = new Hapi.Server('0.0.0.0', 18092, config);
_server.addRoutes([
{ method: 'GET', path: '/profile', config: { proxy: { host: '127.0.0.1', port: 18093, xforward: true, passThrough: true } } },
{ method: 'GET', path: '/item', config: { proxy: { host: '127.0.0.1', port: 18093 }, cache: routeCache } },
{ method: 'POST', path: '/item', config: { proxy: { host: '127.0.0.1', port: 18093 } } }
{ method: 'GET', path: '/unauthorized', config: { proxy: { host: '127.0.0.1', port: 18093 }, cache: routeCache } },
{ method: 'POST', path: '/item', config: { proxy: { host: '127.0.0.1', port: 18093 } } },
{ method: 'POST', path: '/notfound', config: { proxy: { host: '127.0.0.1', port: 18093 } } },
{ method: 'GET', path: '/postResponseError', config: { proxy: { host: '127.0.0.1', port: 18093, postResponse: postResponseWithError }, cache: routeCache } },
{ method: 'POST', path: '/echo', config: { proxy: { mapUri: mapUri } } }
]);

dummyServer.listener.on('listening', function () {
Expand All @@ -62,28 +67,58 @@ describe('Proxy', function () {
_server.start();
}

function mapUri(request, callback) {

return callback(null, 'http://127.0.0.1:18093' + request.path, request.query);
}

function profile(request) {

request.reply({
'id': 'fa0dbda9b1b',
'name': 'John Doe'
});
}

function activeItem(request) {

request.reply({
'id': '55cf687663',
'name': 'Active Item'
});
}

function item(request) {

request.reply.created('http://google.com')({
'id': '55cf687663',
'name': 'Item'
});
}

function echoPostBody(request) {

request.reply(request.payload);
}

function unauthorized(request) {

request.reply(Hapi.Error.unauthorized('Not authorized'));
}

function postResponseWithError(request) {

request.reply(Hapi.Error.forbidden('Forbidden'));
}

function postResponse(request, settings, response, payload) {

request.reply.type(response.headers['content-type']);
request.reply(payload);
}

function makeRequest(options, callback) {

var next = function (err, res) {
return callback(res);
};
Expand All @@ -94,31 +129,84 @@ describe('Proxy', function () {

Request({
method: options.method,
url: _serverUrl + options.path
url: _serverUrl + options.path,
form: options.form
}, next);
}

it('forwards on the response when making a GET request', function (done) {

makeRequest({ path: '/profile' }, function (rawRes) {

expect(rawRes.statusCode).to.equal(200);
expect(rawRes.body).to.contain('John Doe');
done();
});
});

it('forwards on the response when making a GET request to a route that also accepts a POST', function (done) {

makeRequest({ path: '/item' }, function (rawRes) {

expect(rawRes.statusCode).to.equal(200);
expect(rawRes.body).to.contain('Active Item');
done();
});
});

it('forwards on the status code when making a POST request', function (done) {

makeRequest({ path: '/item', method: 'post' }, function (rawRes) {

expect(rawRes.statusCode).to.equal(201);
expect(rawRes.body).to.contain('Item');
done();
});
});

it('sends the correct status code with a request is unauthorized', function(done) {

makeRequest({ path: '/unauthorized', method: 'get' }, function (rawRes) {

expect(rawRes.statusCode).to.equal(401);
done();
});
});

it('sends a 404 status code with a proxied route doesn\'t exist', function(done) {

makeRequest({ path: '/notfound', method: 'get' }, function (rawRes) {

expect(rawRes.statusCode).to.equal(404);
done();
});
});

it('forwards on the status code when a custom postResponse returns an error', function(done) {

makeRequest({ path: '/postResponseError', method: 'get' }, function (rawRes) {

expect(rawRes.statusCode).to.equal(403);
done();
});
});

it('forwards the error message with a custom postResponse and a route error', function(done) {

makeRequest({ path: '/postResponseNotFound', method: 'get' }, function (rawRes) {

expect(rawRes.body).to.contain('error');
done();
});
});

it('forwards on a POST body', function(done) {

makeRequest({ path: '/echo', method: 'post', form: { echo: true } }, function (rawRes) {

expect(rawRes.statusCode).to.equal(200);
expect(rawRes.body).to.contain('echo');
done();
});
});
});

0 comments on commit c55fded

Please sign in to comment.