node-fetch
Advanced tools
Comparing version 1.5.2 to 1.5.3
@@ -8,4 +8,12 @@ | ||
## v1.5.2 (master) | ||
## v1.5.3 (master) | ||
- Fix: handles 204 and 304 responses when body is empty but content-encoding is gzip/deflate | ||
- Fix: allow resolving response and cloned response in any order | ||
- Fix: avoid setting content-length when form-data body use streams | ||
- Fix: send DELETE request with content-length when body is present | ||
- Fix: allow any url when calling new Request, but still reject non-http(s) url in fetch | ||
## v1.5.2 | ||
- Fix: allow node.js core to handle keep-alive connection pool when passing a custom agent | ||
@@ -12,0 +20,0 @@ |
31
index.js
@@ -51,10 +51,12 @@ | ||
// build request object | ||
var options; | ||
try { | ||
options = new Request(url, opts); | ||
} catch (err) { | ||
reject(err); | ||
return; | ||
var options = new Request(url, opts); | ||
if (!options.protocol || !options.hostname) { | ||
throw new Error('only absolute urls are supported'); | ||
} | ||
if (options.protocol !== 'http:' && options.protocol !== 'https:') { | ||
throw new Error('only http(s) protocols are supported'); | ||
} | ||
var send; | ||
@@ -91,8 +93,8 @@ if (options.protocol === 'https:') { | ||
// bring node-fetch closer to browser behavior by setting content-length automatically for POST, PUT, PATCH requests when body is empty or string | ||
if (!headers.has('content-length') && options.method.substr(0, 1).toUpperCase() === 'P') { | ||
// bring node-fetch closer to browser behavior by setting content-length automatically | ||
if (!headers.has('content-length') && /post|put|patch|delete/i.test(options.method)) { | ||
if (typeof options.body === 'string') { | ||
headers.set('content-length', Buffer.byteLength(options.body)); | ||
// detect form data input from form-data module, this hack avoid the need to add content-length header manually | ||
} else if (options.body && typeof options.body.getLengthSync === 'function') { | ||
} else if (options.body && typeof options.body.getLengthSync === 'function' && options.body._lengthRetrievers.length == 0) { | ||
headers.set('content-length', options.body.getLengthSync().toString()); | ||
@@ -172,6 +174,9 @@ // this is only necessary for older nodejs releases (before iojs merge) | ||
if (name == 'gzip' || name == 'x-gzip') { | ||
body = body.pipe(zlib.createGunzip()); | ||
} else if (name == 'deflate' || name == 'x-deflate') { | ||
body = body.pipe(zlib.createInflate()); | ||
// no need to pipe no content and not modified response body | ||
if (res.statusCode !== 204 && res.statusCode !== 304) { | ||
if (name == 'gzip' || name == 'x-gzip') { | ||
body = body.pipe(zlib.createGunzip()); | ||
} else if (name == 'deflate' || name == 'x-deflate') { | ||
body = body.pipe(zlib.createInflate()); | ||
} | ||
} | ||
@@ -178,0 +183,0 @@ } |
@@ -208,3 +208,3 @@ | ||
Body.prototype._clone = function(instance) { | ||
var pass; | ||
var p1, p2; | ||
var body = instance.body; | ||
@@ -220,5 +220,10 @@ | ||
if (bodyStream(body) && typeof body.getBoundary !== 'function') { | ||
pass = new PassThrough(); | ||
body.pipe(pass); | ||
body = pass; | ||
// tee instance body | ||
p1 = new PassThrough(); | ||
p2 = new PassThrough(); | ||
body.pipe(p1); | ||
body.pipe(p2); | ||
// set instance body to teed body and return the other teed body | ||
instance.body = p1; | ||
body = p2; | ||
} | ||
@@ -225,0 +230,0 @@ |
@@ -34,10 +34,2 @@ | ||
if (!url_parsed.protocol || !url_parsed.hostname) { | ||
throw new Error('only absolute urls are supported'); | ||
} | ||
if (url_parsed.protocol !== 'http:' && url_parsed.protocol !== 'https:') { | ||
throw new Error('only http(s) protocols are supported'); | ||
} | ||
// normalize init | ||
@@ -44,0 +36,0 @@ init = init || {}; |
{ | ||
"name": "node-fetch", | ||
"version": "1.5.2", | ||
"version": "1.5.3", | ||
"description": "A light-weight module that brings window.fetch to node.js and io.js", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -267,3 +267,3 @@ | ||
if (p === '/empty') { | ||
if (p === '/no-content') { | ||
res.statusCode = 204; | ||
@@ -273,2 +273,19 @@ res.end(); | ||
if (p === '/no-content/gzip') { | ||
res.statusCode = 204; | ||
res.setHeader('Content-Encoding', 'gzip'); | ||
res.end(); | ||
} | ||
if (p === '/not-modified') { | ||
res.statusCode = 304; | ||
res.end(); | ||
} | ||
if (p === '/not-modified/gzip') { | ||
res.statusCode = 304; | ||
res.setHeader('Content-Encoding', 'gzip'); | ||
res.end(); | ||
} | ||
if (p === '/inspect') { | ||
@@ -275,0 +292,0 @@ res.statusCode = 200; |
122
test/test.js
@@ -14,2 +14,3 @@ | ||
var http = require('http'); | ||
var fs = require('fs'); | ||
@@ -420,4 +421,4 @@ var TestServer = require('./server'); | ||
it('should handle empty response', function() { | ||
url = base + '/empty'; | ||
it('should handle no content response', function() { | ||
url = base + '/no-content'; | ||
return fetch(url).then(function(res) { | ||
@@ -434,2 +435,43 @@ expect(res.status).to.equal(204); | ||
it('should handle no content response with gzip encoding', function() { | ||
url = base + '/no-content/gzip'; | ||
return fetch(url).then(function(res) { | ||
expect(res.status).to.equal(204); | ||
expect(res.statusText).to.equal('No Content'); | ||
expect(res.headers.get('content-encoding')).to.equal('gzip'); | ||
expect(res.ok).to.be.true; | ||
return res.text().then(function(result) { | ||
expect(result).to.be.a('string'); | ||
expect(result).to.be.empty; | ||
}); | ||
}); | ||
}); | ||
it('should handle not modified response', function() { | ||
url = base + '/not-modified'; | ||
return fetch(url).then(function(res) { | ||
expect(res.status).to.equal(304); | ||
expect(res.statusText).to.equal('Not Modified'); | ||
expect(res.ok).to.be.false; | ||
return res.text().then(function(result) { | ||
expect(result).to.be.a('string'); | ||
expect(result).to.be.empty; | ||
}); | ||
}); | ||
}); | ||
it('should handle not modified response with gzip encoding', function() { | ||
url = base + '/not-modified/gzip'; | ||
return fetch(url).then(function(res) { | ||
expect(res.status).to.equal(304); | ||
expect(res.statusText).to.equal('Not Modified'); | ||
expect(res.headers.get('content-encoding')).to.equal('gzip'); | ||
expect(res.ok).to.be.false; | ||
return res.text().then(function(result) { | ||
expect(result).to.be.a('string'); | ||
expect(result).to.be.empty; | ||
}); | ||
}); | ||
}); | ||
it('should decompress gzip response', function() { | ||
@@ -572,6 +614,9 @@ url = base + '/gzip'; | ||
it('should allow POST request with readable stream as body', function() { | ||
var body = resumer().queue('a=1').end(); | ||
body = body.pipe(new stream.PassThrough()); | ||
url = base + '/inspect'; | ||
opts = { | ||
method: 'POST' | ||
, body: resumer().queue('a=1').end() | ||
, body: body | ||
}; | ||
@@ -607,2 +652,22 @@ return fetch(url, opts).then(function(res) { | ||
it('should allow POST request with form-data using stream as body', function() { | ||
var form = new FormData(); | ||
form.append('my_field', fs.createReadStream('test/dummy.txt')); | ||
url = base + '/multipart'; | ||
opts = { | ||
method: 'POST' | ||
, body: form | ||
}; | ||
return fetch(url, opts).then(function(res) { | ||
return res.json(); | ||
}).then(function(res) { | ||
expect(res.method).to.equal('POST'); | ||
expect(res.headers['content-type']).to.contain('multipart/form-data'); | ||
expect(res.headers['content-length']).to.be.undefined; | ||
expect(res.body).to.contain('my_field='); | ||
}); | ||
}); | ||
it('should allow POST request with form-data as body and custom headers', function() { | ||
@@ -658,2 +723,34 @@ var form = new FormData(); | ||
it('should allow POST request with string body', function() { | ||
url = base + '/inspect'; | ||
opts = { | ||
method: 'POST' | ||
, body: 'a=1' | ||
}; | ||
return fetch(url, opts).then(function(res) { | ||
return res.json(); | ||
}).then(function(res) { | ||
expect(res.method).to.equal('POST'); | ||
expect(res.body).to.equal('a=1'); | ||
expect(res.headers['transfer-encoding']).to.be.undefined; | ||
expect(res.headers['content-length']).to.equal('3'); | ||
}); | ||
}); | ||
it('should allow DELETE request with string body', function() { | ||
url = base + '/inspect'; | ||
opts = { | ||
method: 'DELETE' | ||
, body: 'a=1' | ||
}; | ||
return fetch(url, opts).then(function(res) { | ||
return res.json(); | ||
}).then(function(res) { | ||
expect(res.method).to.equal('DELETE'); | ||
expect(res.body).to.equal('a=1'); | ||
expect(res.headers['transfer-encoding']).to.be.undefined; | ||
expect(res.headers['content-length']).to.equal('3'); | ||
}); | ||
}); | ||
it('should allow PATCH request', function() { | ||
@@ -907,2 +1004,15 @@ url = base + '/inspect'; | ||
it('should allow cloning a json response, first log as text response, then return json object', function() { | ||
url = base + '/json'; | ||
return fetch(url).then(function(res) { | ||
var r1 = res.clone(); | ||
return r1.text().then(function(result) { | ||
expect(result).to.equal('{"name":"value"}'); | ||
return res.json().then(function(result) { | ||
expect(result).to.deep.equal({name: 'value'}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('should not allow cloning a response after its been used', function() { | ||
@@ -1207,2 +1317,8 @@ url = base + '/hello'; | ||
it('should support arbitrary url in Request constructor', function() { | ||
url = 'anything'; | ||
var req = new Request(url); | ||
expect(req.url).to.equal('anything'); | ||
}); | ||
it('should support clone() method in Request constructor', function() { | ||
@@ -1209,0 +1325,0 @@ url = base; |
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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
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
76867
16
2140
1
105