diff --git a/package.json b/package.json index 5e5a8c6f4..888728c2d 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "cacheable-lookup": "^6.0.0", "cacheable-request": "^7.0.1", "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", + "http2-wrapper": "^2.0.0", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" diff --git a/readme.md b/readme.md index fc5c7f6e1..7fe4c2422 100644 --- a/readme.md +++ b/readme.md @@ -699,7 +699,7 @@ If set to `true`, Got will additionally accept HTTP2 requests. It will choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol. -**Note:** This option requires Node.js 15 or later as HTTP2 support on older Node.js versions are very buggy. +**Note:** This option requires Node.js 15.10.0 or newer as HTTP/2 support on older Node.js versions is very buggy. **Note:** Overriding `options.request` will disable HTTP2 support. @@ -2187,42 +2187,43 @@ The Electron `net` module is not consistent with the Node.js `http` module. See ## Comparison -| | `got` | [`request`][r0] | [`node-fetch`][n0] | [`ky`][k0] | [`axios`][a0] | [`superagent`][s0] | -|-----------------------|:------------------:|:------------------:|:--------------------:|:------------------------:|:------------------:|:----------------------:| -| HTTP/2 support | :sparkle: | :x: | :x: | :x: | :x: | :heavy_check_mark:\*\* | -| Browser support | :x: | :x: | :heavy_check_mark:\* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Promise API | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Stream API | :heavy_check_mark: | :heavy_check_mark: | Node.js only | :x: | :x: | :heavy_check_mark: | -| Pagination API | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | -| Request cancelation | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| RFC compliant caching | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | -| Cookies (out-of-box) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | -| Follows redirects | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Retries on failure | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: | -| Progress events | :heavy_check_mark: | :x: | :x: | :heavy_check_mark:\*\*\* | Browser only | :heavy_check_mark: | -| Handles gzip/deflate | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Advanced timeouts | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | -| Timings | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | -| Errors with metadata | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | -| JSON mode | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Custom defaults | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | -| Composable | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | -| Hooks | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | -| Issues open | [![][gio]][g1] | [![][rio]][r1] | [![][nio]][n1] | [![][kio]][k1] | [![][aio]][a1] | [![][sio]][s1] | -| Issues closed | [![][gic]][g2] | [![][ric]][r2] | [![][nic]][n2] | [![][kic]][k2] | [![][aic]][a2] | [![][sic]][s2] | -| Downloads | [![][gd]][g3] | [![][rd]][r3] | [![][nd]][n3] | [![][kd]][k3] | [![][ad]][a3] | [![][sd]][s3] | -| Coverage | [![][gc]][g4] | [![][rc]][r4] | [![][nc]][n4] | [![][kc]][k4] | [![][ac]][a4] | [![][sc]][s4] | -| Build | [![][gb]][g5] | [![][rb]][r5] | [![][nb]][n5] | [![][kb]][k5] | [![][ab]][a5] | [![][sb]][s5] | -| Bugs | [![][gbg]][g6] | [![][rbg]][r6] | [![][nbg]][n6] | [![][kbg]][k6] | [![][abg]][a6] | [![][sbg]][s6] | -| Dependents | [![][gdp]][g7] | [![][rdp]][r7] | [![][ndp]][n7] | [![][kdp]][k7] | [![][adp]][a7] | [![][sdp]][s7] | -| Install size | [![][gis]][g8] | [![][ris]][r8] | [![][nis]][n8] | [![][kis]][k8] | [![][ais]][a8] | [![][sis]][s8] | -| GitHub stars | [![][gs]][g9] | [![][rs]][r9] | [![][ns]][n9] | [![][ks]][k9] | [![][as]][a9] | [![][ss]][s9] | -| TypeScript support | [![][gts]][g10] | [![][rts]][r10] | [![][nts]][n10] | [![][kts]][k10] | [![][ats]][a10] | [![][sts]][s11] | -| Last commit | [![][glc]][g11] | [![][rlc]][r11] | [![][nlc]][n11] | [![][klc]][k11] | [![][alc]][a11] | [![][slc]][s11] | +| | `got` | [`request`][r0] | [`node-fetch`][n0] | [`ky`][k0] | [`axios`][a0] | [`superagent`][s0] | +|-----------------------|:-------------------:|:------------------:|:--------------------:|:------------------------:|:------------------:|:----------------------:| +| HTTP/2 support | :heavy_check_mark:¹ | :x: | :x: | :x: | :x: | :heavy_check_mark:\*\* | +| Browser support | :x: | :x: | :heavy_check_mark:\* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Promise API | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Stream API | :heavy_check_mark: | :heavy_check_mark: | Node.js only | :x: | :x: | :heavy_check_mark: | +| Pagination API | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | +| Request cancelation | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| RFC compliant caching | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | +| Cookies (out-of-box) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | +| Follows redirects | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Retries on failure | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: | +| Progress events | :heavy_check_mark: | :x: | :x: | :heavy_check_mark:\*\*\* | Browser only | :heavy_check_mark: | +| Handles gzip/deflate | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Advanced timeouts | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | +| Timings | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | +| Errors with metadata | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| JSON mode | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Custom defaults | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| Composable | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | +| Hooks | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| Issues open | [![][gio]][g1] | [![][rio]][r1] | [![][nio]][n1] | [![][kio]][k1] | [![][aio]][a1] | [![][sio]][s1] | +| Issues closed | [![][gic]][g2] | [![][ric]][r2] | [![][nic]][n2] | [![][kic]][k2] | [![][aic]][a2] | [![][sic]][s2] | +| Downloads | [![][gd]][g3] | [![][rd]][r3] | [![][nd]][n3] | [![][kd]][k3] | [![][ad]][a3] | [![][sd]][s3] | +| Coverage | [![][gc]][g4] | [![][rc]][r4] | [![][nc]][n4] | [![][kc]][k4] | [![][ac]][a4] | [![][sc]][s4] | +| Build | [![][gb]][g5] | [![][rb]][r5] | [![][nb]][n5] | [![][kb]][k5] | [![][ab]][a5] | [![][sb]][s5] | +| Bugs | [![][gbg]][g6] | [![][rbg]][r6] | [![][nbg]][n6] | [![][kbg]][k6] | [![][abg]][a6] | [![][sbg]][s6] | +| Dependents | [![][gdp]][g7] | [![][rdp]][r7] | [![][ndp]][n7] | [![][kdp]][k7] | [![][adp]][a7] | [![][sdp]][s7] | +| Install size | [![][gis]][g8] | [![][ris]][r8] | [![][nis]][n8] | [![][kis]][k8] | [![][ais]][a8] | [![][sis]][s8] | +| GitHub stars | [![][gs]][g9] | [![][rs]][r9] | [![][ns]][n9] | [![][ks]][k9] | [![][as]][a9] | [![][ss]][s9] | +| TypeScript support | [![][gts]][g10] | [![][rts]][r10] | [![][nts]][n10] | [![][kts]][k10] | [![][ats]][a10] | [![][sts]][s11] | +| Last commit | [![][glc]][g11] | [![][rlc]][r11] | [![][nlc]][n11] | [![][klc]][k11] | [![][alc]][a11] | [![][slc]][s11] | \* It's almost API compatible with the browser `fetch` API.\ \*\* Need to switch the protocol manually. Doesn't accept PUSH streams and doesn't reuse HTTP/2 sessions.\ \*\*\* Currently, only `DownloadProgress` event is supported, `UploadProgress` event is not supported.\ +¹ Requires Node.js 15.10.0 or above. :sparkle: Almost-stable feature, but the API may change. Don't hesitate to try it out!\ :grey_question: Feature in early stage of development. Very experimental. diff --git a/source/core/index.ts b/source/core/index.ts index a60c91bee..75e231395 100644 --- a/source/core/index.ts +++ b/source/core/index.ts @@ -11,8 +11,7 @@ import timer, {ClientRequestWithTimings, Timings, IncomingMessageWithTimings} fr import CacheableLookup from 'cacheable-lookup'; import * as CacheableRequest from 'cacheable-request'; import decompressResponse = require('decompress-response'); -// @ts-expect-error Missing types -import * as http2wrapper from 'http2-wrapper'; +import http2wrapper = require('http2-wrapper'); import lowercaseKeys = require('lowercase-keys'); import ResponseLike = require('responselike'); import is, {assert} from '@sindresorhus/is'; @@ -31,6 +30,8 @@ import normalizePromiseArguments from '../as-promise/normalize-arguments'; import {PromiseOnly} from '../as-promise/types'; import calculateRetryDelay from './calculate-retry-delay'; +const [major, minor] = process.versions.node.split('.').map(x => Number(x)) as [number, number, number]; + let globalDnsCache: CacheableLookup; type HttpRequestFunction = typeof httpRequest; @@ -600,7 +601,7 @@ interface PlainOptions extends URLOptions { It will choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol. - __Note__: This option requires Node.js 15 or later as HTTP2 support on older Node.js versions are very buggy. + __Note__: This option requires Node.js 15.10.0 or newer as HTTP/2 support on older Node.js versions is very buggy. __Note__: Overriding `options.request` will disable HTTP2 support. @@ -2342,6 +2343,11 @@ export default class Request extends Duplex implements RequestEvents { // Fallback function let fallbackFn: HttpRequestFunction; if (options.http2) { + if (major < 15 || (major === 15 && minor < 10)) { + throw new Error('To use the `http2` option, install Node.js 15.10.0 or above'); + } + + // @ts-expect-error TS bug? fallbackFn = http2wrapper.auto; } else { fallbackFn = isHttps ? https.request : http.request; diff --git a/test/cache.ts b/test/cache.ts index 9e62df4f5..c21b46d77 100644 --- a/test/cache.ts +++ b/test/cache.ts @@ -349,7 +349,19 @@ test('works with http2', async t => { cache }); - await t.notThrowsAsync(client('https://httpbin.org/anything')); + try { + await client('https://httpbin.org/anything'); + + t.pass(); + } catch (error) { + if (error.message.includes('install Node.js')) { + t.pass(); + + return; + } + + t.fail(error); + } }); test('http-cache-semantics typings', t => { diff --git a/test/https.ts b/test/https.ts index dfd43ade3..126730681 100644 --- a/test/https.ts +++ b/test/https.ts @@ -142,12 +142,24 @@ test('http2', async t => { http2: true }); - const {headers, body} = await promise; - await promise.json(); + try { + const {headers, body} = await promise; + await promise.json(); - // @ts-expect-error Pseudo headers may not be strings - t.is(headers[':status'], 200); - t.is(typeof body, 'string'); + // @ts-expect-error Pseudo headers may not be strings + t.is(headers[':status'], 200); + t.is(typeof body, 'string'); + + t.pass(); + } catch (error) { + if (error.message.includes('install Node.js')) { + t.pass(); + + return; + } + + t.fail(error); + } }); test.serial('deprecated `rejectUnauthorized` option', withHttpsServer(), async (t, server, got) => {