Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting/using a default agent for all requests via defaults() #379

Merged
merged 4 commits into from
Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions lib/needle.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var version = require('../package.json').version;
var user_agent = 'Needle/' + version;
user_agent += ' (Node.js ' + process.version + '; ' + process.platform + ' ' + process.arch + ')';

var tls_options = 'agent pfx key passphrase cert ca ciphers rejectUnauthorized secureProtocol checkServerIdentity family';
var tls_options = 'pfx key passphrase cert ca ciphers rejectUnauthorized secureProtocol checkServerIdentity family';

// older versions of node (< 0.11.4) prevent the runtime from exiting
// because of connections in keep-alive state. so if this is the case
Expand Down Expand Up @@ -83,7 +83,8 @@ var defaults = {
parse_response : 'all', // same as true. valid options: 'json', 'xml' or false/null
proxy : null,

// headers
// agent & headers
agent : null,
headers : {},
accept : '*/*',
user_agent : user_agent,
Expand Down Expand Up @@ -233,6 +234,7 @@ Needle.prototype.setup = function(uri, options) {

var config = {
http_opts : {
agent: get_option('agent', defaults.agent),
localAddress: get_option('localAddress', undefined),
lookup: get_option('lookup', undefined)
}, // passed later to http.request() directly
Expand All @@ -254,9 +256,11 @@ Needle.prototype.setup = function(uri, options) {
// populate http_opts with given TLS options
tls_options.split(' ').forEach(function(key) {
if (typeof options[key] != 'undefined') {
config.http_opts[key] = options[key];
if (typeof options.agent == 'undefined')
config.http_opts.agent = false; // otherwise tls options are skipped
if (config.http_opts.agent) { // pass option to existing agent
config.http_opts.agent.options[key] = options[key];
} else {
config.http_opts[key] = options[key];
}
}
});

Expand Down Expand Up @@ -846,7 +850,7 @@ module.exports.defaults = function(obj) {
var target_key = aliased.options[key] || key;

if (defaults.hasOwnProperty(target_key) && typeof obj[key] != 'undefined') {
if (target_key != 'parse_response' && target_key != 'proxy') {
if (target_key != 'parse_response' && target_key != 'proxy' && target_key != 'agent') {
// ensure type matches the original, except for proxy/parse_response that can be null/bool or string
var valid_type = defaults[target_key].constructor.name;

Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

135 changes: 121 additions & 14 deletions test/headers_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var http = require('http'),

var port = 54321;


describe('request headers', function() {

var needle,
Expand Down Expand Up @@ -38,6 +39,7 @@ describe('request headers', function() {

describe('old node versions (<0.11.4) with persistent keep-alive connections', function() {

// emulate old node behaviour
before(function() {
delete require.cache[require.resolve('..')] // in case it was already loaded
original_defaultMaxSockets = http.Agent.defaultMaxSockets;
Expand Down Expand Up @@ -133,23 +135,47 @@ describe('request headers', function() {

describe('default options', function() {

// TODO:
// this is weird. by default, new node versions set a 'close' header
// while older versions set a keep-alive header
var node_major_ver = process.version.split('.')[0].replace('v', '');

it.skip('sets a Connection header', function(done) {
send_request({}, function(err, resp) {
// should.not.exist(resp.body.headers['connection']);
// done();
if (parseInt(node_major_ver) >= 4) {

it('sets Connection header to close (> v4)', function(done) {
send_request({}, function(err, resp) {
resp.body.headers['connection'].should.eql('close');
done()
})
})
})

it.skip('one open sockets remain after request', function(done) {
send_request({}, function(err, resp) {
// get_active_sockets().length.should.eql(1);
// done();
});
})
} else {

it('sets Connection header to keep-alive (< v4)', function(done) {
send_request({}, function(err, resp) {
resp.body.headers['connection'].should.eql('keep-alive');
done();
})
})

}

if (parseInt(node_major_ver) >= 8 || parseInt(node_major_ver) == 0) {

it('one open socket remains after request (> v8 && v0.10)', function(done) {
send_request({}, function(err, resp) {
get_active_sockets().length.should.eql(existing_sockets + 1);
done();
});
})

} else {

it('no open sockets remain after request (> v0.10 && < v8)', function(done) {
send_request({}, function(err, resp) {
get_active_sockets().length.should.eql(existing_sockets);
done();
});
})

}

})

Expand Down Expand Up @@ -200,4 +226,85 @@ describe('request headers', function() {

})

describe('using shared keep-alive agent', function() {

before(function() {
needle.defaults({ agent: http.Agent({ keepAlive: true }) })
})

after(function() {
needle.defaults().agent.destroy(); // close existing connections
needle.defaults({ agent: null }); // and reset default value
})

describe('default options', function() {

it('sends a Connection: keep-alive header', function(done) {
send_request({}, function(err, resp) {
resp.body.headers['connection'].should.eql('keep-alive');
done();
})
})

it('one open socket remain after request', function(done) {
send_request({ connection: 'keep-alive' }, function(err, resp) {
setTimeout(function() {
get_active_sockets().length.should.eql(existing_sockets + 1);
done();
}, 10);
});
})

})

describe('passing connection: close', function() {

it('sends a Connection: close header', function(done) {
send_request({ connection: 'close' }, function(err, resp) {
resp.body.headers['connection'].should.eql('close');
done();
})
})

it('no open sockets remain after request', function(done) {
send_request({ connection: 'close' }, function(err, resp) {
setTimeout(function() {
get_active_sockets().length.should.eql(existing_sockets);
done();
}, 10)
});
})

})

describe('passing connection: keep-alive', function() {

it('sends a Connection: keep-alive header (using options.headers.connection)', function(done) {
send_request({ headers: { connection: 'keep-alive' }}, function(err, resp) {
resp.body.headers['connection'].should.eql('keep-alive');
done();
})
})

it('sends a Connection: keep-alive header (using options.connection)', function(done) {
send_request({ connection: 'keep-alive' }, function(err, resp) {
resp.body.headers['connection'].should.eql('keep-alive');
done();
})
})

it('one open socket remain after request', function(done) {
send_request({ connection: 'keep-alive' }, function(err, resp) {
setTimeout(function() {
get_active_sockets().length.should.eql(existing_sockets + 1);
done();
}, 10);
})
})

})


})

})
7 changes: 7 additions & 0 deletions test/output_spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// this lets us run tests in ancient node versions (v0.10.x)
if (process.version.split('.')[0] == 'v0' && !Buffer.from) {
Buffer.from = function(args) {
return new Buffer(args);
}
}

var should = require('should'),
needle = require('./../'),
http = require('http'),
Expand Down
2 changes: 1 addition & 1 deletion test/redirect_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('redirects', function() {
url = protocol + '://' + host + ':' + ports[protocol] + '/hello';

function send_request(opts, cb) {
opts.rejectUnauthorized = false;
if (protocol == 'https') opts.rejectUnauthorized = false;
// console.log(' -- sending request ' + url + ' -- redirect to ' + location);
needle.post(url, { foo: 'bar' }, opts, cb);
}
Expand Down
57 changes: 57 additions & 0 deletions test/tls_options_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
var needle = require('..'),
https = require('https'),
helpers = require('./helpers'),
should = require('should');

describe('tls options', function() {

describe('rejectUnauthorized: false', function() {

var url = 'https://expired-rsa-dv.ssl.com/';

it('is an expired cert', function(done) {
needle.get(url, function(err, resp) {
err.code.should.eql('CERT_HAS_EXPIRED')
should.not.exist(resp)
done()
})
})

it('allows fetching pages under expired certificates', function(done) {
needle.get(url, { rejectUnauthorized: false }, function(err, resp) {
should.not.exist(err);
resp.statusCode.should.eql(200);
done()
})
})

it('also works when using custom agent', function(done) {
var agent = new https.Agent({ rejectUnauthorized: true })

// should overwrite value from custom agent
needle.get(url, { rejectUnauthorized: false }, function(err, resp) {
should.not.exist(err);
resp.statusCode.should.eql(200);
done()
})

})

it('also works with shared/default agent', function(done) {
var agent = new https.Agent({ rejectUnauthorized: true })
needle.defaults({ agent: agent })

// should overwrite value from custom agent
needle.get(url, { rejectUnauthorized: false }, function(err, resp) {
should.not.exist(err);
resp.statusCode.should.eql(200);

needle.defaults({ agent: null })
done()
})

})

})

})