UNPKG

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