elasticsearch
Version:
The official low-level Elasticsearch client for Node.js and the browser.
219 lines (181 loc) • 5.68 kB
JavaScript
/**
* A Connection that operates using Node's http module
*
* @param client {Client} - The Client that this class belongs to
* @param config {Object} - Configuration options
* @param [config.protocol=http:] {String} - The HTTP protocol that this connection will use, can be set to https:
* @class HttpConnector
*/
module.exports = HttpConnector;
var handles = {
http: require('http'),
https: require('https'),
};
var _ = require('lodash');
var utils = require('../utils');
var parseUrl = require('url').parse;
var qs = require('querystring');
var AgentKeepAlive = require('agentkeepalive');
var ConnectionAbstract = require('../connection');
var zlib = require('zlib');
var INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;
/**
* Connector used to talk to an elasticsearch node via HTTP
*
* @param {Host} host - The host object representing the elasticsearch node we will be talking to
* @param {Object} [config] - Configuration options (extends the configuration options for ConnectionAbstract)
* @param {Number} [config.concurrency=10] - the maximum number of sockets that will be opened to this node
*/
function HttpConnector(host, config) {
ConnectionAbstract.call(this, host, config);
this.hand = handles[this.host.protocol];
if (!this.hand) {
throw new TypeError(
'Invalid protocol "' +
this.host.protocol +
'", expected one of ' +
_.keys(handles).join(', ')
);
}
this.useSsl = this.host.protocol === 'https';
config = _.defaults(config || {}, {
maxSockets: Infinity,
keepAlive: true,
keepAliveInterval: 1000,
keepAliveMaxFreeSockets: 256,
keepAliveFreeSocketTimeout: 60000,
});
this.agent = config.createNodeAgent
? config.createNodeAgent(this, config)
: this.createAgent(config);
}
utils.inherits(HttpConnector, ConnectionAbstract);
HttpConnector.prototype.onStatusSet = utils.handler(function(status) {
if (status === 'closed') {
var agent = this.agent;
var toRemove = [];
var collectSockets = function(sockets, host) {
_.each(sockets, function(s) {
if (s) toRemove.push([host, s]);
});
};
agent.minSockets = agent.maxSockets = 0;
agent.requests = {};
_.each(agent.sockets, collectSockets);
_.each(agent.freeSockets, collectSockets);
_.each(toRemove, function(args) {
var host = args[0];
var socket = args[1];
agent.removeSocket(socket, parseUrl(host));
socket.destroy();
});
}
});
HttpConnector.prototype.createAgent = function(config) {
var Agent = this.hand.Agent; // the class
if (config.forever) {
config.keepAlive = config.forever;
}
if (config.keepAlive) {
Agent = this.useSsl ? AgentKeepAlive.HttpsAgent : AgentKeepAlive;
this.on('status set', this.bound.onStatusSet);
}
return new Agent(this.makeAgentConfig(config));
};
HttpConnector.prototype.makeAgentConfig = function(config) {
var agentConfig = {
keepAlive: config.keepAlive,
keepAliveMsecs: config.keepAliveInterval,
maxSockets: config.maxSockets,
maxFreeSockets: config.keepAliveMaxFreeSockets,
freeSocketKeepAliveTimeout: config.keepAliveFreeSocketTimeout,
};
if (this.useSsl) {
_.merge(agentConfig, this.host.ssl);
}
return agentConfig;
};
HttpConnector.prototype.makeReqParams = function(params) {
params = params || {};
var host = this.host;
var reqParams = {
method: params.method || 'GET',
protocol: host.protocol + ':',
hostname: host.host,
port: host.port,
path: (host.path || '') + (params.path || ''),
headers: host.getHeaders(params.headers),
agent: this.agent,
};
if (!reqParams.path) {
reqParams.path = '/';
}
var query = host.getQuery(params.query);
if (query) {
reqParams.path = reqParams.path + '?' + qs.stringify(query);
}
return reqParams;
};
HttpConnector.prototype.request = function(params, cb) {
var incoming;
var timeoutId;
var request;
var status = 0;
var headers = {};
var log = this.log;
var response;
var reqParams = this.makeReqParams(params);
// general clean-up procedure to run after the request
// completes, has an error, or is aborted.
var cleanUp = _.bind(function(err) {
clearTimeout(timeoutId);
if (request) {
request.removeAllListeners();
}
if (incoming) {
incoming.removeAllListeners();
}
if (err instanceof Error === false) {
err = void 0;
}
log.trace(params.method, reqParams, params.body, response, status);
if (err) {
cb(err);
} else {
cb(err, response, status, headers);
}
}, this);
if (INVALID_PATH_REGEX.test(reqParams.path) === true) {
cb(new TypeError('ERR_UNESCAPED_CHARACTERS: ' + reqParams.path));
return function() {};
}
request = this.hand.request(reqParams, function(_incoming) {
incoming = _incoming;
status = incoming.statusCode;
headers = incoming.headers;
response = '';
var encoding = (headers['content-encoding'] || '').toLowerCase();
if (encoding === 'gzip' || encoding === 'deflate') {
incoming = incoming.pipe(zlib.createUnzip());
}
incoming.setEncoding('utf8');
incoming.on('data', function(d) {
response += d;
});
incoming.on('error', cleanUp);
incoming.on('end', cleanUp);
});
request.on('error', cleanUp);
request.setNoDelay(true);
request.setSocketKeepAlive(true);
if (params.body) {
request.setHeader('Content-Length', Buffer.byteLength(params.body, 'utf8'));
request.end(params.body);
} else {
request.setHeader('Content-Length', 0);
request.end();
}
return function() {
request.abort();
};
};