Skip to content

Commit 95286f5

Browse files
authored
* Update readme and changelog for `options.agent` - Fix content-length issue introduced in v2.5.0 * More test coverage for `extractContentType` * Slightly improve test performance * `Response.url` should not return null * Document `Headers.raw()` usage better * 2.6.0
1 parent bf8b4e8 commit 95286f5

File tree

7 files changed

+136
-25
lines changed

7 files changed

+136
-25
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ Changelog
55

66
# 2.x release
77

8+
## v2.6.0
9+
10+
- Enhance: `options.agent`, it now accepts a function that returns custom http(s).Agent instance based on current URL, see readme for more information.
11+
- Fix: incorrect `Content-Length` was returned for stream body in 2.5.0 release; note that `node-fetch` doesn't calculate content length for stream body.
12+
- Fix: `Response.url` should return empty string instead of `null` by default.
13+
814
## v2.5.0
915

1016
- Enhance: `Response` object now includes `redirected` property.

README.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ A light-weight module that brings `window.fetch` to Node.js
2929
- [Streams](#streams)
3030
- [Buffer](#buffer)
3131
- [Accessing Headers and other Meta data](#accessing-headers-and-other-meta-data)
32+
- [Extract Set-Cookie Header](#extract-set-cookie-header)
3233
- [Post data using a file stream](#post-data-using-a-file-stream)
3334
- [Post with form-data (detect multipart)](#post-with-form-data-detect-multipart)
3435
- [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal)
@@ -208,6 +209,17 @@ fetch('https://github.com/')
208209
});
209210
```
210211

212+
#### Extract Set-Cookie Header
213+
214+
Unlike browsers, you can access raw `Set-Cookie` headers manually using `Headers.raw()`, this is a `node-fetch` only API.
215+
216+
```js
217+
fetch(url).then(res => {
218+
// returns an array of values, instead of a string of comma-separated values
219+
console.log(res.headers.raw()['set-cookie']);
220+
});
221+
```
222+
211223
#### Post data using a file stream
212224

213225
```js
@@ -317,7 +329,7 @@ The default values are shown after each option key.
317329
timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
318330
compress: true, // support gzip/deflate content encoding. false to disable
319331
size: 0, // maximum response body size in bytes. 0 to disable
320-
agent: null // http(s).Agent instance (or function providing one), allows custom proxy, certificate, dns lookup etc.
332+
agent: null // http(s).Agent instance or function that returns an instance (see below)
321333
}
322334
```
323335

@@ -334,6 +346,39 @@ Header | Value
334346
`Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_
335347
`User-Agent` | `node-fetch/1.0 (+https://github.com/bitinn/node-fetch)`
336348

349+
Note: when `body` is a `Stream`, `Content-Length` is not set automatically.
350+
351+
##### Custom Agent
352+
353+
The `agent` option allows you to specify networking related options that's out of the scope of Fetch. Including and not limit to:
354+
355+
- Support self-signed certificate
356+
- Use only IPv4 or IPv6
357+
- Custom DNS Lookup
358+
359+
See [`http.Agent`](https://nodejs.org/api/http.html#http_new_agent_options) for more information.
360+
361+
In addition, `agent` option accepts a function that returns http(s).Agent instance given current [URL](https://nodejs.org/api/url.html), this is useful during a redirection chain across HTTP and HTTPS protocol.
362+
363+
```js
364+
const httpAgent = new http.Agent({
365+
keepAlive: true
366+
});
367+
const httpsAgent = new https.Agent({
368+
keepAlive: true
369+
});
370+
371+
const options = {
372+
agent: function (_parsedURL) {
373+
if (_parsedURL.protocol == 'http:') {
374+
return httpAgent;
375+
} else {
376+
return httpsAgent;
377+
}
378+
}
379+
}
380+
```
381+
337382
<a id="class-request"></a>
338383
### Class: Request
339384

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-fetch",
3-
"version": "2.5.0",
3+
"version": "2.6.0",
44
"description": "A light-weight module that brings window.fetch to node.js",
55
"main": "lib/index",
66
"browser": "./browser.js",

src/body.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -415,11 +415,9 @@ export function clone(instance) {
415415
*
416416
* This function assumes that instance.body is present.
417417
*
418-
* @param Mixed instance Response or Request instance
418+
* @param Mixed instance Any options.body input
419419
*/
420420
export function extractContentType(body) {
421-
// istanbul ignore if: Currently, because of a guard in Request, body
422-
// can never be null. Included here for completeness.
423421
if (body === null) {
424422
// body is null
425423
return null;
@@ -466,7 +464,6 @@ export function extractContentType(body) {
466464
export function getTotalBytes(instance) {
467465
const {body} = instance;
468466

469-
// istanbul ignore if: included for completion
470467
if (body === null) {
471468
// body is null
472469
return 0;
@@ -484,7 +481,7 @@ export function getTotalBytes(instance) {
484481
return null;
485482
} else {
486483
// body is stream
487-
return instance.size || null;
484+
return null;
488485
}
489486
}
490487

src/response.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default class Response {
4646
}
4747

4848
get url() {
49-
return this[INTERNALS].url;
49+
return this[INTERNALS].url || '';
5050
}
5151

5252
get status() {

test/server.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ export default class TestServer {
157157
res.setHeader('Content-Type', 'text/plain');
158158
setTimeout(function() {
159159
res.write('test');
160-
}, 50);
160+
}, 10);
161161
setTimeout(function() {
162162
res.end('test');
163-
}, 100);
163+
}, 20);
164164
}
165165

166166
if (p === '/size/long') {
@@ -280,7 +280,7 @@ export default class TestServer {
280280
res.setHeader('Location', '/redirect/slow');
281281
setTimeout(function() {
282282
res.end();
283-
}, 100);
283+
}, 10);
284284
}
285285

286286
if (p === '/redirect/slow-stream') {

test/test.js

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import FetchErrorOrig from '../src/fetch-error.js';
4848
import HeadersOrig, { createHeadersLenient } from '../src/headers.js';
4949
import RequestOrig from '../src/request.js';
5050
import ResponseOrig from '../src/response.js';
51-
import Body from '../src/body.js';
51+
import Body, { getTotalBytes, extractContentType } from '../src/body.js';
5252
import Blob from '../src/blob.js';
5353
import zlib from "zlib";
5454

@@ -738,7 +738,7 @@ describe('node-fetch', () => {
738738
// Wait a few ms to see if a uncaught error occurs
739739
setTimeout(() => {
740740
done();
741-
}, 50);
741+
}, 20);
742742
});
743743
});
744744

@@ -748,7 +748,7 @@ describe('node-fetch', () => {
748748
return new Promise((resolve) => {
749749
setTimeout(() => {
750750
resolve(value)
751-
}, 100);
751+
}, 20);
752752
});
753753
}
754754

@@ -789,21 +789,19 @@ describe('node-fetch', () => {
789789
});
790790

791791
it('should allow custom timeout', function() {
792-
this.timeout(500);
793792
const url = `${base}timeout`;
794793
const opts = {
795-
timeout: 100
794+
timeout: 20
796795
};
797796
return expect(fetch(url, opts)).to.eventually.be.rejected
798797
.and.be.an.instanceOf(FetchError)
799798
.and.have.property('type', 'request-timeout');
800799
});
801800

802801
it('should allow custom timeout on response body', function() {
803-
this.timeout(500);
804802
const url = `${base}slow`;
805803
const opts = {
806-
timeout: 100
804+
timeout: 20
807805
};
808806
return fetch(url, opts).then(res => {
809807
expect(res.ok).to.be.true;
@@ -814,10 +812,9 @@ describe('node-fetch', () => {
814812
});
815813

816814
it('should allow custom timeout on redirected requests', function() {
817-
this.timeout(2000);
818815
const url = `${base}redirect/slow-chain`;
819816
const opts = {
820-
timeout: 200
817+
timeout: 20
821818
};
822819
return expect(fetch(url, opts)).to.eventually.be.rejected
823820
.and.be.an.instanceOf(FetchError)
@@ -908,7 +905,7 @@ describe('node-fetch', () => {
908905
'${base}timeout',
909906
{ signal: controller.signal, timeout: 10000 }
910907
);
911-
setTimeout(function () { controller.abort(); }, 100);
908+
setTimeout(function () { controller.abort(); }, 20);
912909
`
913910
spawn('node', ['-e', script])
914911
.on('exit', () => {
@@ -940,7 +937,7 @@ describe('node-fetch', () => {
940937
});
941938
setTimeout(() => {
942939
abortController.abort();
943-
}, 50);
940+
}, 20);
944941
return expect(fetch(request)).to.be.eventually.rejected
945942
.and.be.an.instanceOf(Error)
946943
.and.have.property('name', 'AbortError');
@@ -1914,8 +1911,8 @@ describe('node-fetch', () => {
19141911
expect(err.type).to.equal('test-error');
19151912
expect(err.code).to.equal('ESOMEERROR');
19161913
expect(err.errno).to.equal('ESOMEERROR');
1917-
expect(err.stack).to.include('funcName')
1918-
.and.to.startWith(`${err.name}: ${err.message}`);
1914+
// reading the stack is quite slow (~30-50ms)
1915+
expect(err.stack).to.include('funcName').and.to.startWith(`${err.name}: ${err.message}`);
19191916
});
19201917

19211918
it('should support https request', function() {
@@ -1982,7 +1979,7 @@ describe('node-fetch', () => {
19821979
it('should allow a function supplying the agent', function() {
19831980
const url = `${base}inspect`;
19841981

1985-
const agent = http.Agent({
1982+
const agent = new http.Agent({
19861983
keepAlive: true
19871984
});
19881985

@@ -2002,6 +1999,67 @@ describe('node-fetch', () => {
20021999
expect(res.headers['connection']).to.equal('keep-alive');
20032000
});
20042001
});
2002+
2003+
it('should calculate content length and extract content type for each body type', function () {
2004+
const url = `${base}hello`;
2005+
const bodyContent = 'a=1';
2006+
2007+
let streamBody = resumer().queue(bodyContent).end();
2008+
streamBody = streamBody.pipe(new stream.PassThrough());
2009+
const streamRequest = new Request(url, {
2010+
method: 'POST',
2011+
body: streamBody,
2012+
size: 1024
2013+
});
2014+
2015+
let blobBody = new Blob([bodyContent], { type: 'text/plain' });
2016+
const blobRequest = new Request(url, {
2017+
method: 'POST',
2018+
body: blobBody,
2019+
size: 1024
2020+
});
2021+
2022+
let formBody = new FormData();
2023+
formBody.append('a', '1');
2024+
const formRequest = new Request(url, {
2025+
method: 'POST',
2026+
body: formBody,
2027+
size: 1024
2028+
});
2029+
2030+
let bufferBody = Buffer.from(bodyContent);
2031+
const bufferRequest = new Request(url, {
2032+
method: 'POST',
2033+
body: bufferBody,
2034+
size: 1024
2035+
});
2036+
2037+
const stringRequest = new Request(url, {
2038+
method: 'POST',
2039+
body: bodyContent,
2040+
size: 1024
2041+
});
2042+
2043+
const nullRequest = new Request(url, {
2044+
method: 'GET',
2045+
body: null,
2046+
size: 1024
2047+
});
2048+
2049+
expect(getTotalBytes(streamRequest)).to.be.null;
2050+
expect(getTotalBytes(blobRequest)).to.equal(blobBody.size);
2051+
expect(getTotalBytes(formRequest)).to.not.be.null;
2052+
expect(getTotalBytes(bufferRequest)).to.equal(bufferBody.length);
2053+
expect(getTotalBytes(stringRequest)).to.equal(bodyContent.length);
2054+
expect(getTotalBytes(nullRequest)).to.equal(0);
2055+
2056+
expect(extractContentType(streamBody)).to.be.null;
2057+
expect(extractContentType(blobBody)).to.equal('text/plain');
2058+
expect(extractContentType(formBody)).to.startWith('multipart/form-data');
2059+
expect(extractContentType(bufferBody)).to.be.null;
2060+
expect(extractContentType(bodyContent)).to.equal('text/plain;charset=UTF-8');
2061+
expect(extractContentType(null)).to.be.null;
2062+
});
20052063
});
20062064

20072065
describe('Headers', function () {
@@ -2387,6 +2445,11 @@ describe('Response', function () {
23872445
const res = new Response(null);
23882446
expect(res.status).to.equal(200);
23892447
});
2448+
2449+
it('should default to empty string as url', function() {
2450+
const res = new Response();
2451+
expect(res.url).to.equal('');
2452+
});
23902453
});
23912454

23922455
describe('Request', function () {

0 commit comments

Comments
 (0)