UNPKG

hapi

Version:

HTTP Server framework

382 lines (270 loc) 13.5 kB
// Load modules var Lab = require('lab'); var Fs = require('fs'); var Request = require('request'); var Zlib = require('zlib'); var Hapi = require('../..'); // Declare internals var internals = {}; // Test shortcuts var expect = Lab.expect; var before = Lab.before; var after = Lab.after; var describe = Lab.experiment; var it = Lab.test; describe('Proxy', function () { var server = null; before(function (done) { // Define backend handlers var mapUriWithError = function (request, callback) { return callback(new Error('myerror')); }; var profile = function () { if (this.state.test) { return this.reply('error'); } var profile = { 'id': 'fa0dbda9b1b', 'name': 'John Doe' }; this.reply(profile).state('test', '123'); }; var activeItem = function () { this.reply({ 'id': '55cf687663', 'name': 'Active Item' }); }; var item = function () { this.reply({ 'id': '55cf687663', 'name': 'Item' }).created('http://example.com'); }; var echoPostBody = function () { this.reply(this.payload); }; var unauthorized = function () { this.reply(Hapi.error.unauthorized('Not authorized')); }; var postResponseWithError = function (request) { request.reply(Hapi.error.forbidden('Forbidden')); }; var streamHandler = function () { this.reply('success'); }; var forward = function () { expect(this.raw.req.headers['x-forwarded-for']).to.contain('xforwardfor'); expect(this.raw.req.headers['x-forwarded-port']).to.contain('9000'); expect(this.raw.req.headers['x-forwarded-proto']).to.contain('xforwardproto'); this.reply('Success'); }; var headers = function () { this.reply({ status: 'success' }) .header('Custom1', 'custom header value 1') .header('X-Custom2', 'custom header value 2') .header('access-control-allow-headers', 'Invalid, List, Of, Values'); }; var gzipHandler = function () { this.reply('123456789012345678901234567890123456789012345678901234567890'); }; var gzipStreamHandler = function () { this.reply(new Hapi.response.File(__dirname + '/../../package.json')); }; var upstream = new Hapi.Server(0); upstream.route([ { method: 'GET', path: '/profile', handler: profile }, { method: 'GET', path: '/item', handler: activeItem }, { method: 'GET', path: '/proxyerror', handler: activeItem }, { method: 'POST', path: '/item', handler: item }, { method: 'GET', path: '/unauthorized', handler: unauthorized }, { method: 'POST', path: '/file', handler: streamHandler, config: { payload: 'stream' } }, { method: 'POST', path: '/echo', handler: echoPostBody }, { method: 'GET', path: '/headers', handler: headers }, { method: 'GET', path: '/noHeaders', handler: headers }, { method: 'GET', path: '/forward', handler: forward }, { method: 'GET', path: '/gzip', handler: gzipHandler }, { method: 'GET', path: '/gzipstream', handler: gzipStreamHandler } ]); var mapUri = function (request, callback) { return callback(null, upstream.info.uri + request.path + (request.url.search || '')); }; upstream.start(function () { var backendPort = upstream.info.port; var routeCache = { expiresIn: 500, mode: 'server+client' }; server = new Hapi.Server(0, { cors: true }); server.route([ { method: 'GET', path: '/profile', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } }, { method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } }, { method: 'GET', path: '/unauthorized', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } }, { method: 'POST', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } } }, { method: 'POST', path: '/notfound', handler: { proxy: { host: 'localhost', port: backendPort } } }, { method: 'GET', path: '/proxyerror', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } }, { method: 'GET', path: '/postResponseError', handler: { proxy: { host: 'localhost', port: backendPort, postResponse: postResponseWithError } }, config: { cache: routeCache } }, { method: 'GET', path: '/errorResponse', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } }, { method: 'POST', path: '/echo', handler: { proxy: { mapUri: mapUri } } }, { method: 'POST', path: '/file', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { payload: 'stream' } }, { method: 'GET', path: '/maperror', handler: { proxy: { mapUri: mapUriWithError } } }, { method: 'GET', path: '/forward', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } }, { method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } }, { method: 'GET', path: '/noHeaders', handler: { proxy: { host: 'localhost', port: backendPort } } }, { method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } }, { method: 'GET', path: '/gzipstream', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } } ]); server.state('auto', { autoValue: 'xyz' }); server.start(function () { done(); }); }); }); function makeRequest(options, callback) { options = options || {}; options.path = options.path || '/'; options.method = options.method || 'get'; var req = { method: options.method, url: server.info.uri + options.path, form: options.form, headers: options.headers, jar: false }; Request(req, function (err, res) { callback(res); }); } 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'); expect(rawRes.headers['set-cookie']).to.deep.equal(['test=123','auto=xyz']); makeRequest({ path: '/profile' }, function (rawRes) { expect(rawRes.statusCode).to.equal(200); expect(rawRes.body).to.contain('John Doe'); done(); }); }); }); it('forwards on x-forward headers', function (done) { makeRequest({ path: '/forward', headers: { 'x-forwarded-for': 'xforwardfor', 'x-forwarded-port': '9000', 'x-forwarded-proto': 'xforwardproto' } }, function (rawRes) { expect(rawRes.statusCode).to.equal(200); expect(rawRes.body).to.equal('Success'); done(); }); }); it('forwards upstream headers', function (done) { server.inject({ url: '/headers', method: 'GET' }, function (res) { expect(res.statusCode).to.equal(200); expect(res.result).to.equal('{\"status\":\"success\"}'); expect(res.headers.custom1).to.equal('custom header value 1'); expect(res.headers['x-custom2']).to.equal('custom header value 2'); expect(res.headers['access-control-allow-headers']).to.equal('Authorization, Content-Type, If-None-Match'); expect(res.headers['access-control-expose-headers']).to.equal('WWW-Authenticate, Server-Authorization'); done(); }); }); it('forwards gzipped content', function (done) { Zlib.gzip(new Buffer('123456789012345678901234567890123456789012345678901234567890'), function (err, zipped) { makeRequest({ path: '/gzip', method: 'GET', headers: { 'accept-encoding': 'gzip' } }, function (res) { expect(res.statusCode).to.equal(200); expect(res.body).to.equal(zipped.toString()); done(); }); }); }); it('forwards gzipped stream', function (done) { makeRequest({ path: '/gzipstream', method: 'GET', headers: { 'accept-encoding': 'gzip' } }, function (res) { expect(res.statusCode).to.equal(200); Fs.readFile(__dirname + '/../../package.json', function (err, file) { Zlib.gzip(file, function (err, zipped) { expect(zipped.toString()).to.equal(res.body); done(); }); }); }); }); it('does not forward upstream headers without passThrough', function (done) { makeRequest({ path: '/noHeaders' }, function (rawRes) { expect(rawRes.statusCode).to.equal(200); expect(rawRes.body).to.equal('{\"status\":\"success\"}'); expect(rawRes.headers.custom1).to.not.exist; expect(rawRes.headers['x-custom2']).to.not.exist; 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('handles an error from request safely', function (done) { var requestStub = function (options, callback) { callback(new Error()); }; var route = server._router.route({ method: 'get', path: '/proxyerror', info: {}, raw: { req: { headers: {} } } }); route.proxy.httpClient = requestStub; makeRequest({ path: '/proxyerror', method: 'get' }, function (rawRes) { expect(rawRes.statusCode).to.equal(500); 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(); }); }); it('replies with an error when it occurs in mapUri', function (done) { makeRequest({ path: '/maperror', method: 'get' }, function (rawRes) { expect(rawRes.body).to.contain('myerror'); done(); }); }); it('works with a stream when the proxy response is streamed', function (done) { Fs.createReadStream(__dirname + '/proxy.js').pipe(Request.post(server.info.uri + '/file', function (err, rawRes, body) { expect(rawRes.statusCode).to.equal(200); done(); })); }); it('can add a proxy route with a http protocol set', function (done) { server.route({ method: 'GET', path: '/httpport', handler: { proxy: { host: 'localhost', protocol: 'http' } } }); done(); }); it('can add a proxy route with a https protocol set', function (done) { server.route({ method: 'GET', path: '/httpsport', handler: { proxy: { host: 'localhost', protocol: 'https' } } }); done(); }); });