lib: add fetch

Fixes: https://github.com/nodejs/node/issues/19393

PR-URL: https://github.com/nodejs/node/pull/41749
Refs: https://github.com/nodejs/undici/pull/1183
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
Reviewed-By: Mestery <mestery@protonmail.com>
Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
This commit is contained in:
Michaël Zasso 2022-02-01 09:17:50 +01:00 committed by GitHub
parent 7123a00b03
commit 6ec2253926
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 8076 additions and 3 deletions

View file

@ -338,5 +338,9 @@ module.exports = {
atob: 'readable',
performance: 'readable',
structuredClone: 'readable',
fetch: 'readable',
Headers: 'readable',
Request: 'readable',
Response: 'readable',
},
};

25
LICENSE
View file

@ -634,6 +634,31 @@ The externally maintained libraries used by Node.js are:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
- undici, located at deps/undici, is licensed as follows:
"""
MIT License
Copyright (c) Matteo Collina and Undici contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- OpenSSL, located at deps/openssl, is licensed as follows:
"""
Apache License

21
deps/undici/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) Matteo Collina and Undici contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7887
deps/undici/undici.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -280,6 +280,14 @@ effort to report stack traces relative to the original source file.
Overriding `Error.prepareStackTrace` prevents `--enable-source-maps` from
modifying the stack trace.
### `--experimental-fetch`
<!-- YAML
added: REPLACEME
-->
Enable experimental support for the [Fetch API][].
### `--experimental-import-meta-resolve`
<!-- YAML
@ -1559,6 +1567,7 @@ Node.js options that are allowed are:
* `--enable-fips`
* `--enable-source-maps`
* `--experimental-abortcontroller`
* `--experimental-fetch`
* `--experimental-import-meta-resolve`
* `--experimental-json-modules`
* `--experimental-loader`
@ -1952,6 +1961,7 @@ $ node --max-old-space-size=1536 index.js
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
[CommonJS]: modules.md
[ECMAScript module loader]: esm.md#loaders
[Fetch API]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
[Modules loaders]: packages.md#modules-loaders
[OSSL_PROVIDER-legacy]: https://www.openssl.org/docs/man3.0/man7/OSSL_PROVIDER-legacy.html
[REPL]: repl.md

View file

@ -333,6 +333,17 @@ A browser-compatible implementation of the `EventTarget` class. See
This variable may appear to be global but is not. See [`exports`][].
## `fetch`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental. Enable this API with the [`--experimental-fetch`][]
> CLI flag.
A browser-compatible implementation of the [`fetch()`][] function.
## `global`
<!-- YAML
@ -348,6 +359,17 @@ within the browser `var something` will define a new global variable. In
Node.js this is different. The top-level scope is not the global scope;
`var something` inside a Node.js module will be local to that module.
## Class `Headers`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental. Enable this API with the [`--experimental-fetch`][]
> CLI flag.
A browser-compatible implementation of {Headers}.
## `MessageChannel`
<!-- YAML
@ -442,6 +464,28 @@ DataHandler.prototype.load = async function load(key) {
This variable may appear to be global but is not. See [`require()`][].
## `Response`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental. Enable this API with the [`--experimental-fetch`][]
> CLI flag.
A browser-compatible implementation of {Response}.
## `Request`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental. Enable this API with the [`--experimental-fetch`][]
> CLI flag.
A browser-compatible implementation of {Request}.
## `setImmediate(callback[, ...args])`
<!-- YAML
@ -546,6 +590,7 @@ The object that acts as the namespace for all W3C
[WebAssembly][webassembly-org] related functionality. See the
[Mozilla Developer Network][webassembly-mdn] for usage and compatibility.
[`--experimental-fetch`]: cli.md#--experimental-fetch
[`AbortController`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
[`DOMException`]: https://developer.mozilla.org/en-US/docs/Web/API/DOMException
[`EventTarget` and `Event` API]: events.md#eventtarget-and-event-api
@ -565,6 +610,7 @@ The object that acts as the namespace for all W3C
[`clearTimeout`]: timers.md#cleartimeouttimeout
[`console`]: console.md
[`exports`]: modules.md#exports
[`fetch()`]: https://developer.mozilla.org/en-US/docs/Web/API/fetch
[`module`]: modules.md#module
[`perf_hooks.performance`]: perf_hooks.md#perf_hooksperformance
[`process.nextTick()`]: process.md#processnexttickcallback-args

View file

@ -139,6 +139,9 @@ Requires Node.js to be built with
.It Fl -enable-source-maps
Enable Source Map V3 support for stack traces.
.
.It Fl -experimental-fetch
Enable experimental support for the Fetch API.
.
.It Fl -experimental-import-meta-resolve
Enable experimental ES modules support for import.meta.resolve().
.

View file

@ -14,6 +14,11 @@ const {
getEmbedderOptions,
} = require('internal/options');
const { reconnectZeroFillToggle } = require('internal/buffer');
const {
defineOperation,
emitExperimentalWarning,
exposeInterface,
} = require('internal/util');
const { Buffer } = require('buffer');
const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes;
@ -30,6 +35,7 @@ function prepareMainThreadExecution(expandArgv1 = false) {
setupPerfHooks();
setupInspectorHooks();
setupWarningHandler();
setupFetch();
// Resolve the coverage directory to an absolute path, and
// overwrite process.env so that the original path gets passed
@ -139,6 +145,21 @@ function setupWarningHandler() {
}
}
// https://fetch.spec.whatwg.org/
function setupFetch() {
if (!getOptionValue('--experimental-fetch')) {
return;
}
emitExperimentalWarning('Fetch');
const undici = require('internal/deps/undici/undici');
defineOperation(globalThis, 'fetch', undici.fetch);
exposeInterface(globalThis, 'Headers', undici.Headers);
exposeInterface(globalThis, 'Request', undici.Request);
exposeInterface(globalThis, 'Response', undici.Response);
}
// Setup User-facing NODE_V8_COVERAGE environment variable that writes
// ScriptCoverage to a specified file.
function setupCoverageHooks(dir) {
@ -479,6 +500,7 @@ module.exports = {
patchProcessObject,
setupCoverageHooks,
setupWarningHandler,
setupFetch,
setupDebugEnv,
setupPerfHooks,
prepareMainThreadExecution,

View file

@ -17,6 +17,7 @@ const {
setupCoverageHooks,
setupInspectorHooks,
setupWarningHandler,
setupFetch,
setupDebugEnv,
setupPerfHooks,
initializeDeprecations,
@ -67,6 +68,7 @@ setupInspectorHooks();
setupDebugEnv();
setupWarningHandler();
setupFetch();
initializeSourceMapsHandlers();
// Since worker threads cannot switch cwd, we do not need to

View file

@ -51,6 +51,7 @@
'deps/acorn/acorn-walk/dist/walk.js',
'deps/cjs-module-lexer/lexer.js',
'deps/cjs-module-lexer/dist/lexer.js',
'deps/undici/undici.js',
],
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
'mkcodecache_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)mkcodecache<(EXECUTABLE_SUFFIX)',

View file

@ -315,6 +315,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
kAllowedInEnvironment);
AddOption("--experimental-abortcontroller", "",
NoOp{}, kAllowedInEnvironment);
AddOption("--experimental-fetch",
"experimental Fetch API",
&EnvironmentOptions::experimental_fetch,
kAllowedInEnvironment);
AddOption("--experimental-json-modules", "", NoOp{}, kAllowedInEnvironment);
AddOption("--experimental-loader",
"use the specified module as a custom loader",

View file

@ -107,6 +107,7 @@ class EnvironmentOptions : public Options {
std::vector<std::string> conditions;
std::string dns_result_order;
bool enable_source_maps = false;
bool experimental_fetch = false;
std::string experimental_specifier_resolution;
bool experimental_wasm_modules = false;
bool experimental_import_meta_resolve = false;

View file

@ -68,12 +68,12 @@ if (process.argv.length === 2 &&
!process.env.NODE_SKIP_FLAG_CHECK &&
isMainThread &&
hasCrypto &&
require.main &&
require('cluster').isPrimary) {
require('cluster').isPrimary &&
fs.existsSync(process.argv[1])) {
// The copyright notice is relatively big and the flags could come afterwards.
const bytesToRead = 1500;
const buffer = Buffer.allocUnsafe(bytesToRead);
const fd = fs.openSync(require.main.filename, 'r');
const fd = fs.openSync(process.argv[1], 'r');
const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead);
fs.closeSync(fd);
const source = buffer.toString('utf8', 0, bytesRead);
@ -300,6 +300,15 @@ if (global.structuredClone) {
knownGlobals.push(global.structuredClone);
}
if (global.fetch) {
knownGlobals.push(
global.fetch,
global.Request,
global.Response,
global.Headers,
);
}
function allowGlobals(...allowlist) {
knownGlobals = knownGlobals.concat(allowlist);
}

View file

@ -0,0 +1,32 @@
// Flags: --experimental-fetch --no-warnings
import '../common/index.mjs';
import assert from 'assert';
import events from 'events';
import http from 'http';
assert.strictEqual(typeof globalThis.fetch, 'function');
assert.strictEqual(typeof globalThis.Headers, 'function');
assert.strictEqual(typeof globalThis.Request, 'function');
assert.strictEqual(typeof globalThis.Response, 'function');
const server = http.createServer((req, res) => {
// TODO: Remove this once keep-alive behavior can be disabled from the client
// side.
res.setHeader('Keep-Alive', 'timeout=0, max=0');
res.end('Hello world');
});
server.listen(0);
await events.once(server, 'listening');
const port = server.address().port;
const response = await fetch(`http://localhost:${port}`);
assert(response instanceof Response);
assert.strictEqual(response.status, 200);
assert.strictEqual(response.statusText, 'OK');
const body = await response.text();
assert.strictEqual(body, 'Hello world');
server.close();

View file

@ -270,6 +270,10 @@ const customTypesMap = {
'webstreams.md#class-textencoderstream',
'TextDecoderStream':
'webstreams.md#class-textdecoderstream',
'Headers': 'https://developer.mozilla.org/en-US/docs/Web/API/Headers',
'Response': 'https://developer.mozilla.org/en-US/docs/Web/API/Response',
'Request': 'https://developer.mozilla.org/en-US/docs/Web/API/Request',
};
const arrayPart = /(?:\[])+$/;

View file

@ -63,6 +63,8 @@ licenseText="$(cat deps/llhttp/LICENSE-MIT)"
addlicense "llhttp" "deps/llhttp" "$licenseText"
licenseText="$(cat "${rootdir}"/deps/corepack/LICENSE.md)"
addlicense "corepack" "deps/corepack" "$licenseText"
licenseText="$(cat "${rootdir}"/deps/undici/LICENSE)"
addlicense "undici" "deps/undici" "$licenseText"
licenseText="$(cat "${rootdir}"/deps/openssl/openssl/LICENSE.txt)"
addlicense "OpenSSL" "deps/openssl" "$licenseText"
licenseText="$(curl -sL https://raw.githubusercontent.com/bestiejs/punycode.js/HEAD/LICENSE-MIT.txt)"