Comparing version 0.11.0 to 1.0.0
@@ -10,15 +10,11 @@ // | ||
// RegExps | ||
const COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/; | ||
const EXCLUDED_CHARS = /[\x00-\x1F\x7F\x3B\x3B\s\"\,\\"%]/g; | ||
const TRAILING_SEMICOLON = /\x3B+$/; | ||
const SEP_SEMICOLON = /\s*\x3B\s*/; | ||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/; | ||
var EXCLUDED_CHARS = /[\x00-\x1F\x7F\x3B\x3B\s\"\,\\"%]/g; | ||
var TRAILING_SEMICOLON = /\x3B+$/; | ||
var SEP_SEMICOLON = /\s*\x3B\s*/; | ||
// Constants | ||
const KEY_INDEX = 1; // index of key from COOKIE_PAIR match | ||
const VALUE_INDEX = 3; // index of value from COOKIE_PAIR match | ||
var KEY_INDEX = 1; // index of key from COOKIE_PAIR match | ||
var VALUE_INDEX = 3; // index of value from COOKIE_PAIR match | ||
// Convenience functions | ||
// Returns a copy str trimmed and without trainling semicolon. | ||
@@ -34,4 +30,2 @@ function cleanCookieString(str) { | ||
// Private functions | ||
// Returns a encoded copy of str based on RFC6265 S4.1.1. | ||
@@ -48,11 +42,12 @@ function encodeCookieComponent(str) { | ||
var res = COOKIE_PAIR.exec(str); | ||
if (!res || !res[VALUE_INDEX]) return null; | ||
return { | ||
name: decodeURIComponent(res[KEY_INDEX]), | ||
value: decodeURIComponent(res[VALUE_INDEX]) | ||
name : decodeURIComponent(res[KEY_INDEX]), | ||
value : decodeURIComponent(res[VALUE_INDEX]) | ||
}; | ||
} | ||
// Parses a set-cookie-header and returns a key/value object. Each key | ||
// represent a name of a cookie. | ||
// Parses a set-cookie-header and returns a key/value object. | ||
// Each key represents the name of a cookie. | ||
function parseSetCookieHeader(header) { | ||
@@ -63,3 +58,3 @@ header = (header instanceof Array) ? header : [header]; | ||
var cookie = parseSetCookieString(str); | ||
res[cookie.name] = cookie.value; | ||
if (cookie) res[cookie.name] = cookie.value; | ||
return res; | ||
@@ -72,11 +67,9 @@ }, {}); | ||
return Object.keys(obj).reduce(function(str, name) { | ||
var encodedName = encodeCookieComponent(name); | ||
var encodedName = encodeCookieComponent(name); | ||
var encodedValue = encodeCookieComponent(obj[name]); | ||
str += (str ? "; " : "") + encodedName + '=' + encodedValue; | ||
str += (str ? '; ' : '') + encodedName + '=' + encodedValue; | ||
return str; | ||
}, ""); | ||
}, ''); | ||
} | ||
// Module interface | ||
// returns a key/val object from an array of cookie strings | ||
@@ -86,2 +79,2 @@ exports.read = parseSetCookieHeader; | ||
// writes a cookie string header | ||
exports.write = writeCookieString; | ||
exports.write = writeCookieString; |
@@ -6,3 +6,3 @@ var readFile = require('fs').readFile, | ||
if (typeof data != 'object') | ||
if (typeof data != 'object' || typeof data.pipe == 'function') | ||
return callback(new Error('Multipart builder expects data as key/val object.')); | ||
@@ -9,0 +9,0 @@ |
@@ -29,4 +29,9 @@ ////////////////////////////////////////// | ||
var tls_options = 'agent pfx key passphrase cert ca ciphers rejectUnauthorized secureProtocol'; | ||
var tls_options = 'agent pfx key passphrase cert ca ciphers rejectUnauthorized secureProtocol checkServerIdentity'; | ||
// 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 | ||
// we'll default new requests to set a Connection: close header. | ||
var close_by_default = !http.Agent || http.Agent.defaultMaxSockets != Infinity; | ||
////////////////////////////////////////// | ||
@@ -59,3 +64,2 @@ // decompressors for gzip/deflate bodies | ||
accept : '*/*', | ||
connection : 'close', | ||
user_agent : user_agent, | ||
@@ -184,6 +188,9 @@ | ||
'Accept' : options.accept || defaults.accept, | ||
'Connection' : options.connection || defaults.connection, | ||
'User-Agent' : options.user_agent || defaults.user_agent | ||
} | ||
// set connection header if opts.connection was passed, or if node < 0.11.4 (close) | ||
if (options.connection || close_by_default) | ||
config.headers['Connection'] = options.connection || 'close'; | ||
if ((options.compressed || defaults.compressed) && typeof zlib != 'undefined') | ||
@@ -236,4 +243,3 @@ config.headers['Accept-Encoding'] = 'gzip,deflate'; | ||
var self = this, | ||
out = new stream.PassThrough({ objectMode: false }), | ||
var out = new stream.PassThrough({ objectMode: false }), | ||
uri = this.uri, | ||
@@ -249,20 +255,16 @@ data = this.data, | ||
var config = this.setup(uri, options); | ||
var body, config = this.setup(uri, options); | ||
if (data) { | ||
if (method.toUpperCase() == 'GET') { // build query string and append to URI | ||
uri = uri.replace(/\?.*|$/, '?' + stringify(data)); | ||
post_data = null; | ||
if (options.multipart) { // boss says we do multipart. so we do it. | ||
} else if (options.multipart) { // build multipart body for request | ||
var self = this, boundary = options.boundary || defaults.boundary; | ||
var boundary = options.boundary || defaults.boundary; | ||
multipart.build(data, boundary, function(err, body) { | ||
multipart.build(data, boundary, function(err, parts) { | ||
if (err) throw(err); | ||
config.headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary; | ||
config.headers['Content-Length'] = body.length; | ||
self.send_request(1, method, uri, config, body, out, callback); | ||
config.headers['Content-Length'] = parts.length; | ||
self.send_request(1, method, uri, config, parts, out, callback); | ||
}); | ||
@@ -274,25 +276,41 @@ | ||
post_data = data; | ||
if (is_stream(data) && method.toUpperCase() == 'GET') | ||
throw new Error('Refusing to pipe() a stream via GET. Did you mean .post?'); | ||
body = data; // use the raw buffer or stream as request body. | ||
} else if (method.toUpperCase() == 'GET' && !options.json) { | ||
// append the data to the URI as a querystring. | ||
uri = uri.replace(/\?.*|$/, '?' + stringify(data)); | ||
} else { // string or object data, no multipart. | ||
// if no content-type was passed, determine if json or not. | ||
if (!config.headers['Content-Type']) { | ||
config.headers['Content-Type'] = options.json | ||
? 'application/json; charset=utf-8' | ||
: 'application/x-www-form-urlencoded'; // no charset says W3 spec. | ||
} | ||
// if string, leave it as it is, otherwise, stringify. | ||
body = (typeof(data) === 'string') ? data | ||
: options.json ? JSON.stringify(data) : stringify(data); | ||
// format post_data and build a buffer out of it. | ||
var post_data = options.json ? JSON.stringify(data) : stringify(data); | ||
post_data = new Buffer(post_data, config.encoding); | ||
config.headers['Content-Length'] = post_data.length; | ||
// ensure we have a buffer so bytecount is correct. | ||
body = new Buffer(body, config.encoding); | ||
} | ||
// unless a specific accept header was passed, assume json wants json back. | ||
if (options.json && config.headers['Accept'] === defaults.accept) | ||
config.headers['Accept'] = 'application/json'; | ||
} | ||
if (body) { | ||
// unless we have a stream or set a querystring, set the content length. | ||
if (body.length) config.headers['Content-Length'] = body.length; | ||
// if no content-type was passed, determine if json or not. | ||
if (!config.headers['Content-Type']) { | ||
config.headers['Content-Type'] = options.json | ||
? 'application/json; charset=utf-8' | ||
: 'application/x-www-form-urlencoded'; // no charset says W3 spec. | ||
} | ||
// unless a specific accept header was passed, assume json wants json back. | ||
if (options.json && config.headers['Accept'] === defaults.accept) | ||
config.headers['Accept'] = 'application/json'; | ||
} | ||
return this.send_request(1, method, uri, config, post_data, out, callback); | ||
return this.send_request(1, method, uri, config, body, out, callback); | ||
} | ||
@@ -357,11 +375,13 @@ | ||
function done(err, resp, body) { | ||
if (returned++ > 0) return; | ||
if (returned++ > 0) | ||
return debug('Already finished, stopping here.'); | ||
if (timer) clearTimeout(timer); | ||
request.removeListener('error', had_error); | ||
request.socket.removeListener('end', on_socket_end); | ||
if (callback) | ||
callback(err, resp, body); | ||
else | ||
out.emit('end', err, resp, body); | ||
return callback(err, resp, body); | ||
out.emit('end', err, resp, body); | ||
} | ||
@@ -379,2 +399,12 @@ | ||
// handle errors on the underlying socket, that may be closed while writing | ||
// for an example case, see test/long_string_spec.js. we make sure this | ||
// scenario ocurred by verifying the socket's writable & destroyed states. | ||
function on_socket_end() { | ||
if (!this.writable && this.destroyed === false) { | ||
this.destroy(); | ||
had_error(new Error('Remote end closed socket abruptly.')) | ||
} | ||
} | ||
debug('Making request #' + count, request_opts); | ||
@@ -412,3 +442,3 @@ var request = protocol.request(request_opts, function(resp) { | ||
if (config.follow_set_referer) | ||
config.headers['Referer'] = uri; | ||
config.headers['Referer'] = uri; // the original, not the destination URL. | ||
@@ -424,3 +454,3 @@ config.headers['Host'] = null; // clear previous Host header to avoid conflicts. | ||
// if authentication is requested and credentials were not passed, resend request if we have user/pass | ||
// if auth is requested and credentials were not passed, resend request, provided we have user/pass. | ||
if (resp.statusCode == 401 && headers['www-authenticate'] && config.credentials) { | ||
@@ -437,3 +467,3 @@ if (!config.headers['Authorization']) { // only if authentication hasn't been sent | ||
// ok so we got a valid (non-redirect & authorized) response. notify the stream guys. | ||
// ok, so we got a valid (non-redirect & authorized) response. let's notify the stream guys. | ||
out.emit('header', resp.statusCode, headers); | ||
@@ -508,3 +538,3 @@ out.emit('headers', headers); | ||
// Count the amount of (raw) bytes passed using a PassThrough stream. | ||
// Gather and count the amount of (raw) bytes using a PassThrough stream. | ||
var clean_pipe = new stream.PassThrough(); | ||
@@ -535,3 +565,3 @@ resp.pipe(clean_pipe); | ||
out.on('end', function() { | ||
// we may want access to the raw data, so keep a reference. | ||
// we want to be able to access to the raw data later, so keep a reference. | ||
resp.raw = Buffer.concat(resp.raw); | ||
@@ -566,6 +596,13 @@ | ||
// unless timeout was disabled, set a timeout to abort the request | ||
// unless open_timeout was disabled, set a timeout to abort the request. | ||
set_timeout(config.open_timeout); | ||
// handle errors on the request object. things might get bumpy. | ||
request.on('error', had_error); | ||
// handle socket 'end' event to ensure we don't get delayed EPIPE errors. | ||
request.once('socket', function(socket) { | ||
socket.once('end', on_socket_end.bind(socket)); | ||
}) | ||
if (post_data) { | ||
@@ -572,0 +609,0 @@ if (is_stream(post_data)) { |
{ | ||
"name": "needle", | ||
"version": "0.11.0", | ||
"version": "1.0.0", | ||
"description": "The leanest and most handsome HTTP client in the Nodelands.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -20,3 +20,2 @@ var needle = require('../'), | ||
describe('when host does not exist', function(){ | ||
@@ -54,2 +53,16 @@ | ||
it('does not emit an error event', function(done) { | ||
var emitted = false; | ||
var req = needle.get(url, function(err, resp) { }) | ||
req.on('error', function() { | ||
emitted = true; | ||
}) | ||
setTimeout(function() { | ||
emitted.should.eql(false); | ||
done(); | ||
}, 100); | ||
}) | ||
}) | ||
@@ -106,2 +119,16 @@ | ||
it('does not emit an error event', function(done) { | ||
var emitted = false, | ||
req = needle.get(url); | ||
req.on('error', function() { | ||
emitted = true; | ||
}) | ||
setTimeout(function() { | ||
emitted.should.eql(false); | ||
done(); | ||
}, 100); | ||
}) | ||
}) | ||
@@ -163,2 +190,19 @@ | ||
it('does not emit an error event', function(done) { | ||
var emitted = false; | ||
var req = send_request(function(err, resp) { | ||
should.not.exist(resp); | ||
}) | ||
req.on('error', function() { | ||
emitted = true; | ||
}) | ||
setTimeout(function() { | ||
emitted.should.eql(false); | ||
done(); | ||
}, 100); | ||
}) | ||
}) | ||
@@ -223,2 +267,16 @@ | ||
it('does not emit an error event', function(done) { | ||
var emitted = false; | ||
var req = send_request(); | ||
req.on('error', function() { | ||
emitted = true; | ||
}) | ||
setTimeout(function() { | ||
emitted.should.eql(false); | ||
done(); | ||
}, 100); | ||
}) | ||
}) | ||
@@ -225,0 +283,0 @@ |
@@ -44,3 +44,3 @@ var fs = require('fs'); | ||
var handler = function(req, res){ | ||
var handler = function(req, res) { | ||
@@ -50,2 +50,6 @@ req.setEncoding('utf8'); // get as string | ||
req.on('data', function(str) { req.body += str }) | ||
req.socket.on('error', function(e) { | ||
// res.writeHead(500, {'Content-Type': 'text/plain'}); | ||
// res.end('Error: ' + e.message); | ||
}) | ||
@@ -52,0 +56,0 @@ setTimeout(function(){ |
@@ -98,3 +98,8 @@ var helpers = require('./helpers'), | ||
send_request(opts, function(err, resp) { | ||
spies.http.callCount.should.eql(1); // the one from http.request | ||
// on new node versions, https.request calls http.request internally, | ||
// so we need to amount for that additional call. | ||
var http_calls = protocols.http.Agent.defaultMaxSockets == Infinity ? 2 : 1; | ||
spies.http.callCount.should.eql(http_calls); // the one(s) from http.request | ||
spies.https.callCount.should.eql(1); // the one from https.request (redirect) | ||
@@ -101,0 +106,0 @@ done(); |
@@ -29,3 +29,5 @@ var should = require('should'), | ||
stream._readableState.flowing.should.be.false; | ||
// newer node versions set this to null instead of false | ||
var bool = !!stream._readableState.flowing; | ||
should.equal(false, bool); | ||
@@ -46,3 +48,3 @@ var readableCalled = false; | ||
it('should should emit a single data item which is our JSON object', function(done) { | ||
var stream = needle.get('localhost:' + port) | ||
var stream = needle.get('localhost:' + port) | ||
@@ -49,0 +51,0 @@ var chunks = []; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
173882
44
3845
1
19