diff --git a/README.md b/README.md index 21e6d79dc..b72276798 100644 --- a/README.md +++ b/README.md @@ -772,7 +772,11 @@ The first argument can be either a `url` or an `options` object. The only requir --- -- `pool` - An object describing which agents to use for the request. If this option is omitted the request will use the global agent (as long as [your options allow for it](request.js#L747)). Otherwise, request will search the pool for your custom agent. If no custom agent is found, a new agent will be created and added to the pool. +- `agent` - `http(s).Agent` instance to use +- `agentClass` - alternatively specify your agent's class name +- `agentOptions` - and pass its options. **Note:** for HTTPS see [tls API doc for TLS/SSL options](http://nodejs.org/api/tls.html#tls_tls_connect_options_callback) and the [documentation above](#using-optionsagentoptions). +- `forever` - set to `true` to use the [forever-agent](https://github.com/request/forever-agent) **Note:** Defaults to `http(s).Agent({keepAlive:true})` in node 0.12+ +- `pool` - An object describing which agents to use for the request. If this option is omitted the request will use the global agent (as long as your options allow for it). Otherwise, request will search the pool for your custom agent. If no custom agent is found, a new agent will be created and added to the pool. **Note:** `pool` is used only when the `agent` option is not specified. - A `maxSockets` property can also be provided on the `pool` object to set the max number of sockets for all agents created (ex: `pool: {maxSockets: Infinity}`). - Note that if you are sending multiple requests in a loop and creating multiple new `pool` objects, `maxSockets` will not work as intended. To @@ -783,10 +787,12 @@ The first argument can be either a `url` or an `options` object. The only requir request to respond before aborting the request. Note that if the underlying TCP connection cannot be established, the OS-wide TCP connection timeout will overrule the `timeout` option ([the default in Linux is around 20 seconds](http://www.sekuda.com/overriding_the_default_linux_kernel_20_second_tcp_socket_connect_timeout)). + +--- + - `localAddress` - Local interface to bind for network connections. - `proxy` - An HTTP proxy to be used. Supports proxy Auth with Basic Auth, identical to support for the `url` parameter (by embedding the auth info in the `uri`) - `strictSSL` - If `true`, requires SSL certificates be valid. **Note:** to use your own certificate authority, you need to specify an agent that was created with that CA as an option. -- `agentOptions` - Object containing user agent options. See documentation above. **Note:** [see tls API doc for TLS/SSL options](http://nodejs.org/api/tls.html#tls_tls_connect_options_callback). - `tunnel` - controls the behavior of [HTTP `CONNECT` tunneling](https://en.wikipedia.org/wiki/HTTP_tunnel#HTTP_CONNECT_tunneling) as follows: @@ -803,9 +809,6 @@ The first argument can be either a `url` or an `options` object. The only requir --- - `time` - If `true`, the request-response cycle (including all redirects) is timed at millisecond resolution, and the result provided on the response's `elapsedTime` property. - ---- - - `har` - A [HAR 1.2 Request Object](http://www.softwareishard.com/blog/har-12-spec/#request), will be processed from HAR format into options overwriting matching values *(see the [HAR 1.2 section](#support-for-har-1.2) for details)* The callback argument gets 3 arguments: diff --git a/lib/helpers.js b/lib/helpers.js index 5cc79da86..5e8594606 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -54,6 +54,15 @@ function copy (obj) { return o } +function version () { + var numbers = process.version.replace('v', '').split('.') + return { + major: parseInt(numbers[0], 10), + minor: parseInt(numbers[1], 10), + patch: parseInt(numbers[2], 10) + } +} + exports.isFunction = isFunction exports.paramsHaveRequestBody = paramsHaveRequestBody exports.safeStringify = safeStringify @@ -61,4 +70,5 @@ exports.md5 = md5 exports.isReadStream = isReadStream exports.toBase64 = toBase64 exports.copy = copy +exports.version = version exports.defer = deferMethod() diff --git a/request.js b/request.js index e61db5e1b..6932741c5 100644 --- a/request.js +++ b/request.js @@ -31,6 +31,7 @@ var safeStringify = helpers.safeStringify , toBase64 = helpers.toBase64 , defer = helpers.defer , copy = helpers.copy + , version = helpers.version , globalCookieJar = cookies.jar() @@ -477,7 +478,16 @@ Request.prototype.init = function (options) { if (options.agentClass) { self.agentClass = options.agentClass } else if (options.forever) { - self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL + var v = version() + // use ForeverAgent in node 0.10- only + if (v.major === 0 && v.minor <= 10) { + self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL + } else { + self.agent = new self.httpModule.Agent({ + keepAlive: true, + maxSockets: (options.pool && options.pool.maxSockets) || Infinity + }) + } } else { self.agentClass = self.httpModule.Agent } diff --git a/tests/test-agent.js b/tests/test-agent.js index cca03d410..8d18d35c6 100644 --- a/tests/test-agent.js +++ b/tests/test-agent.js @@ -1,34 +1,103 @@ 'use strict' var request = require('../index') - , http = require('http') - , tape = require('tape') + , version = require('../lib/helpers').version + , http = require('http') + , ForeverAgent = require('forever-agent') + , tape = require('tape') -var s = http.createServer(function(req, res) { +var s = http.createServer(function (req, res) { res.statusCode = 200 - res.end('ok') + res.end() }) -tape('setup', function(t) { +tape('setup', function (t) { s.listen(6767, function() { t.end() }) }) -tape('should work with forever agent', function(t) { - var r = request.forever({maxSockets: 1}) +function httpAgent (t, options, req) { + var r = (req || request)(options, function (_err, res, body) { - r({ - url: 'http://localhost:6767', - headers: { 'Connection':'Close' } - }, function(err, resp, body) { - t.equal(err, null) - t.equal(body, 'ok') - t.end() + t.ok(r.agent instanceof http.Agent, 'is http.Agent') + t.equal(r.agent.options.keepAlive, true, 'is keepAlive') + t.equal(Object.keys(r.agent.sockets).length, 1, '1 socket name') + + var name = (typeof r.agent.getName === 'function') + ? r.agent.getName({port:6767}) + : 'localhost:6767' // node 0.10- + t.equal(r.agent.sockets[name].length, 1, '1 open socket') + + var socket = r.agent.sockets[name][0] + socket.on('close', function () { + t.equal(Object.keys(r.agent.sockets).length, 0, '0 open sockets') + t.end() + }) + socket.end() + }) +} + +function foreverAgent (t, options, req) { + var r = (req || request)(options, function (_err, res, body) { + + t.ok(r.agent instanceof ForeverAgent, 'is ForeverAgent') + t.equal(Object.keys(r.agent.sockets).length, 1, '1 socket name') + + var name = 'localhost:6767' // node 0.10- + t.equal(r.agent.sockets[name].length, 1, '1 open socket') + + var socket = r.agent.sockets[name][0] + socket.on('close', function () { + t.equal(Object.keys(r.agent.sockets[name]).length, 0, '0 open sockets') + t.end() + }) + socket.end() + }) +} + +// http.Agent + +tape('options.agent', function (t) { + httpAgent(t, { + uri: 'http://localhost:6767', + agent: new http.Agent({keepAlive: true}) }) }) -tape('cleanup', function(t) { +tape('options.agentClass + options.agentOptions', function (t) { + httpAgent(t, { + uri: 'http://localhost:6767', + agentClass: http.Agent, + agentOptions: {keepAlive: true} + }) +}) + +// forever-agent + +tape('options.forever = true', function (t) { + var v = version() + var options = { + uri: 'http://localhost:6767', + forever: true + } + + if (v.major === 0 && v.minor <= 10) {foreverAgent(t, options)} + else {httpAgent(t, options)} +}) + +tape('forever() method', function (t) { + var v = version() + var options = { + uri: 'http://localhost:6767' + } + var r = request.forever({maxSockets: 1}) + + if (v.major === 0 && v.minor <= 10) {foreverAgent(t, options, r)} + else {httpAgent(t, options, r)} +}) + +tape('cleanup', function (t) { s.close(function() { t.end() })