Permalink
Browse files

Add httpOptions to Config and allow HTTP timeout

    var s3 = new AWS.S3({httpOptions: {timeout: 500}});
    s3.client.getObject(params, function(err, data) {
      // err.code can be a TimeoutError
    });

httpOptions can also contain other HTTP level options, like `agent`,
which sets the `http.Agent` object to use in the request.

Creating a custom connection pool for a specific client over HTTP:

  var http = require('http');
  var agent = new http.Agent({maxSockets: 20}); // pool size = 20
  var options = {sslEnabled: false, httpOptions: {agent: agent}};
  var s3 = new AWS.S3(options);

Note that increasing the connection pool size over HTTPS connections
increases overhead for short-running processes.

Closes #5
  • Loading branch information...
1 parent 6ca207c commit 380009a6aeae3900097c130e0ea2f41e8bf5fb41 Loren Segal committed Mar 14, 2013
Showing with 55 additions and 7 deletions.
  1. +11 −0 lib/config.js
  2. +2 −1 lib/event_listeners.js
  3. +13 −1 lib/http.js
  4. +4 −0 test/config.spec.coffee
  5. +10 −0 test/event_listeners.spec.coffee
  6. +3 −3 test/helpers.coffee
  7. +12 −2 test/node_http_client.spec.coffee
View
@@ -91,6 +91,16 @@ AWS.Config = inherit({
* in S3 only)
* @option options s3ForcePathStyle [Boolean] whether to force path
* style URLs for S3 objects.
+ * @option options httpOptions [map] A set of options to pass to the low-level
+ * HTTP request. Currently supported options are:
+ *
+ * * **agent** [http.Agent, https.Agent] — the Agent object to perform
+ * HTTP requests with. Used for connection pooling. Defaults to the global
+ * agent (`http.globalAgent`) for non-SSL connections. Note that for
+ * SSL connections, a special Agent object is used in order to enable
+ * peer certificate verification.
+ * * **timeout** [Integer] — The number of milliseconds to wait before
+ * giving up on a connection attempt. Defaults to no timeout.
*/
constructor: function Config(options) {
if (options === undefined) options = {};
@@ -185,6 +195,7 @@ AWS.Config = inherit({
region: function() {
return process.env.AWS_REGION || process.env.AMAZON_REGION;
},
+ httpOptions: {},
maxRetries: undefined,
paramValidation: true,
sslEnabled: true,
View
@@ -165,7 +165,8 @@ AWS.EventListeners = {
}
var http = AWS.HttpClient.getInstance();
- http.handleRequest(this.httpRequest, callback, error);
+ var httpOptions = resp.request.client.config.httpOptions || {};
+ http.handleRequest(this.httpRequest, httpOptions, callback, error);
});
add('HTTP_HEADERS', 'httpHeaders',
View
@@ -163,7 +163,7 @@ AWS.HttpResponse = inherit({
* @api private
*/
AWS.NodeHttpClient = inherit({
- handleRequest: function handleRequest(httpRequest, callback, errCallback) {
+ handleRequest: function handleRequest(httpRequest, httpOptions, callback, errCallback) {
var useSSL = httpRequest.endpoint.protocol === 'https:';
var http = useSSL ? require('https') : require('http');
var options = {
@@ -178,7 +178,19 @@ AWS.NodeHttpClient = inherit({
options.agent = this.sslAgent(http);
}
+ AWS.util.update(options, httpOptions || {});
+ delete options.timeout; // timeout isn't an HTTP option
+
var stream = http.request(options, callback);
+
+ // timeout support
+ stream.setTimeout(httpOptions.timeout || 0);
+ stream.on('timeout', function() {
+ var msg = 'Connection timed out after ' + httpOptions.timeout + 'ms';
+ stream.end();
+ errCallback(AWS.util.error(new Error(msg), {code: 'TimeoutError'}));
+ })
+
stream.on('error', errCallback);
this.writeBody(stream, httpRequest);
return stream;
View
@@ -84,6 +84,10 @@ describe 'AWS.Config', ->
it 'can be set to false', ->
expect(configure(sslEnabled: false).sslEnabled).toEqual(false)
+ describe 'httpOptions', ->
+ it 'defaults to {}', ->
+ expect(configure().httpOptions).toEqual({})
+
describe 'set', ->
it 'should set a default value for a key', ->
config = new AWS.Config()
@@ -159,6 +159,16 @@ describe 'AWS.EventListeners', ->
response = request.send()
expect(response.error).toEqual('mockservice')
+ describe 'send', ->
+ it 'passes httpOptions from config', ->
+ options = {}
+ spyOn(AWS.HttpClient, 'getInstance').andReturn handleRequest: (req, opts) ->
+ options = opts
+ client.config.httpOptions = timeout: 15
+ client.config.maxRetries = 0
+ makeRequest(->)
+ expect(options.timeout).toEqual(15)
+
describe 'httpData', ->
beforeEach ->
helpers.mockHttpResponse 200, {}, ['FOO', 'BAR', 'BAZ', 'QUX']
View
@@ -30,7 +30,7 @@ AWS.EventListeners.Core.removeListener 'validate',
# TODO: refactor this out.
`setTimeout = function(fn, delay) { fn(); }`
-AWS.HttpClient.getInstance = -> throw new Error('Unmocked HTTP request')
+#AWS.HttpClient.getInstance = -> throw new Error('Unmocked HTTP request')
flattenXML = (xml) ->
if (!xml)
@@ -80,7 +80,7 @@ mockHttpSuccessfulResponse = (status, headers, data, cb) ->
mockHttpResponse = (status, headers, data) ->
stream = new EventEmitter()
spyOn(AWS.HttpClient, 'getInstance')
- AWS.HttpClient.getInstance.andReturn handleRequest: (req, cb, errCb) ->
+ AWS.HttpClient.getInstance.andReturn handleRequest: (req, opts, cb, errCb) ->
if typeof status == 'number'
mockHttpSuccessfulResponse status, headers, data, cb
else
@@ -91,7 +91,7 @@ mockHttpResponse = (status, headers, data) ->
mockIntermittentFailureResponse = (numFailures, status, headers, data) ->
retryCount = 0
spyOn(AWS.HttpClient, 'getInstance')
- AWS.HttpClient.getInstance.andReturn handleRequest: (req, cb, errCb) ->
+ AWS.HttpClient.getInstance.andReturn handleRequest: (req, opts, cb, errCb) ->
if retryCount < numFailures
retryCount += 1
errCb code: 'NetworkingError', message: 'FAIL!'
@@ -22,7 +22,7 @@ describe 'AWS.NodeHttpClient', ->
readSpy = spyOn(AWS.util, 'readFileSync').andCallThrough()
done = false
req = new AWS.HttpRequest 'https://invalid'
- runs -> http.handleRequest req, null, ->
+ runs -> http.handleRequest req, {}, null, ->
done = true
expect(AWS.NodeHttpClient.sslAgent).not.toEqual(null)
expect(readSpy.callCount).toEqual(1)
@@ -32,7 +32,17 @@ describe 'AWS.NodeHttpClient', ->
done = false
req = new AWS.HttpRequest 'http://invalid'
runs ->
- http.handleRequest req, null, (err) ->
+ http.handleRequest req, {}, null, (err) ->
expect(err.code).toEqual 'ENOTFOUND'
done = true
waitsFor -> done
+
+ it 'supports timeout in httpOptions', ->
+ done = false
+ req = new AWS.HttpRequest 'http://1.1.1.1'
+ runs ->
+ http.handleRequest req, {timeout: 12}, null, (err) ->
+ expect(err.code).toEqual 'TimeoutError'
+ expect(err.message).toEqual 'Connection timed out after 12ms'
+ done = true
+ waitsFor -> done

0 comments on commit 380009a

Please sign in to comment.