hapi
Version:
HTTP Server framework
144 lines (97 loc) • 5.08 kB
JavaScript
// Load modules
var Request = require('request');
var Utils = require('./utils');
var Boom = require('boom');
// Declare internals
var internals = {};
// Create and configure server instance
exports = module.exports = internals.Proxy = function (options, route) {
Utils.assert(options, 'Missing options');
Utils.assert(!!options.host ^ !!options.mapUri, 'Must have either options.host or options.mapUri');
Utils.assert(!options.passThrough || !route.settings.cache.mode.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 = {};
this.settings.mapUri = options.mapUri || internals.mapUri(options.protocol, options.host, options.port);
this.settings.xforward = options.xforward || false;
this.settings.passThrough = options.passThrough || false;
this.settings.isCustomPostResponse = !!options.postResponse;
this.settings.postResponse = options.postResponse || internals.postResponse; // function (request, settings, response, payload)
return this;
};
internals.Proxy.prototype.httpClient = Request;
internals.Proxy.prototype.handler = function () {
var self = this;
return function (request) {
self.settings.mapUri(request, function (err, uri) {
if (err) {
return request.reply(err);
}
var req = request.raw.req;
var options = {
uri: uri,
method: request.method,
headers: {},
jar: false
};
if (self.settings.passThrough) { // Never set with cache
options.headers = Utils.clone(req.headers);
delete options.headers.host;
}
if (self.settings.xforward) {
options.headers['x-forwarded-for'] = (options.headers['x-forwarded-for'] ? options.headers['x-forwarded-for'] + ',' : '') + req.connection.remoteAddress || req.socket.remoteAddress;
options.headers['x-forwarded-port'] = (options.headers['x-forwarded-port'] ? options.headers['x-forwarded-port'] + ',' : '') + req.connection.remotePort || req.socket.remotePort;
options.headers['x-forwarded-proto'] = (options.headers['x-forwarded-proto'] ? options.headers['x-forwarded-proto'] + ',' : '') + self.settings.protocol;
}
var isGet = (request.method === 'get' || request.method === 'head');
// Parsed payload interface
if (self.settings.isCustomPostResponse || // Custom response method
(isGet && request.route.cache.mode.server)) { // GET/HEAD with Cache
delete options.headers['accept-encoding']; // Remove until Request supports unzip/deflate
self.httpClient(options, function (err, res, payload) {
// Request handles all redirect responses (3xx) and will return an err if redirection fails
if (err) {
return request.reply(Boom.internal('Proxy error', err));
}
return self.settings.postResponse(request, self.settings, res, payload);
});
return;
}
// Streamed payload interface
if (!isGet &&
request.rawPayload &&
request.rawPayload.length) {
options.headers['Content-Type'] = req.headers['content-type'];
options.body = request.rawPayload;
}
var reqStream = self.httpClient(options);
reqStream.on('response', function (resStream) {
request.reply(resStream); // Request._respond will pass-through headers and status code
});
if (!isGet &&
!request.rawPayload) {
request.raw.req.pipe(reqStream);
}
});
};
};
internals.mapUri = function (protocol, host, port) {
protocol = protocol || 'http';
port = port || (protocol === 'http' ? 80 : 443);
var baseUrl = protocol + '://' + host + ':' + port;
return function (request, next) {
return next(null, baseUrl + request.path + (request.url.search || ''));
};
};
internals.postResponse = function (request, settings, res, payload) {
var contentType = res.headers['content-type'];
var statusCode = res.statusCode;
if (statusCode >= 400) {
return request.reply(Boom.passThrough(statusCode, payload, contentType));
}
var response = request.reply(payload);
if (contentType) {
response.type(contentType);
}
};