From 62e4f897655aedf22b562a80c9e572c7592fe0ac Mon Sep 17 00:00:00 2001 From: Timothy J Fontaine Date: Thu, 11 Apr 2013 14:47:15 -0700 Subject: [PATCH] http: split Agent into its own file --- lib/_http_agent.js | 156 +++++++++++++++++++++++++++++++++++++++++++++ lib/http.js | 133 +------------------------------------- node.gyp | 1 + 3 files changed, 160 insertions(+), 130 deletions(-) create mode 100644 lib/_http_agent.js diff --git a/lib/_http_agent.js b/lib/_http_agent.js new file mode 100644 index 00000000000..7ad5c7ec56a --- /dev/null +++ b/lib/_http_agent.js @@ -0,0 +1,156 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var net = require('net'); +var util = require('util'); +var EventEmitter = require('events').EventEmitter; + +// New Agent code. + +// The largest departure from the previous implementation is that +// an Agent instance holds connections for a variable number of host:ports. +// Surprisingly, this is still API compatible as far as third parties are +// concerned. The only code that really notices the difference is the +// request object. + +// Another departure is that all code related to HTTP parsing is in +// ClientRequest.onSocket(). The Agent is now *strictly* +// concerned with managing a connection pool. + +function Agent(options) { + EventEmitter.call(this); + + var self = this; + self.options = options || {}; + self.requests = {}; + self.sockets = {}; + self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets; + self.on('free', function(socket, host, port, localAddress) { + var name = host + ':' + port; + if (localAddress) { + name += ':' + localAddress; + } + + if (!socket.destroyed && + self.requests[name] && self.requests[name].length) { + self.requests[name].shift().onSocket(socket); + if (self.requests[name].length === 0) { + // don't leak + delete self.requests[name]; + } + } else { + // If there are no pending requests just destroy the + // socket and it will get removed from the pool. This + // gets us out of timeout issues and allows us to + // default to Connection:keep-alive. + socket.destroy(); + } + }); + self.createConnection = net.createConnection; +} +util.inherits(Agent, EventEmitter); +exports.Agent = Agent; + +Agent.defaultMaxSockets = 5; + +Agent.prototype.defaultPort = 80; +Agent.prototype.addRequest = function(req, host, port, localAddress) { + var name = host + ':' + port; + if (localAddress) { + name += ':' + localAddress; + } + if (!this.sockets[name]) { + this.sockets[name] = []; + } + if (this.sockets[name].length < this.maxSockets) { + // If we are under maxSockets create a new one. + req.onSocket(this.createSocket(name, host, port, localAddress, req)); + } else { + // We are over limit so we'll add it to the queue. + if (!this.requests[name]) { + this.requests[name] = []; + } + this.requests[name].push(req); + } +}; +Agent.prototype.createSocket = function(name, host, port, localAddress, req) { + var self = this; + var options = util._extend({}, self.options); + options.port = port; + options.host = host; + options.localAddress = localAddress; + + options.servername = host; + if (req) { + var hostHeader = req.getHeader('host'); + if (hostHeader) { + options.servername = hostHeader.replace(/:.*$/, ''); + } + } + + var s = self.createConnection(options); + if (!self.sockets[name]) { + self.sockets[name] = []; + } + this.sockets[name].push(s); + var onFree = function() { + self.emit('free', s, host, port, localAddress); + } + s.on('free', onFree); + var onClose = function(err) { + // This is the only place where sockets get removed from the Agent. + // If you want to remove a socket from the pool, just close it. + // All socket errors end in a close event anyway. + self.removeSocket(s, name, host, port, localAddress); + } + s.on('close', onClose); + var onRemove = function() { + // We need this function for cases like HTTP 'upgrade' + // (defined by WebSockets) where we need to remove a socket from the pool + // because it'll be locked up indefinitely + self.removeSocket(s, name, host, port, localAddress); + s.removeListener('close', onClose); + s.removeListener('free', onFree); + s.removeListener('agentRemove', onRemove); + } + s.on('agentRemove', onRemove); + return s; +}; +Agent.prototype.removeSocket = function(s, name, host, port, localAddress) { + if (this.sockets[name]) { + var index = this.sockets[name].indexOf(s); + if (index !== -1) { + this.sockets[name].splice(index, 1); + if (this.sockets[name].length === 0) { + // don't leak + delete this.sockets[name]; + } + } + } + if (this.requests[name] && this.requests[name].length) { + var req = this.requests[name][0]; + // If we have pending requests and a socket gets closed a new one + this.createSocket(name, host, port, localAddress, req).emit('free'); + } +}; + +var globalAgent = new Agent(); +exports.globalAgent = globalAgent; diff --git a/lib/http.js b/lib/http.js index f6a9b7d4f6d..8536773c0f6 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1157,137 +1157,10 @@ ServerResponse.prototype.writeHeader = function() { }; -// New Agent code. +var agent = require('_http_agent'); -// The largest departure from the previous implementation is that -// an Agent instance holds connections for a variable number of host:ports. -// Surprisingly, this is still API compatible as far as third parties are -// concerned. The only code that really notices the difference is the -// request object. - -// Another departure is that all code related to HTTP parsing is in -// ClientRequest.onSocket(). The Agent is now *strictly* -// concerned with managing a connection pool. - -function Agent(options) { - EventEmitter.call(this); - - var self = this; - self.options = options || {}; - self.requests = {}; - self.sockets = {}; - self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets; - self.on('free', function(socket, host, port, localAddress) { - var name = host + ':' + port; - if (localAddress) { - name += ':' + localAddress; - } - - if (!socket.destroyed && - self.requests[name] && self.requests[name].length) { - self.requests[name].shift().onSocket(socket); - if (self.requests[name].length === 0) { - // don't leak - delete self.requests[name]; - } - } else { - // If there are no pending requests just destroy the - // socket and it will get removed from the pool. This - // gets us out of timeout issues and allows us to - // default to Connection:keep-alive. - socket.destroy(); - } - }); - self.createConnection = net.createConnection; -} -util.inherits(Agent, EventEmitter); -exports.Agent = Agent; - -Agent.defaultMaxSockets = 5; - -Agent.prototype.defaultPort = 80; -Agent.prototype.addRequest = function(req, host, port, localAddress) { - var name = host + ':' + port; - if (localAddress) { - name += ':' + localAddress; - } - if (!this.sockets[name]) { - this.sockets[name] = []; - } - if (this.sockets[name].length < this.maxSockets) { - // If we are under maxSockets create a new one. - req.onSocket(this.createSocket(name, host, port, localAddress, req)); - } else { - // We are over limit so we'll add it to the queue. - if (!this.requests[name]) { - this.requests[name] = []; - } - this.requests[name].push(req); - } -}; -Agent.prototype.createSocket = function(name, host, port, localAddress, req) { - var self = this; - var options = util._extend({}, self.options); - options.port = port; - options.host = host; - options.localAddress = localAddress; - - options.servername = host; - if (req) { - var hostHeader = req.getHeader('host'); - if (hostHeader) { - options.servername = hostHeader.replace(/:.*$/, ''); - } - } - - var s = self.createConnection(options); - if (!self.sockets[name]) { - self.sockets[name] = []; - } - this.sockets[name].push(s); - var onFree = function() { - self.emit('free', s, host, port, localAddress); - } - s.on('free', onFree); - var onClose = function(err) { - // This is the only place where sockets get removed from the Agent. - // If you want to remove a socket from the pool, just close it. - // All socket errors end in a close event anyway. - self.removeSocket(s, name, host, port, localAddress); - } - s.on('close', onClose); - var onRemove = function() { - // We need this function for cases like HTTP 'upgrade' - // (defined by WebSockets) where we need to remove a socket from the pool - // because it'll be locked up indefinitely - self.removeSocket(s, name, host, port, localAddress); - s.removeListener('close', onClose); - s.removeListener('free', onFree); - s.removeListener('agentRemove', onRemove); - } - s.on('agentRemove', onRemove); - return s; -}; -Agent.prototype.removeSocket = function(s, name, host, port, localAddress) { - if (this.sockets[name]) { - var index = this.sockets[name].indexOf(s); - if (index !== -1) { - this.sockets[name].splice(index, 1); - if (this.sockets[name].length === 0) { - // don't leak - delete this.sockets[name]; - } - } - } - if (this.requests[name] && this.requests[name].length) { - var req = this.requests[name][0]; - // If we have pending requests and a socket gets closed a new one - this.createSocket(name, host, port, localAddress, req).emit('free'); - } -}; - -var globalAgent = new Agent(); -exports.globalAgent = globalAgent; +var Agent = exports.Agent = agent.Agent; +var globalAgent = exports.globalAgent = agent.globalAgent; function ClientRequest(options, cb) { diff --git a/node.gyp b/node.gyp index 4bf1ba762e8..a9ff8d9592b 100644 --- a/node.gyp +++ b/node.gyp @@ -34,6 +34,7 @@ 'lib/freelist.js', 'lib/fs.js', 'lib/http.js', + 'lib/_http_agent.js', 'lib/https.js', 'lib/module.js', 'lib/net.js',