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 |
|
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 |
|
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 |
|
85 | Note 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 |
|
110 | Apart 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 |
|
136 | An 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 |
|
138 | The following options are supported:
|
139 |
|
140 | ```ts
|
141 | interface 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 |
|
177 | interface 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 |
|
203 | interface 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 |
|
273 | Using `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 |
|
292 | Using `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
|
367 | const { createUrl, fetch } = require('@adobe/fetch');
|
368 |
|
369 | const qs = {
|
370 | fake: 'dummy',
|
371 | foo: 'bar',
|
372 | rumple: "stiltskin",
|
373 | };
|
374 |
|
375 | const resp = await fetch(createUrl('https://httpbin.org/json', qs));
|
376 | ```
|
377 |
|
378 | or using `URLSearchParams`:
|
379 |
|
380 | ```javascript
|
381 | const { fetch } = require('@adobe/fetch');
|
382 |
|
383 | const body = new URLSearchParams({
|
384 | fake: 'dummy',
|
385 | foo: 'bar',
|
386 | rumple: "stiltskin",
|
387 | });
|
388 |
|
389 | const resp = await fetch('https://httpbin.org/json', { body });
|
390 | ```
|
391 |
|
392 | ### Cache
|
393 |
|
394 | Responses 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
|
397 | const { fetch } = require('@adobe/fetch');
|
398 |
|
399 | const url = 'https://httpbin.org/cache/60'; // -> max-age=60 (seconds)
|
400 | // send initial request, priming cache
|
401 | let resp = await fetch(url);
|
402 | assert(resp.ok);
|
403 | assert(!resp.fromCache);
|
404 |
|
405 | // re-send request and verify it's served from cache
|
406 | resp = await fetch(url);
|
407 | assert(resp.ok);
|
408 | assert(resp.fromCache);
|
409 | ```
|
410 |
|
411 | You can disable caching per request with the `cache: 'no-store'` option:
|
412 |
|
413 | ```javascript
|
414 | const { fetch } = require('@adobe/fetch');
|
415 |
|
416 | const resp = await fetch('https://httbin.org/', { cache: 'no-store' });
|
417 | assert(resp.ok);
|
418 | assert(!resp.fromCache);
|
419 | ```
|
420 |
|
421 | You can disable caching entirely:
|
422 |
|
423 | ```javascript
|
424 | const { fetch } = require('@adobe/fetch').noCache();
|
425 | ```
|
426 |
|
427 | ## Advanced Usage Examples
|
428 |
|
429 | ### HTTP/2 Server Push
|
430 |
|
431 | Note that pushed resources will be automatically and transparently added to the cache.
|
432 | You 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
|
464 | const { fetch } = require('@adobe/fetch').keepAlive();
|
465 |
|
466 | const resp = await fetch('https://httpbin.org/status/200');
|
467 | console.log(`Connection: ${resp.headers.get('connection')}`); // -> keep-alive
|
468 | ```
|
469 |
|
470 | ### Extract Set-Cookie Header
|
471 |
|
472 | Unlike browsers, you can access raw `Set-Cookie` headers manually using `Headers.raw()`. This is an `@adobe/fetch` only API.
|
473 |
|
474 | ```javascript
|
475 | const { fetch } = require('@adobe/fetch');
|
476 |
|
477 | const 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
|
479 | console.log(resp.headers.raw()['set-cookie']);
|
480 | ```
|
481 |
|
482 | ### Self-signed Certificates
|
483 |
|
484 | ```javascript
|
485 | const { fetch } = require('@adobe/fetch').context({ rejectUnauthorized: false });
|
486 |
|
487 | const 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 |
|
527 | More 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 |
|
551 | You 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 |
|
557 | This 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 |
|
574 | Additionally, 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 |
|
587 | Thanks 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)
|