Show:
'use strict';

var https   = require('https');
var debug   = require('debug')('azure:agent');
var util    = require('util');

/*
 * Idle socket timeout, set to 55 seconds because the Azure load balancer will
 * silently drop connections after 60 s of idle time. And we won't detect the
 * drop until we try to send keep-alive packages, so to avoid making requests
 * on invalid connections we close connections after 55 s of idle time.
 */
var FREE_SOCKET_IDLE_TIMEOUT = 55 * 1000;

/*
 * Error handler that ignores errors which occurs while the socket is owned by
 * Agent. We don't want these errors to crash our application.
 */
function freeSocketErrorHandler(err) {
  debug("Error from idle socket owned by azure.Agent: %s", err.stack);
}

/*
 * Socket timeout handler for azure.Azure sockets, which destroys sockets that
 * haven't been destroyed. Used to close sockets after being idle for 55 s.
 */
function freeSocketTimeoutHandler() {
  if (!this.destroyed) {
    debug("Destroying free socket after idle timeout");
    this.destroy();
  }
}

/**
 * A https.Agent subclass for use with a Azure Storage Services. This agent
 * is a specialization of the https.Agent class with extra features:
 *  - catches socket errors from free sockets,
 *  - closes sockets after being idle for 55 seconds, and
 *  - disables TCP Nagle for all sockets (socket.setNoDelay).
 *
 * For details on Azure issues with ECONNRESET see:
 * [blog.gluwer.com](http://bit.ly/1HBuJK1).
 *
 * @class Agent
 * @extends https.Agent
 * @constructor
 * @param {object} options - `options` compatible with `http.Agent`.
 */
var Agent = function(options) {
  https.Agent.call(this, options);

  // Listen for free sockets
  this.on('free', function(socket) {
    // Ignore errors from free sockets
    socket.on('error', freeSocketErrorHandler);
    // Set idle timeout to avoid connection drops from azure
    socket.setTimeout(FREE_SOCKET_IDLE_TIMEOUT, freeSocketTimeoutHandler);
  });
};

// Subclass https.Agent
util.inherits(Agent, https.Agent);

/**
 * Overwrites the `addRequest` method so we can remove error handler and timeout
 * handler from sockets when they are given to a request.
 *
 * @private
 * @method addRequest
 */
Agent.prototype.addRequest = function addRequest(req, options) {
  req.once('socket', function(socket) {
    // Disable TCP Nagle for socket about to be used by the request
    socket.setNoDelay(true);
    socket.removeListener('error', freeSocketErrorHandler);
    socket.setTimeout(0, freeSocketTimeoutHandler);
  });
  return https.Agent.prototype.addRequest.call(this, req, options);
};

/**
 * Overwrites the `removeSocket` method so we can remove error handler and
 * timeout handler from sockets when they are removed the agent.
 *
 * @private
 * @method removeSocket
 */
Agent.prototype.removeSocket = function removeSocket(socket, options) {
  socket.removeListener('error', freeSocketErrorHandler);
  socket.setTimeout(0, freeSocketTimeoutHandler);
  return https.Agent.prototype.removeSocket.call(this, socket, options);
};

// Don't do anything for node 0.10
if (/^0\.10/.test(process.versions.node)) {
  Agent = https.Agent;
}

// Create global agent
if (/^0\.10/.test(process.versions.node)) {
  Agent.globalAgent = https.globalAgent;
} else {
  // Some relatively sane defaults, 100 is a bit high depending on hardware
  Agent.globalAgent = new Agent({
    keepAlive:      true,
    maxSockets:     100,
    maxFreeSockets: 100
  });
}

// Export Agent
module.exports = Agent;