mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
refactor(node): reimplement http client (#19122)
This commit reimplements most of "node:http" client APIs using "ext/fetch". There is some duplicated code and two removed Node compat tests that will be fixed in follow up PRs. --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
a22388bbd1
commit
867a6d3032
14 changed files with 1702 additions and 1163 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1155,6 +1155,7 @@ dependencies = [
|
|||
"cbc",
|
||||
"data-encoding",
|
||||
"deno_core",
|
||||
"deno_fetch",
|
||||
"deno_fs",
|
||||
"deno_media_type",
|
||||
"deno_npm",
|
||||
|
@ -1183,6 +1184,7 @@ dependencies = [
|
|||
"pbkdf2",
|
||||
"rand",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"ring",
|
||||
"ripemd",
|
||||
"rsa",
|
||||
|
|
|
@ -362,11 +362,13 @@
|
|||
// failing
|
||||
//"test-http-client-set-timeout.js",
|
||||
"test-http-localaddress.js",
|
||||
"test-http-outgoing-buffer.js",
|
||||
// TODO(bartlomieju): temporarily disabled while we iterate on the HTTP client
|
||||
// "test-http-outgoing-buffer.js",
|
||||
"test-http-outgoing-internal-headernames-getter.js",
|
||||
"test-http-outgoing-internal-headernames-setter.js",
|
||||
"test-http-outgoing-internal-headers.js",
|
||||
"test-http-outgoing-message-inheritance.js",
|
||||
// TODO(bartlomieju): temporarily disabled while we iterate on the HTTP client
|
||||
// "test-http-outgoing-message-inheritance.js",
|
||||
"test-http-outgoing-renderHeaders.js",
|
||||
"test-http-outgoing-settimeout.js",
|
||||
"test-net-access-byteswritten.js",
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
// deno-fmt-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
|
||||
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||
// Taken from Node 18.12.1
|
||||
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually
|
||||
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { getDefaultHighWaterMark } = require('internal/streams/state');
|
||||
|
||||
const http = require('http');
|
||||
const OutgoingMessage = http.OutgoingMessage;
|
||||
|
||||
const msg = new OutgoingMessage();
|
||||
msg._implicitHeader = function() {};
|
||||
|
||||
// Writes should be buffered until highwatermark
|
||||
// even when no socket is assigned.
|
||||
|
||||
assert.strictEqual(msg.write('asd'), true);
|
||||
while (msg.write('asd'));
|
||||
const highwatermark = msg.writableHighWaterMark || getDefaultHighWaterMark();
|
||||
assert(msg.outputSize >= highwatermark);
|
|
@ -1,43 +0,0 @@
|
|||
// deno-fmt-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
|
||||
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||
// Taken from Node 18.12.1
|
||||
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { OutgoingMessage } = require('http');
|
||||
const { Writable } = require('stream');
|
||||
const assert = require('assert');
|
||||
|
||||
// Check that OutgoingMessage can be used without a proper Socket
|
||||
// Refs: https://github.com/nodejs/node/issues/14386
|
||||
// Refs: https://github.com/nodejs/node/issues/14381
|
||||
|
||||
class Response extends OutgoingMessage {
|
||||
_implicitHeader() {}
|
||||
}
|
||||
|
||||
const res = new Response();
|
||||
|
||||
let firstChunk = true;
|
||||
|
||||
const ws = new Writable({
|
||||
write: common.mustCall((chunk, encoding, callback) => {
|
||||
if (firstChunk) {
|
||||
assert(chunk.toString().endsWith('hello world'));
|
||||
firstChunk = false;
|
||||
} else {
|
||||
assert.strictEqual(chunk.length, 0);
|
||||
}
|
||||
setImmediate(callback);
|
||||
}, 2)
|
||||
});
|
||||
|
||||
res.socket = ws;
|
||||
ws._httpMessage = res;
|
||||
res.connection = ws;
|
||||
|
||||
res.end('hello world');
|
|
@ -185,6 +185,7 @@ Deno.test("[node/http] server can respond with 101, 204, 205, 304 status", async
|
|||
|
||||
Deno.test("[node/http] request default protocol", async () => {
|
||||
const promise = deferred<void>();
|
||||
const promise2 = deferred<void>();
|
||||
const server = http.createServer((_, res) => {
|
||||
res.end("ok");
|
||||
});
|
||||
|
@ -198,6 +199,7 @@ Deno.test("[node/http] request default protocol", async () => {
|
|||
server.close();
|
||||
});
|
||||
assertEquals(res.statusCode, 200);
|
||||
promise2.resolve();
|
||||
},
|
||||
);
|
||||
req.end();
|
||||
|
@ -206,6 +208,7 @@ Deno.test("[node/http] request default protocol", async () => {
|
|||
promise.resolve();
|
||||
});
|
||||
await promise;
|
||||
await promise2;
|
||||
});
|
||||
|
||||
Deno.test("[node/http] request with headers", async () => {
|
||||
|
@ -292,32 +295,6 @@ Deno.test("[node/http] http.IncomingMessage can be created without url", () => {
|
|||
});
|
||||
*/
|
||||
|
||||
Deno.test("[node/http] set http.IncomingMessage.statusMessage", () => {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const message = new (http as any).IncomingMessageForClient(
|
||||
new Response(null, { status: 404, statusText: "Not Found" }),
|
||||
{
|
||||
encrypted: true,
|
||||
readable: false,
|
||||
remoteAddress: "foo",
|
||||
address() {
|
||||
return { port: 443, family: "IPv4" };
|
||||
},
|
||||
// deno-lint-ignore no-explicit-any
|
||||
end(_cb: any) {
|
||||
return this;
|
||||
},
|
||||
// deno-lint-ignore no-explicit-any
|
||||
destroy(_e: any) {
|
||||
return;
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(message.statusMessage, "Not Found");
|
||||
message.statusMessage = "boom";
|
||||
assertEquals(message.statusMessage, "boom");
|
||||
});
|
||||
|
||||
Deno.test("[node/http] send request with non-chunked body", async () => {
|
||||
let requestHeaders: Headers;
|
||||
let requestBody = "";
|
||||
|
|
|
@ -66,7 +66,7 @@ pub use reqwest;
|
|||
|
||||
pub use fs_fetch_handler::FsFetchHandler;
|
||||
|
||||
use crate::byte_stream::MpscByteStream;
|
||||
pub use crate::byte_stream::MpscByteStream;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Options {
|
||||
|
@ -186,9 +186,9 @@ pub fn get_declaration() -> PathBuf {
|
|||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FetchReturn {
|
||||
request_rid: ResourceId,
|
||||
request_body_rid: Option<ResourceId>,
|
||||
cancel_handle_rid: Option<ResourceId>,
|
||||
pub request_rid: ResourceId,
|
||||
pub request_body_rid: Option<ResourceId>,
|
||||
pub cancel_handle_rid: Option<ResourceId>,
|
||||
}
|
||||
|
||||
pub fn get_or_create_client_from_state(
|
||||
|
@ -302,7 +302,7 @@ where
|
|||
}
|
||||
Some(data) => {
|
||||
// If a body is passed, we use it, and don't return a body for streaming.
|
||||
request = request.body(Vec::from(&*data));
|
||||
request = request.body(data.to_vec());
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -400,12 +400,12 @@ where
|
|||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FetchResponse {
|
||||
status: u16,
|
||||
status_text: String,
|
||||
headers: Vec<(ByteString, ByteString)>,
|
||||
url: String,
|
||||
response_rid: ResourceId,
|
||||
content_length: Option<u64>,
|
||||
pub status: u16,
|
||||
pub status_text: String,
|
||||
pub headers: Vec<(ByteString, ByteString)>,
|
||||
pub url: String,
|
||||
pub response_rid: ResourceId,
|
||||
pub content_length: Option<u64>,
|
||||
}
|
||||
|
||||
#[op]
|
||||
|
@ -462,8 +462,8 @@ pub async fn op_fetch_send(
|
|||
|
||||
type CancelableResponseResult = Result<Result<Response, AnyError>, Canceled>;
|
||||
|
||||
struct FetchRequestResource(
|
||||
Pin<Box<dyn Future<Output = CancelableResponseResult>>>,
|
||||
pub struct FetchRequestResource(
|
||||
pub Pin<Box<dyn Future<Output = CancelableResponseResult>>>,
|
||||
);
|
||||
|
||||
impl Resource for FetchRequestResource {
|
||||
|
@ -472,7 +472,7 @@ impl Resource for FetchRequestResource {
|
|||
}
|
||||
}
|
||||
|
||||
struct FetchCancelHandle(Rc<CancelHandle>);
|
||||
pub struct FetchCancelHandle(pub Rc<CancelHandle>);
|
||||
|
||||
impl Resource for FetchCancelHandle {
|
||||
fn name(&self) -> Cow<str> {
|
||||
|
@ -485,8 +485,8 @@ impl Resource for FetchCancelHandle {
|
|||
}
|
||||
|
||||
pub struct FetchRequestBodyResource {
|
||||
body: AsyncRefCell<mpsc::Sender<Option<bytes::Bytes>>>,
|
||||
cancel: CancelHandle,
|
||||
pub body: AsyncRefCell<mpsc::Sender<Option<bytes::Bytes>>>,
|
||||
pub cancel: CancelHandle,
|
||||
}
|
||||
|
||||
impl Resource for FetchRequestBodyResource {
|
||||
|
@ -537,10 +537,10 @@ impl Resource for FetchRequestBodyResource {
|
|||
type BytesStream =
|
||||
Pin<Box<dyn Stream<Item = Result<bytes::Bytes, std::io::Error>> + Unpin>>;
|
||||
|
||||
struct FetchResponseBodyResource {
|
||||
reader: AsyncRefCell<Peekable<BytesStream>>,
|
||||
cancel: CancelHandle,
|
||||
size: Option<u64>,
|
||||
pub struct FetchResponseBodyResource {
|
||||
pub reader: AsyncRefCell<Peekable<BytesStream>>,
|
||||
pub cancel: CancelHandle,
|
||||
pub size: Option<u64>,
|
||||
}
|
||||
|
||||
impl Resource for FetchResponseBodyResource {
|
||||
|
@ -590,8 +590,8 @@ impl Resource for FetchResponseBodyResource {
|
|||
}
|
||||
}
|
||||
|
||||
struct HttpClientResource {
|
||||
client: Client,
|
||||
pub struct HttpClientResource {
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
impl Resource for HttpClientResource {
|
||||
|
|
|
@ -18,6 +18,7 @@ aes.workspace = true
|
|||
cbc.workspace = true
|
||||
data-encoding = "2.3.3"
|
||||
deno_core.workspace = true
|
||||
deno_fetch.workspace = true
|
||||
deno_fs.workspace = true
|
||||
deno_media_type.workspace = true
|
||||
deno_npm.workspace = true
|
||||
|
@ -46,6 +47,7 @@ path-clean = "=0.1.0"
|
|||
pbkdf2 = "0.12.1"
|
||||
rand.workspace = true
|
||||
regex.workspace = true
|
||||
reqwest.workspace = true
|
||||
ring.workspace = true
|
||||
ripemd = "0.1.3"
|
||||
rsa.workspace = true
|
||||
|
|
|
@ -206,6 +206,7 @@ deno_core::extension!(deno_node,
|
|||
ops::zlib::op_zlib_write_async,
|
||||
ops::zlib::op_zlib_init,
|
||||
ops::zlib::op_zlib_reset,
|
||||
ops::http::op_node_http_request,
|
||||
op_node_build_os,
|
||||
ops::require::op_require_init_paths,
|
||||
ops::require::op_require_node_module_paths<P>,
|
||||
|
|
101
ext/node/ops/http.rs
Normal file
101
ext/node/ops/http.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::error::type_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::op;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::AsyncRefCell;
|
||||
use deno_core::ByteString;
|
||||
use deno_core::CancelFuture;
|
||||
use deno_core::CancelHandle;
|
||||
use deno_core::OpState;
|
||||
use deno_fetch::get_or_create_client_from_state;
|
||||
use deno_fetch::FetchCancelHandle;
|
||||
use deno_fetch::FetchRequestBodyResource;
|
||||
use deno_fetch::FetchRequestResource;
|
||||
use deno_fetch::FetchReturn;
|
||||
use deno_fetch::HttpClientResource;
|
||||
use deno_fetch::MpscByteStream;
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::header::HeaderName;
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::header::CONTENT_LENGTH;
|
||||
use reqwest::Body;
|
||||
use reqwest::Method;
|
||||
|
||||
#[op]
|
||||
pub fn op_node_http_request(
|
||||
state: &mut OpState,
|
||||
method: ByteString,
|
||||
url: String,
|
||||
headers: Vec<(ByteString, ByteString)>,
|
||||
client_rid: Option<u32>,
|
||||
has_body: bool,
|
||||
) -> Result<FetchReturn, AnyError> {
|
||||
let client = if let Some(rid) = client_rid {
|
||||
let r = state.resource_table.get::<HttpClientResource>(rid)?;
|
||||
r.client.clone()
|
||||
} else {
|
||||
get_or_create_client_from_state(state)?
|
||||
};
|
||||
|
||||
let method = Method::from_bytes(&method)?;
|
||||
let url = Url::parse(&url)?;
|
||||
|
||||
let mut header_map = HeaderMap::new();
|
||||
for (key, value) in headers {
|
||||
let name = HeaderName::from_bytes(&key)
|
||||
.map_err(|err| type_error(err.to_string()))?;
|
||||
let v = HeaderValue::from_bytes(&value)
|
||||
.map_err(|err| type_error(err.to_string()))?;
|
||||
|
||||
header_map.append(name, v);
|
||||
}
|
||||
|
||||
let mut request = client.request(method.clone(), url).headers(header_map);
|
||||
|
||||
let request_body_rid = if has_body {
|
||||
// If no body is passed, we return a writer for streaming the body.
|
||||
let (stream, tx) = MpscByteStream::new();
|
||||
|
||||
request = request.body(Body::wrap_stream(stream));
|
||||
|
||||
let request_body_rid = state.resource_table.add(FetchRequestBodyResource {
|
||||
body: AsyncRefCell::new(tx),
|
||||
cancel: CancelHandle::default(),
|
||||
});
|
||||
|
||||
Some(request_body_rid)
|
||||
} else {
|
||||
// POST and PUT requests should always have a 0 length content-length,
|
||||
// if there is no body. https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
|
||||
if matches!(method, Method::POST | Method::PUT) {
|
||||
request = request.header(CONTENT_LENGTH, HeaderValue::from(0));
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let cancel_handle = CancelHandle::new_rc();
|
||||
let cancel_handle_ = cancel_handle.clone();
|
||||
|
||||
let fut = async move {
|
||||
request
|
||||
.send()
|
||||
.or_cancel(cancel_handle_)
|
||||
.await
|
||||
.map(|res| res.map_err(|err| type_error(err.to_string())))
|
||||
};
|
||||
|
||||
let request_rid = state
|
||||
.resource_table
|
||||
.add(FetchRequestResource(Box::pin(fut)));
|
||||
|
||||
let cancel_handle_rid =
|
||||
state.resource_table.add(FetchCancelHandle(cancel_handle));
|
||||
|
||||
Ok(FetchReturn {
|
||||
request_rid,
|
||||
request_body_rid,
|
||||
cancel_handle_rid: Some(cancel_handle_rid),
|
||||
})
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
pub mod crypto;
|
||||
pub mod http;
|
||||
pub mod idna;
|
||||
pub mod require;
|
||||
pub mod v8;
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -4,15 +4,11 @@
|
|||
import { notImplemented } from "ext:deno_node/_utils.ts";
|
||||
import { urlToHttpOptions } from "ext:deno_node/internal/url.ts";
|
||||
import {
|
||||
Agent as HttpAgent,
|
||||
ClientRequest,
|
||||
IncomingMessageForClient as IncomingMessage,
|
||||
type RequestOptions,
|
||||
} from "ext:deno_node/http.ts";
|
||||
import type { Socket } from "ext:deno_node/net.ts";
|
||||
|
||||
export class Agent extends HttpAgent {
|
||||
}
|
||||
import { Agent as HttpAgent } from "ext:deno_node/_http_agent.mjs";
|
||||
|
||||
export class Server {
|
||||
constructor() {
|
||||
|
@ -53,41 +49,61 @@ export function get(...args: any[]) {
|
|||
return req;
|
||||
}
|
||||
|
||||
export const globalAgent = undefined;
|
||||
export class Agent extends HttpAgent {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.defaultPort = 443;
|
||||
this.protocol = "https:";
|
||||
this.maxCachedSessions = this.options.maxCachedSessions;
|
||||
if (this.maxCachedSessions === undefined) {
|
||||
this.maxCachedSessions = 100;
|
||||
}
|
||||
|
||||
this._sessionCache = {
|
||||
map: {},
|
||||
list: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const globalAgent = new Agent({
|
||||
keepAlive: true,
|
||||
scheduling: "lifo",
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
/** HttpsClientRequest class loosely follows http.ClientRequest class API. */
|
||||
class HttpsClientRequest extends ClientRequest {
|
||||
override defaultProtocol = "https:";
|
||||
override async _createCustomClient(): Promise<
|
||||
Deno.HttpClient | undefined
|
||||
> {
|
||||
override _getClient(): Deno.HttpClient | undefined {
|
||||
if (caCerts === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (caCerts !== undefined) {
|
||||
return Deno.createHttpClient({ caCerts });
|
||||
}
|
||||
const status = await Deno.permissions.query({
|
||||
name: "env",
|
||||
variable: "NODE_EXTRA_CA_CERTS",
|
||||
});
|
||||
if (status.state !== "granted") {
|
||||
caCerts = null;
|
||||
return undefined;
|
||||
}
|
||||
// const status = await Deno.permissions.query({
|
||||
// name: "env",
|
||||
// variable: "NODE_EXTRA_CA_CERTS",
|
||||
// });
|
||||
// if (status.state !== "granted") {
|
||||
// caCerts = null;
|
||||
// return undefined;
|
||||
// }
|
||||
const certFilename = Deno.env.get("NODE_EXTRA_CA_CERTS");
|
||||
if (!certFilename) {
|
||||
caCerts = null;
|
||||
return undefined;
|
||||
}
|
||||
const caCert = await Deno.readTextFile(certFilename);
|
||||
const caCert = Deno.readTextFileSync(certFilename);
|
||||
caCerts = [caCert];
|
||||
return Deno.createHttpClient({ caCerts });
|
||||
}
|
||||
|
||||
override _createSocket(): Socket {
|
||||
/*override _createSocket(): Socket {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
return { authorized: true } as any;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/** Makes a request to an https server. */
|
||||
|
@ -107,15 +123,21 @@ export function request(
|
|||
// deno-lint-ignore no-explicit-any
|
||||
export function request(...args: any[]) {
|
||||
let options = {};
|
||||
|
||||
if (typeof args[0] === "string") {
|
||||
options = urlToHttpOptions(new URL(args.shift()));
|
||||
const urlStr = args.shift();
|
||||
options = urlToHttpOptions(new URL(urlStr));
|
||||
} else if (args[0] instanceof URL) {
|
||||
options = urlToHttpOptions(args.shift());
|
||||
}
|
||||
|
||||
if (args[0] && typeof args[0] !== "function") {
|
||||
Object.assign(options, args.shift());
|
||||
}
|
||||
|
||||
options._defaultAgent = globalAgent;
|
||||
args.unshift(options);
|
||||
|
||||
return new HttpsClientRequest(args[0], args[1]);
|
||||
}
|
||||
export default {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
NOTE: This file should not be manually edited. Please edit 'cli/tests/node_compat/config.json' and run 'tools/node_compat/setup.ts' instead.
|
||||
|
||||
Total: 2933
|
||||
Total: 2935
|
||||
|
||||
- [abort/test-abort-backtrace.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-abort-backtrace.js)
|
||||
- [abort/test-abort-fatal-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-abort-fatal-error.js)
|
||||
|
@ -1083,6 +1083,7 @@ Total: 2933
|
|||
- [parallel/test-http-no-content-length.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-no-content-length.js)
|
||||
- [parallel/test-http-no-read-no-dump.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-no-read-no-dump.js)
|
||||
- [parallel/test-http-nodelay.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-nodelay.js)
|
||||
- [parallel/test-http-outgoing-buffer.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-buffer.js)
|
||||
- [parallel/test-http-outgoing-destroy.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-destroy.js)
|
||||
- [parallel/test-http-outgoing-destroyed.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-destroyed.js)
|
||||
- [parallel/test-http-outgoing-end-cork.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-end-cork.js)
|
||||
|
@ -1093,6 +1094,7 @@ Total: 2933
|
|||
- [parallel/test-http-outgoing-finished.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-finished.js)
|
||||
- [parallel/test-http-outgoing-first-chunk-singlebyte-encoding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-first-chunk-singlebyte-encoding.js)
|
||||
- [parallel/test-http-outgoing-message-capture-rejection.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-message-capture-rejection.js)
|
||||
- [parallel/test-http-outgoing-message-inheritance.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-message-inheritance.js)
|
||||
- [parallel/test-http-outgoing-message-write-callback.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-message-write-callback.js)
|
||||
- [parallel/test-http-outgoing-properties.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-properties.js)
|
||||
- [parallel/test-http-outgoing-proto.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-http-outgoing-proto.js)
|
||||
|
|
Loading…
Reference in a new issue