UNPKG

20.3 kBMarkdownView Raw
1<div align="center">
2 <img src="banner.jpeg" alt="Adobe Fetch"/>
3 <br>
4 <p>Light-weight <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a> implementation transparently supporting both <b>HTTP/1(.1)</b> and <b>HTTP/2</b></p>
5 <a href="https://codecov.io/gh/adobe/fetch"><img src="https://img.shields.io/codecov/c/github/adobe/fetch.svg" alt="codecov"></a>
6 <a href="https://circleci.com/gh/adobe/fetch"><img src="https://img.shields.io/circleci/project/github/adobe/fetch.svg" alt="CircleCI"></a>
7 <a href="https://github.com/adobe/fetch/blob/main/LICENSE.txt"><img src="https://img.shields.io/github/license/adobe/fetch.svg" alt="GitHub license"></a>
8 <a href="https://github.com/adobe/fetch/issues"><img src="https://img.shields.io/github/issues/adobe/fetch.svg" alt="GitHub issues"></a>
9 <a href="https://lgtm.com/projects/g/adobe/fetch"><img src="https://img.shields.io/lgtm/grade/javascript/g/adobe/fetch.svg?logo=lgtm&logoWidth=18" alt="LGTM Code Quality Grade: JavaScript"></a>
10 <a href="https://renovatebot.com/"><img src="https://img.shields.io/badge/renovate-enabled-brightgreen.svg" alt="Renovate enabled"></a>
11 <a href="https://github.com/semantic-release/semantic-release"><img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" alt="semantic-release"></a>
12 <a href="https://packagephobia.now.sh/result?p=@adobe/fetch"><img src="https://badgen.net/packagephobia/install/@adobe/fetch" alt="Install size"></a>
13 <a href="https://www.npmjs.com/package/@adobe/fetch"><img src="https://img.shields.io/npm/v/@adobe/fetch" alt="Current version"></a>
14</div>
15
16---
17
18<!-- TOC -->
19- [About](#about)
20- [Features](#features)
21- [Installation](#installation)
22- [API](#api)
23 - [Context](#context)
24- [Common Usage Examples](#common-usage-examples)
25 - [Access Response Headers and other Meta data](#access-response-headers-and-other-meta-data)
26 - [Fetch JSON](#fetch-json)
27 - [Fetch text data](#fetch-text-data)
28 - [Fetch binary data](#fetch-binary-data)
29 - [Specify a timeout for a `fetch` operation](#specify-a-timeout-for-a-fetch-operation)
30 - [Stream an image](#stream-an-image)
31 - [Post JSON](#post-json)
32 - [Post JPEG image](#post-jpeg-image)
33 - [Post form data](#post-form-data)
34 - [GET with query parameters object](#get-with-query-parameters-object)
35 - [Cache](#cache)
36- [Advanced Usage Examples](#advanced-usage-examples)
37 - [HTTP/2 Server Push](#http2-server-push)
38 - [Use h2c (http2 cleartext w/prior-knowledge) protocol](#use-h2c-http2-cleartext-wprior-knowledge-protocol)
39 - [Force HTTP/1(.1) protocol](#force-http11-protocol)
40 - [HTTP/1.1 Keep-Alive](#http11-keep-alive)
41 - [Extract Set-Cookie Header](#extract-set-cookie-header)
42 - [Self-signed Certificates](#self-signed-certificates)
43 - [Set cache size limit](#set-cache-size-limit)
44 - [Disable caching](#disable-caching)
45 - [Set a custom user agent](#set-a-custom-user-agent)
46- [More examples](#more-examples)
47- [Development](#development)
48 - [Build](#build)
49 - [Test](#test)
50 - [Lint](#lint)
51 - [Troubleshooting](#troubleshooting)
52- [Acknowledgement](#acknowledgement)
53- [License](#license)
54<!-- /TOC -->
55
56---
57
58## About
59
60`@adobe/fetch` in general adheres to the [Fetch API Specification](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), implementing a subset of the API. However, there are some notable deviations:
61
62* `Response.body` returns a Node.js [Readable stream](https://nodejs.org/api/stream.html#stream_readable_streams).
63* `Response.blob()` is not implemented. Use `Response.buffer()` instead.
64* `Response.formData()` is not implemented.
65* Cookies are not stored by default. However, cookies can be extracted and passed by manipulating request and response headers.
66* The following values of the `fetch()` option `cache` are supported: `'default'` (the implicit default) and `'no-store'`. All other values are currently ignored.
67* The following `fetch()` options are ignored due to the nature of Node.js and since `@adobe/fetch` doesn't have the concept of web pages: `mode`, `referrer`, `referrerPolicy`, `integrity` and `credentials`.
68* The `fetch()` option `keepalive` is not supported. But you can use the `h1.keepAlive` context option, as demonstrated [here](#http11-keep-alive).
69
70`@adobe/fetch` also supports the following non-spec extensions:
71
72* `Response.buffer()` returns a Node.js `Buffer`.
73* `Response.url` contains the final url when following redirects.
74* The `body` that can be sent in a `Request` can also be a `Readable` Node.js stream, a `Buffer`, a string or a plain object.
75* There are no forbidden header names.
76* The `Response` object has an extra property `httpVersion` which is one of `'1.0'`, `'1.1'` or `'2.0'`, depending on what was negotiated with the server.
77* The `Response` object has an extra property `fromCache` which determines whether the response was retrieved from cache.
78* The `Response` object has an extra property `decoded` which determines whether the response body was automatically decoded (see Fetch option `decode` below).
79* `Response.headers.plain()` returns the headers as a plain object.
80* `Response.headers.raw()` returns the internal/raw representation of the headers where e.g. the `Set-Cokkie` header is represented with an array of strings value.
81* The Fetch option `follow` allows to limit the number of redirects to follow (default: `20`).
82* The Fetch option `compress` enables transparent gzip/deflate/br content encoding (default: `true`).
83* The Fetch option `decode` enables transparent gzip/deflate/br content decoding (default: `true`).
84
85Note that non-standard Fetch options have been aligned with [node-fetch](https://github.com/node-fetch/node-fetch) where appropriate.
86
87## Features
88
89* [x] supports reasonable subset of the standard [Fetch specification](https://fetch.spec.whatwg.org/)
90* [x] Transparent handling of HTTP/1(.1) and HTTP/2 connections
91* [x] [RFC 7234](https://httpwg.org/specs/rfc7234.html) compliant cache
92* [x] Support `gzip/deflate/br` content encoding
93* [x] HTTP/2 request and response multiplexing support
94* [x] HTTP/2 Server Push support (transparent caching and explicit listener support)
95* [x] overridable User-Agent
96* [x] low-level HTTP/1.* agent/connect options support (e.g. `keepAlive`, `rejectUnauthorized`)
97
98## Installation
99
100> **Note**:
101>
102> As of v2 Node version >= 12 is required.
103
104```bash
105$ npm install @adobe/fetch
106```
107
108## API
109
110Apart from the standard Fetch API
111
112* `fetch()`
113* `Request`
114* `Response`
115* `Headers`
116* `Body`
117
118`@adobe/fetch` exposes the following non-spec extensions:
119
120* `context()` - creates a new customized API context
121* `reset()` - resets the current API context, i.e. closes pending sessions/sockets, clears internal caches, etc ...
122* `onPush()` - registers an HTTP/2 Server Push listener
123* `offPush()`- deregisters a listener previously registered with `onPush()`
124* `clearCache()` - clears the HTTP cache (cached responses)
125* `cacheStats()` - returns cache statistics
126* `noCache()` - creates a customized API context with disabled caching (_convenience_)
127* `h1()` - creates a customized API context with enforced HTTP/1.1 protocol (_convenience_)
128* `keepAlive()` - creates a customized API context with enforced HTTP/1.1 protocol and persistent connections (_convenience_)
129* `h1NoCache()` - creates a customized API context with disabled caching and enforced HTTP/1.1 protocol (_convenience_)
130* `keepAliveNoCache()` - creates a customized API context with disabled caching and enforced HTTP/1.1 protocol with persistent connections (_convenience_)
131* `createUrl()` - creates a URL with query parameters (_convenience_)
132* `timeoutSignal()` - ceates a timeout signal (_convenience_)
133
134### Context
135
136An API context allows to customize certain aspects of the implementation and provides isolation of internal structures (session caches, HTTP cache, etc.) per API context.
137
138The following options are supported:
139
140```ts
141interface ContextOptions {
142 /**
143 * Value of `user-agent` request header
144 * @default 'adobe-fetch/<version>'
145 */
146 userAgent?: string;
147 /**
148 * The maximum total size of the cached entries (in bytes). 0 disables caching.
149 * @default 100 * 1024 * 1024
150 */
151 maxCacheSize?: number;
152 /**
153 * The protocols to be negotiated, in order of preference
154 * @default [ALPN_HTTP2, ALPN_HTTP1_1, ALPN_HTTP1_0]
155 */
156 alpnProtocols?: ReadonlyArray< ALPNProtocol >;
157 /**
158 * How long (in milliseconds) should ALPN information be cached for a given host?
159 * @default 60 * 60 * 1000
160 */
161 alpnCacheTTL?: number;
162 /**
163 * (HTTPS only, applies to HTTP/1.x and HTTP/2)
164 * If not false, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails; err.code contains the OpenSSL error code.
165 * @default true
166 */
167 rejectUnauthorized?: boolean;
168 /**
169 * Maximum number of ALPN cache entries
170 * @default 100
171 */
172 alpnCacheSize?: number;
173 h1?: Http1Options;
174 h2?: Http2Options;
175};
176
177interface Http1Options {
178 /**
179 * Keep sockets around in a pool to be used by other requests in the future.
180 * @default false
181 */
182 keepAlive?: boolean;
183 /**
184 * When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive.
185 * Only relevant if keepAlive is set to true.
186 * @default 1000
187 */
188 keepAliveMsecs?: number;
189 /**
190 * (HTTPS only)
191 * If not false, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails; err.code contains the OpenSSL error code.
192 * @default true
193 */
194 rejectUnauthorized?: boolean;
195 /**
196 * (HTTPS only)
197 * Maximum number of TLS cached sessions. Use 0 to disable TLS session caching.
198 * @default 100
199 */
200 maxCachedSessions?: number;
201}
202
203interface Http2Options {
204 /**
205 * Max idle time in milliseconds after which a session will be automatically closed.
206 * @default 5 * 60 * 1000
207 */
208 idleSessionTimeout?: number;
209 /**
210 * Enable HTTP/2 Server Push?
211 * @default true
212 */
213 enablePush?: boolean;
214 /**
215 * Max idle time in milliseconds after which a pushed stream will be automatically closed.
216 * @default 5000
217 */
218 pushedStreamIdleTimeout?: number;
219 /**
220 * (HTTPS only)
221 * If not false, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails; err.code contains the OpenSSL error code.
222 * @default true
223 */
224 rejectUnauthorized?: boolean;
225};
226```
227
228## Common Usage Examples
229
230### Access Response Headers and other Meta data
231
232```javascript
233 const { fetch } = require('@adobe/fetch');
234
235 const resp = await fetch('https://httpbin.org/get');
236 console.log(resp.ok);
237 console.log(resp.status);
238 console.log(resp.statusText);
239 console.log(resp.httpVersion);
240 console.log(resp.headers.plain());
241 console.log(resp.headers.get('content-type'));
242```
243
244### Fetch JSON
245
246```javascript
247 const { fetch } = require('@adobe/fetch');
248
249 const resp = await fetch('https://httpbin.org/json');
250 const jsonData = await resp.json();
251```
252
253### Fetch text data
254
255```javascript
256 const { fetch } = require('@adobe/fetch');
257
258 const resp = await fetch('https://httpbin.org/');
259 const textData = await resp.text();
260```
261
262### Fetch binary data
263
264```javascript
265 const { fetch } = require('@adobe/fetch');
266
267 const resp = await fetch('https://httpbin.org//stream-bytes/65535');
268 const imageData = await resp.buffer();
269```
270
271### Specify a timeout for a `fetch` operation
272
273Using `timeoutSignal(ms)` non-spec extension:
274
275```javascript
276 const { fetch, timeoutSignal, AbortError } = require('@adobe/fetch');
277
278 const signal = timeoutSignal(1000);
279 try {
280 const resp = await fetch('https://httpbin.org/json', { signal });
281 const jsonData = await resp.json();
282 } catch (err) {
283 if (err instanceof AbortError) {
284 console.log('fetch timed out after 1s');
285 }
286 } finally {
287 // avoid pending timers which prevent node process from exiting
288 signal.clear();
289 }
290```
291
292Using `AbortController`:
293
294```javascript
295 const { fetch, AbortController, AbortError } = require('@adobe/fetch');
296
297 const controller = new AbortController();
298 const timerId = setTimeout(() => controller.abort(), 1000);
299 const { signal } = controller;
300
301 try {
302 const resp = await fetch('https://httpbin.org/json', { signal });
303 const jsonData = await resp.json();
304 } catch (err) {
305 if (err instanceof AbortError) {
306 console.log('fetch timed out after 1s');
307 }
308 } finally {
309 // avoid pending timers which prevent node process from exiting
310 clearTimeout(timerId);
311 }
312```
313
314### Stream an image
315
316```javascript
317 const fs = require('fs');
318 const { fetch } = require('@adobe/fetch');
319
320 const resp = await fetch('https://httpbin.org/image/jpeg');
321 resp.body.pipe(fs.createWriteStream('saved-image.jpg'));
322```
323
324### Post JSON
325
326```javascript
327 const { fetch } = require('@adobe/fetch');
328
329 const method = 'POST';
330 const body = { foo: 'bar' };
331 const resp = await fetch('https://httpbin.org/post', { method, body });
332```
333
334### Post JPEG image
335
336```javascript
337 const fs = require('fs');
338 const { fetch } = require('@adobe/fetch');
339
340 const method = 'POST';
341 const body = fs.createReadStream('some-image.jpg');
342 const headers = { 'content-type': 'image/jpeg' };
343 const resp = await fetch('https://httpbin.org/post', { method, body, headers });
344```
345
346### Post form data
347
348```javascript
349 const { FormData, Blob, File } = require('formdata-node'); // spec-compliant implementations
350 const { fileFromPath } = require('formdata-node/file-from-path'); // helper for creating File instance from disk file
351
352 const { fetch } = require('@adobe/fetch');
353
354 const method = 'POST';
355 const fd = new FormData();
356 fd.set('field1', 'foo');
357 fd.set('field2', 'bar');
358 fd.set('blob', new Blob([0x68, 0x65, 0x6c, 0x69, 0x78, 0x2d, 0x66, 0x65, 0x74, 0x63, 0x68]));
359 fd.set('file', new File(['File content goes here'], 'file.txt'));
360 fd.set('other_file', await fileFromPath('/foo/bar.jpg', 'bar.jpg', { type: 'image/jpeg' }));
361 const resp = await fetch('https://httpbin.org/post', { method, body: fd });
362```
363
364### GET with query parameters object
365
366```javascript
367const { createUrl, fetch } = require('@adobe/fetch');
368
369const qs = {
370 fake: 'dummy',
371 foo: 'bar',
372 rumple: "stiltskin",
373};
374
375const resp = await fetch(createUrl('https://httpbin.org/json', qs));
376```
377
378or using `URLSearchParams`:
379
380```javascript
381const { fetch } = require('@adobe/fetch');
382
383const body = new URLSearchParams({
384 fake: 'dummy',
385 foo: 'bar',
386 rumple: "stiltskin",
387});
388
389const resp = await fetch('https://httpbin.org/json', { body });
390```
391
392### Cache
393
394Responses of `GET` and `HEAD` requests are by default cached, according to the rules of [RFC 7234](https://httpwg.org/specs/rfc7234.html):
395
396```javascript
397const { fetch } = require('@adobe/fetch');
398
399const url = 'https://httpbin.org/cache/60'; // -> max-age=60 (seconds)
400// send initial request, priming cache
401let resp = await fetch(url);
402assert(resp.ok);
403assert(!resp.fromCache);
404
405// re-send request and verify it's served from cache
406resp = await fetch(url);
407assert(resp.ok);
408assert(resp.fromCache);
409```
410
411You can disable caching per request with the `cache: 'no-store'` option:
412
413```javascript
414const { fetch } = require('@adobe/fetch');
415
416const resp = await fetch('https://httbin.org/', { cache: 'no-store' });
417assert(resp.ok);
418assert(!resp.fromCache);
419```
420
421You can disable caching entirely:
422
423```javascript
424const { fetch } = require('@adobe/fetch').noCache();
425```
426
427## Advanced Usage Examples
428
429### HTTP/2 Server Push
430
431Note that pushed resources will be automatically and transparently added to the cache.
432You can however add a listener which will be notified on every pushed (and cached) resource.
433
434```javascript
435 const { fetch, onPush } = require('@adobe/fetch');
436
437 onPush((url, response) => console.log(`received server push: ${url} status ${response.status}`));
438
439 const resp = await fetch('https://nghttp2.org');
440 console.log(`Http version: ${resp.httpVersion}`);
441```
442
443### Use h2c (http2 cleartext w/prior-knowledge) protocol
444
445```javascript
446 const { fetch } = require('@adobe/fetch');
447
448 const resp = await fetch('http2://nghttp2.org');
449 console.log(`Http version: ${resp.httpVersion}`);
450```
451
452### Force HTTP/1(.1) protocol
453
454```javascript
455 const { fetch } = require('@adobe/fetch').h1();
456
457 const resp = await fetch('https://nghttp2.org');
458 console.log(`Http version: ${resp.httpVersion}`);
459```
460
461### HTTP/1.1 Keep-Alive
462
463```javascript
464const { fetch } = require('@adobe/fetch').keepAlive();
465
466const resp = await fetch('https://httpbin.org/status/200');
467console.log(`Connection: ${resp.headers.get('connection')}`); // -> keep-alive
468```
469
470### Extract Set-Cookie Header
471
472Unlike browsers, you can access raw `Set-Cookie` headers manually using `Headers.raw()`. This is an `@adobe/fetch` only API.
473
474```javascript
475const { fetch } = require('@adobe/fetch');
476
477const resp = await fetch('https://httpbin.org/cookies/set?a=1&b=2');
478// returns an array of values, instead of a string of comma-separated values
479console.log(resp.headers.raw()['set-cookie']);
480```
481
482### Self-signed Certificates
483
484```javascript
485const { fetch } = require('@adobe/fetch').context({ rejectUnauthorized: false });
486
487const resp = await fetch('https://localhost:8443/'); // a server using a self-signed certificate
488```
489
490### Set cache size limit
491
492```javascript
493 const { fetch, cacheStats } = require('@adobe/fetch').context({
494 maxCacheSize: 100 * 1024, // 100kb (Default: 100mb)
495 });
496
497 let resp = await fetch('https://httpbin.org/bytes/60000'); // ~60kb response
498 resp = await fetch('https://httpbin.org/bytes/50000'); // ~50kb response
499 console.log(cacheStats());
500```
501
502### Disable caching
503
504```javascript
505 const { fetch } = require('@adobe/fetch').noCache();
506
507 let resp = await fetch('https://httpbin.org/cache/60'); // -> max-age=60 (seconds)
508 // re-fetch
509 resp = await fetch('https://httpbin.org/cache/60');
510 assert(!resp.fromCache);
511```
512
513### Set a custom user agent
514
515```javascript
516 const { fetch } = require('@adobe/fetch').context({
517 userAgent: 'custom-fetch'
518 });
519
520 const resp = await fetch('https://httpbin.org//user-agent');
521 const json = await resp.json();
522 console.log(json['user-agent']);
523```
524
525## More examples
526
527More example code can be found in the [test source files](/test/).
528
529## Development
530
531### Build
532
533```bash
534$ npm install
535```
536
537### Test
538
539```bash
540$ npm test
541```
542
543### Lint
544
545```bash
546$ npm run lint
547```
548
549### Troubleshooting
550
551You can enable `@adobe/fetch` low-level debug console output by setting the `DEBUG` environment variable to `adobe/fetch*`, e.g.:
552
553```bash
554$ DEBUG=adobe/fetch* node test.js
555```
556
557This will produce console outout similar to:
558
559```bash
560 ...
561 adobe/fetch:core established TLS connection: #48 (www.nghttp2.org) +2s
562 adobe/fetch:core www.nghttp2.org -> h2 +0ms
563 adobe/fetch:h2 reusing socket #48 (www.nghttp2.org) +2s
564 adobe/fetch:h2 GET www.nghttp2.org/httpbin/user-agent +0ms
565 adobe/fetch:h2 session https://www.nghttp2.org established +1ms
566 adobe/fetch:h2 caching session https://www.nghttp2.org +0ms
567 adobe/fetch:h2 session https://www.nghttp2.org remoteSettings: {"headerTableSize":8192,"enablePush":true,"initialWindowSize":1048576,"maxFrameSize":16384,"maxConcurrentStreams":100,"maxHeaderListSize":4294967295,"maxHeaderSize":4294967295,"enableConnectProtocol":true} +263ms
568 adobe/fetch:h2 session https://www.nghttp2.org localSettings: {"headerTableSize":4096,"enablePush":true,"initialWindowSize":65535,"maxFrameSize":16384,"maxConcurrentStreams":4294967295,"maxHeaderListSize":4294967295,"maxHeaderSize":4294967295,"enableConnectProtocol":false} +0ms
569 adobe/fetch:h2 session https://www.nghttp2.org closed +6ms
570 adobe/fetch:h2 discarding cached session https://www.nghttp2.org +0ms
571 ...
572```
573
574Additionally, you can enable Node.js low-level debug console output by setting the `NODE_DEBUG` environment variable appropriately, e.g.
575
576```bash
577$ export NODE_DEBUG=http*,stream*
578$ export DEBUG=adobe/fetch*
579
580$ node test.js
581```
582
583> Note: this will flood the console with highly verbose debug output.
584
585## Acknowledgement
586
587Thanks to [node-fetch](https://github.com/node-fetch/node-fetch) and [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference.
588
589## License
590
591[Apache 2.0](LICENSE.txt)