mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
fix(ext/fetch): include TCP src/dst socket info in error messages (#24939)
This commit makes `fetch` error messages include source and destination TCP socket info i.e. port number and IP address for better debuggability. Closes #24922
This commit is contained in:
parent
18b9b43c36
commit
e36b1a3aa8
4 changed files with 84 additions and 19 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -3630,9 +3630,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956"
|
checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
|
|
@ -124,7 +124,7 @@ http_v02 = { package = "http", version = "0.2.9" }
|
||||||
httparse = "1.8.0"
|
httparse = "1.8.0"
|
||||||
hyper = { version = "1.4.1", features = ["full"] }
|
hyper = { version = "1.4.1", features = ["full"] }
|
||||||
hyper-rustls = { version = "0.27.2", default-features = false, features = ["http1", "http2", "tls12", "ring"] }
|
hyper-rustls = { version = "0.27.2", default-features = false, features = ["http1", "http2", "tls12", "ring"] }
|
||||||
hyper-util = { version = "=0.1.6", features = ["tokio", "client", "client-legacy", "server", "server-auto"] }
|
hyper-util = { version = "=0.1.7", features = ["tokio", "client", "client-legacy", "server", "server-auto"] }
|
||||||
hyper_v014 = { package = "hyper", version = "0.14.26", features = ["runtime", "http1"] }
|
hyper_v014 = { package = "hyper", version = "0.14.26", features = ["runtime", "http1"] }
|
||||||
indexmap = { version = "2", features = ["serde"] }
|
indexmap = { version = "2", features = ["serde"] }
|
||||||
ipnet = "2.3"
|
ipnet = "2.3"
|
||||||
|
|
|
@ -62,11 +62,13 @@ use http::header::HOST;
|
||||||
use http::header::PROXY_AUTHORIZATION;
|
use http::header::PROXY_AUTHORIZATION;
|
||||||
use http::header::RANGE;
|
use http::header::RANGE;
|
||||||
use http::header::USER_AGENT;
|
use http::header::USER_AGENT;
|
||||||
|
use http::Extensions;
|
||||||
use http::Method;
|
use http::Method;
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
use hyper::body::Frame;
|
use hyper::body::Frame;
|
||||||
use hyper_util::client::legacy::connect::HttpConnector;
|
use hyper_util::client::legacy::connect::HttpConnector;
|
||||||
|
use hyper_util::client::legacy::connect::HttpInfo;
|
||||||
use hyper_util::rt::TokioExecutor;
|
use hyper_util::rt::TokioExecutor;
|
||||||
use hyper_util::rt::TokioIo;
|
use hyper_util::rt::TokioIo;
|
||||||
use hyper_util::rt::TokioTimer;
|
use hyper_util::rt::TokioTimer;
|
||||||
|
@ -1104,18 +1106,40 @@ impl ClientSendError {
|
||||||
pub fn is_connect_error(&self) -> bool {
|
pub fn is_connect_error(&self) -> bool {
|
||||||
self.source.is_connect()
|
self.source.is_connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn http_info(&self) -> Option<HttpInfo> {
|
||||||
|
let mut exts = Extensions::new();
|
||||||
|
self.source.connect_info()?.get_extras(&mut exts);
|
||||||
|
exts.remove::<HttpInfo>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ClientSendError {
|
impl std::fmt::Display for ClientSendError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
// NOTE: we can use `std::error::Report` instead once it's stabilized.
|
||||||
|
let detail = error_reporter::Report::new(&self.source);
|
||||||
|
|
||||||
|
match self.http_info() {
|
||||||
|
Some(http_info) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"error sending request for url ({uri}): {source}",
|
"error sending request from {src} for {uri} ({dst}): {detail}",
|
||||||
|
src = http_info.local_addr(),
|
||||||
uri = self.uri,
|
uri = self.uri,
|
||||||
// NOTE: we can use `std::error::Report` instead once it's stabilized.
|
dst = http_info.remote_addr(),
|
||||||
source = error_reporter::Report::new(&self.source),
|
detail = detail,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"error sending request for url ({uri}): {detail}",
|
||||||
|
uri = self.uri,
|
||||||
|
detail = detail,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ClientSendError {
|
impl std::error::Error for ClientSendError {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
assert,
|
assert,
|
||||||
assertEquals,
|
assertEquals,
|
||||||
assertRejects,
|
assertRejects,
|
||||||
|
assertStringIncludes,
|
||||||
assertThrows,
|
assertThrows,
|
||||||
delay,
|
delay,
|
||||||
fail,
|
fail,
|
||||||
|
@ -1977,14 +1978,24 @@ Deno.test(
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = `http://localhost:${listenPort}/`;
|
const url = `http://localhost:${listenPort}/`;
|
||||||
const err = await assertRejects(
|
const err = await assertRejects(() =>
|
||||||
() =>
|
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
body: stream,
|
body: stream,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
}),
|
})
|
||||||
TypeError,
|
);
|
||||||
`error sending request for url (${url}): client error (SendRequest): error from user's Body stream`,
|
|
||||||
|
assert(err instanceof TypeError, `err was ${err}`);
|
||||||
|
|
||||||
|
assertStringIncludes(
|
||||||
|
err.message,
|
||||||
|
"error sending request from 127.0.0.1:",
|
||||||
|
`err.message was ${err.message}`,
|
||||||
|
);
|
||||||
|
assertStringIncludes(
|
||||||
|
err.message,
|
||||||
|
` for http://localhost:${listenPort}/ (127.0.0.1:${listenPort}): client error (SendRequest): error from user's Body stream`,
|
||||||
|
`err.message was ${err.message}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(err.cause, `err.cause was null ${err}`);
|
assert(err.cause, `err.cause was null ${err}`);
|
||||||
|
@ -2066,7 +2077,7 @@ Deno.test("URL authority is used as 'Authorization' header", async () => {
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { net: true } },
|
{ permissions: { net: true } },
|
||||||
async function errorMessageIncludesUrlAndDetails() {
|
async function errorMessageIncludesUrlAndDetailsWithNoTcpInfo() {
|
||||||
await assertRejects(
|
await assertRejects(
|
||||||
() => fetch("http://example.invalid"),
|
() => fetch("http://example.invalid"),
|
||||||
TypeError,
|
TypeError,
|
||||||
|
@ -2074,3 +2085,33 @@ Deno.test(
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { net: true } },
|
||||||
|
async function errorMessageIncludesUrlAndDetailsWithTcpInfo() {
|
||||||
|
const listener = Deno.listen({ port: listenPort });
|
||||||
|
const server = (async () => {
|
||||||
|
const conn = await listener.accept();
|
||||||
|
listener.close();
|
||||||
|
// Immediately close the connection to simulate a connection error
|
||||||
|
conn.close();
|
||||||
|
})();
|
||||||
|
|
||||||
|
const url = `http://localhost:${listenPort}`;
|
||||||
|
const err = await assertRejects(() => fetch(url));
|
||||||
|
|
||||||
|
assert(err instanceof TypeError, `${err}`);
|
||||||
|
assertStringIncludes(
|
||||||
|
err.message,
|
||||||
|
"error sending request from 127.0.0.1:",
|
||||||
|
`${err.message}`,
|
||||||
|
);
|
||||||
|
assertStringIncludes(
|
||||||
|
err.message,
|
||||||
|
` for http://localhost:${listenPort}/ (127.0.0.1:${listenPort}): client error (SendRequest): `,
|
||||||
|
`${err.message}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await server;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
Loading…
Reference in a new issue