avancement planning
This commit is contained in:
+2
-2
@@ -62,7 +62,7 @@ With only two real dependencies, Needle supports:
|
||||
|
||||
And yes, Mr. Wayne, it does come in black.
|
||||
|
||||
This makes Needle an ideal alternative for performing quick HTTP requests in Node, either for API interaction, downloading or uploading streams of data, and so on. If you need OAuth, AWS support or anything fancier, you should check out mikeal's request module.
|
||||
This makes Needle an ideal alternative for performing quick HTTP requests in Node, either for API interaction, downloading or uploading streams of data, and so on.
|
||||
|
||||
Install
|
||||
-------
|
||||
@@ -362,7 +362,7 @@ Redirect options
|
||||
|
||||
These options only apply if the `follow_max` (or `follow`) option is higher than 0.
|
||||
|
||||
- `follow_set_cookies` : Sends the cookies received in the `set-cookie` header as part of the following request, *if hosts match*. `false` by default.
|
||||
- `follow_set_cookies` : Sends the cookies received in the `set-cookie` header as part of the following request, *if hosts match*, along with the original request cookies (if provided). `false` by default.
|
||||
- `follow_set_referer` : Sets the 'Referer' header to the requested URI when following a redirect. `false` by default.
|
||||
- `follow_keep_method` : If enabled, resends the request using the original verb instead of being rewritten to `get` with no data. `false` by default.
|
||||
- `follow_if_same_host` : When true, Needle will only follow redirects that point to the same host as the original request. `false` by default.
|
||||
|
||||
+2
-1
@@ -20,7 +20,7 @@ var SEP_SEMICOLON = /\s*\x3B\s*/;
|
||||
var KEY_INDEX = 1; // index of key from COOKIE_PAIR match
|
||||
var VALUE_INDEX = 3; // index of value from COOKIE_PAIR match
|
||||
|
||||
// Returns a copy str trimmed and without trainling semicolon.
|
||||
// Returns a copy str trimmed and without trailing semicolon.
|
||||
function cleanCookieString(str) {
|
||||
return str.trim().replace(/\x3B+$/, '');
|
||||
}
|
||||
@@ -53,6 +53,7 @@ function parseSetCookieString(str) {
|
||||
// Each key represents the name of a cookie.
|
||||
function parseSetCookieHeader(header) {
|
||||
if (!header) return {};
|
||||
if (typeof header === 'string' || header instanceof String) header = header.split(';');
|
||||
header = Array.isArray(header) ? header : [header];
|
||||
|
||||
return header.reduce(function(res, str) {
|
||||
|
||||
+6
-5
@@ -39,10 +39,9 @@ function generate_part(name, part, boundary, callback) {
|
||||
var return_part = '--' + boundary + '\r\n';
|
||||
return_part += 'Content-Disposition: form-data; name="' + name + '"';
|
||||
|
||||
function append(data, filename) {
|
||||
|
||||
function append(data, filename, force_binary) {
|
||||
if (data) {
|
||||
var binary = part.content_type.indexOf('text') == -1;
|
||||
var binary = force_binary || part.content_type.indexOf('text') == -1;
|
||||
return_part += '; filename="' + encodeURIComponent(filename) + '"\r\n';
|
||||
if (binary) return_part += 'Content-Transfer-Encoding: binary\r\n';
|
||||
return_part += 'Content-Type: ' + part.content_type + '\r\n\r\n';
|
||||
@@ -55,14 +54,16 @@ function generate_part(name, part, boundary, callback) {
|
||||
if ((part.file || part.buffer) && part.content_type) {
|
||||
|
||||
var filename = part.filename ? part.filename : part.file ? basename(part.file) : name;
|
||||
if (part.buffer) return append(part.buffer, filename);
|
||||
if (part.buffer) return append(part.buffer, filename, true);
|
||||
|
||||
readFile(part.file, function(err, data) {
|
||||
if (err) return callback(err);
|
||||
append(data, filename);
|
||||
append(data, filename, true);
|
||||
});
|
||||
|
||||
} else {
|
||||
if (!part.value)
|
||||
throw new Error('value missing for multipart!')
|
||||
|
||||
if (typeof part.value == 'object')
|
||||
return callback(new Error('Object received for ' + name + ', expected string.'))
|
||||
|
||||
+33
-16
@@ -455,13 +455,8 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
|
||||
protocol = request_opts.protocol == 'https:' ? https : http,
|
||||
signal = request_opts.signal;
|
||||
|
||||
function done(err, resp) {
|
||||
if (returned++ > 0)
|
||||
return debug('Already finished, stopping here.');
|
||||
|
||||
if (timer) clearTimeout(timer);
|
||||
function unlisten_errors() {
|
||||
request.removeListener('error', had_error);
|
||||
out.done = true;
|
||||
|
||||
// An error can still be fired after closing. In particular, on macOS.
|
||||
// See also:
|
||||
@@ -469,6 +464,16 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
|
||||
// - https://github.com/less/less.js/issues/3693
|
||||
// - https://github.com/nodejs/node/issues/27916
|
||||
request.once('error', function() {});
|
||||
}
|
||||
|
||||
function done(err, resp) {
|
||||
if (returned++ > 0)
|
||||
return debug('Already finished, stopping here.');
|
||||
|
||||
if (timer) clearTimeout(timer);
|
||||
out.done = true;
|
||||
|
||||
unlisten_errors();
|
||||
|
||||
if (callback)
|
||||
return callback(err, resp, resp ? resp.body : undefined);
|
||||
@@ -539,17 +544,28 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
|
||||
delete config.headers['content-length']; // in case the original was a multipart POST request.
|
||||
}
|
||||
|
||||
// if follow_set_cookies is true, insert cookies in the next request's headers.
|
||||
// we set both the original request cookies plus any response cookies we might have received.
|
||||
if (config.follow_set_cookies && utils.host_and_ports_match(headers.location, uri)) {
|
||||
var request_cookies = cookies.read(config.headers['cookie']);
|
||||
config.previous_resp_cookies = resp.cookies;
|
||||
if (Object.keys(request_cookies).length || Object.keys(resp.cookies || {}).length) {
|
||||
config.headers['cookie'] = cookies.write(extend(request_cookies, resp.cookies));
|
||||
if (utils.host_and_ports_match(headers.location, uri)) {
|
||||
|
||||
// if follow_set_cookies is true, insert cookies in the next request's headers.
|
||||
// we set both the original request cookies plus any response cookies we might have received.
|
||||
if (config.follow_set_cookies) {
|
||||
var request_cookies = cookies.read(config.headers['cookie']);
|
||||
config.previous_resp_cookies = resp.cookies;
|
||||
if (Object.keys(request_cookies).length || Object.keys(resp.cookies || {}).length) {
|
||||
config.headers['cookie'] = cookies.write(extend(request_cookies, resp.cookies));
|
||||
}
|
||||
|
||||
} else {
|
||||
// set response cookies if present, otherwise remove header
|
||||
// if (resp.cookies && Object.keys(resp.cookies).length)
|
||||
// config.headers['cookie'] = cookies.write(resp.cookies);
|
||||
// else
|
||||
delete config.headers['cookie'];
|
||||
}
|
||||
} else if (config.headers['cookie']) {
|
||||
debug('Clearing original request cookie', config.headers['cookie']);
|
||||
} else {
|
||||
delete config.headers['cookie'];
|
||||
delete config.headers['authorization'];
|
||||
delete config.headers['proxy-authorization'];
|
||||
}
|
||||
|
||||
if (config.follow_set_referer)
|
||||
@@ -559,6 +575,7 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
|
||||
|
||||
var redirect_url = utils.resolve_url(headers.location, uri);
|
||||
debug('Redirecting to ' + redirect_url.toString());
|
||||
unlisten_errors();
|
||||
return self.send_request(++count, method, redirect_url.toString(), config, post_data, out, callback);
|
||||
} else if (config.follow_max > 0) {
|
||||
return done(new Error('Max redirects reached. Possible loop in: ' + headers.location));
|
||||
@@ -843,7 +860,7 @@ module.exports.defaults = function(obj) {
|
||||
|
||||
'head get'.split(' ').forEach(function(method) {
|
||||
module.exports[method] = function(uri, options, callback) {
|
||||
return new Needle(method, uri, null, options, callback).start();
|
||||
return new Needle(method, uri, options.query, options, callback).start();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "needle",
|
||||
"version": "3.3.1",
|
||||
"version": "3.5.0",
|
||||
"description": "The leanest and most handsome HTTP client in the Nodelands.",
|
||||
"keywords": [
|
||||
"http",
|
||||
|
||||
+41
-6
@@ -265,6 +265,41 @@ describe('cookies', function() {
|
||||
|
||||
})
|
||||
|
||||
describe('with multiple original request cookies', function() {
|
||||
|
||||
var opts = {
|
||||
follow_set_cookies: true,
|
||||
follow_max: 4,
|
||||
cookies: { 'xxx': 123, 'yyy': 456 }
|
||||
};
|
||||
|
||||
it('request cookie is passed passed to redirects, and response cookies are added too', function(done) {
|
||||
needle.get('localhost:' + testPort + '/0', opts, function(err, resp) {
|
||||
requestCookies.should.eql([
|
||||
"xxx=123; yyy=456",
|
||||
"xxx=123; yyy=456; wc=!'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~; bc=Y29va2llCg==; FOO=123",
|
||||
"xxx=123; yyy=456; wc=!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~; bc=Y29va2llCg==; FOO=123; fc=%20%3B%22%5C%2C; nc=12354342",
|
||||
"xxx=123; yyy=456; wc=!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~; bc=Y29va2llCg==; FOO=BAR; fc=%20%3B%22%5C%2C; nc=12354342",
|
||||
"xxx=123; yyy=456; wc=!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~; bc=Y29va2llCg==; FOO=BAR; fc=%20%3B%22%5C%2C; nc=12354342"
|
||||
])
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('response cookies are passed as well', function(done) {
|
||||
needle.get('localhost:' + testPort + '/0', opts, function(err, resp) {
|
||||
resp.cookies.should.have.property(WEIRD_COOKIE_NAME);
|
||||
resp.cookies.should.have.property(BASE64_COOKIE_NAME);
|
||||
resp.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
|
||||
resp.cookies.should.have.property(NUMBER_COOKIE_NAME);
|
||||
resp.cookies.should.have.property('FOO');
|
||||
resp.cookies.FOO.should.eql('BAR'); // should overwrite previous one
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
describe('without original request cookie', function() {
|
||||
|
||||
var opts = {
|
||||
@@ -287,12 +322,12 @@ describe('cookies', function() {
|
||||
|
||||
it('response cookies are passed as well', function(done) {
|
||||
needle.get('localhost:' + testPort + '/0', opts, function(err, resp) {
|
||||
// resp.cookies.should.have.property(WEIRD_COOKIE_NAME);
|
||||
// resp.cookies.should.have.property(BASE64_COOKIE_NAME);
|
||||
// resp.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
|
||||
// resp.cookies.should.have.property(NUMBER_COOKIE_NAME);
|
||||
// resp.cookies.should.have.property('FOO');
|
||||
// resp.cookies.FOO.should.eql('BAR'); // should overwrite previous one
|
||||
resp.cookies.should.have.property(WEIRD_COOKIE_NAME);
|
||||
resp.cookies.should.have.property(BASE64_COOKIE_NAME);
|
||||
resp.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
|
||||
resp.cookies.should.have.property(NUMBER_COOKIE_NAME);
|
||||
resp.cookies.should.have.property('FOO');
|
||||
resp.cookies.FOO.should.eql('BAR'); // should overwrite previous one
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
+28
-1
@@ -146,7 +146,7 @@ describe('post data (e.g. request body)', function() {
|
||||
it('writes japanese chars correctly as binary', function(done) {
|
||||
spystub_request();
|
||||
|
||||
get({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
|
||||
post({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
|
||||
spy.called.should.be.true;
|
||||
|
||||
spy.args[0][0].should.be.an.instanceof(String);
|
||||
@@ -155,6 +155,33 @@ describe('post data (e.g. request body)', function() {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when buffer', function() {
|
||||
|
||||
it('includes Content-Transfer-Encoding: binary header', function(done) {
|
||||
spystub_request();
|
||||
|
||||
post({ foo: { buffer: Buffer.from('test content'), content_type: 'application/octet-stream' } }, { multipart: true }, function(err, resp) {
|
||||
spy.called.should.be.true;
|
||||
resp.body.body.should.containEql('Content-Transfer-Encoding: binary');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when file', function() {
|
||||
|
||||
it('includes Content-Transfer-Encoding: binary header', function(done) {
|
||||
spystub_request();
|
||||
|
||||
post({ foo: { file: __dirname + '/keys/ssl.key', content_type: 'application/octet-stream' } }, { multipart: true }, function(err, resp) {
|
||||
spy.called.should.be.true;
|
||||
resp.body.body.should.containEql('Content-Transfer-Encoding: binary');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user