Add connection_verbose setting to log IO events (#774)
This commit is contained in:
@@ -64,6 +64,7 @@ struct Config {
|
|||||||
#[cfg(feature = "__tls")]
|
#[cfg(feature = "__tls")]
|
||||||
certs_verification: bool,
|
certs_verification: bool,
|
||||||
connect_timeout: Option<Duration>,
|
connect_timeout: Option<Duration>,
|
||||||
|
connection_verbose: bool,
|
||||||
max_idle_per_host: usize,
|
max_idle_per_host: usize,
|
||||||
#[cfg(feature = "__tls")]
|
#[cfg(feature = "__tls")]
|
||||||
identity: Option<Identity>,
|
identity: Option<Identity>,
|
||||||
@@ -111,6 +112,7 @@ impl ClientBuilder {
|
|||||||
#[cfg(feature = "__tls")]
|
#[cfg(feature = "__tls")]
|
||||||
certs_verification: true,
|
certs_verification: true,
|
||||||
connect_timeout: None,
|
connect_timeout: None,
|
||||||
|
connection_verbose: false,
|
||||||
max_idle_per_host: std::usize::MAX,
|
max_idle_per_host: std::usize::MAX,
|
||||||
proxies: Vec::new(),
|
proxies: Vec::new(),
|
||||||
auto_sys_proxy: true,
|
auto_sys_proxy: true,
|
||||||
@@ -234,6 +236,7 @@ impl ClientBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
connector.set_timeout(config.connect_timeout);
|
connector.set_timeout(config.connect_timeout);
|
||||||
|
connector.set_verbose(config.connection_verbose);
|
||||||
|
|
||||||
let mut builder = hyper::Client::builder();
|
let mut builder = hyper::Client::builder();
|
||||||
if config.http2_only {
|
if config.http2_only {
|
||||||
@@ -489,6 +492,17 @@ impl ClientBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set whether connections should emit verbose logs.
|
||||||
|
///
|
||||||
|
/// Enabling this option will emit [log][] messages at the `TRACE` level
|
||||||
|
/// for read and write operations on connections.
|
||||||
|
///
|
||||||
|
/// [log]: https://crates.io/crates/log
|
||||||
|
pub fn connection_verbose(mut self, verbose: bool) -> ClientBuilder {
|
||||||
|
self.config.connection_verbose = verbose;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP options
|
// HTTP options
|
||||||
|
|
||||||
/// Sets the maximum idle connection per host allowed in the pool.
|
/// Sets the maximum idle connection per host allowed in the pool.
|
||||||
|
|||||||
@@ -487,6 +487,8 @@ impl PercentEncoding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gen_boundary() -> String {
|
fn gen_boundary() -> String {
|
||||||
|
use crate::util::fast_random as random;
|
||||||
|
|
||||||
let a = random();
|
let a = random();
|
||||||
let b = random();
|
let b = random();
|
||||||
let c = random();
|
let c = random();
|
||||||
@@ -495,42 +497,6 @@ fn gen_boundary() -> String {
|
|||||||
format!("{:016x}-{:016x}-{:016x}-{:016x}", a, b, c, d)
|
format!("{:016x}-{:016x}-{:016x}-{:016x}", a, b, c, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// xor-shift
|
|
||||||
fn random() -> u64 {
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::collections::hash_map::RandomState;
|
|
||||||
use std::hash::{BuildHasher, Hasher};
|
|
||||||
use std::num::Wrapping;
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static RNG: Cell<Wrapping<u64>> = Cell::new(Wrapping(seed()));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seed() -> u64 {
|
|
||||||
let seed = RandomState::new();
|
|
||||||
|
|
||||||
let mut out = 0;
|
|
||||||
let mut cnt = 0;
|
|
||||||
while out == 0 {
|
|
||||||
cnt += 1;
|
|
||||||
let mut hasher = seed.build_hasher();
|
|
||||||
hasher.write_usize(cnt);
|
|
||||||
out = hasher.finish();
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
RNG.with(|rng| {
|
|
||||||
let mut n = rng.get();
|
|
||||||
debug_assert_ne!(n.0, 0);
|
|
||||||
n ^= n >> 12;
|
|
||||||
n ^= n << 25;
|
|
||||||
n ^= n >> 27;
|
|
||||||
rng.set(n);
|
|
||||||
n.0.wrapping_mul(0x2545_f491_4f6c_dd1d)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -273,6 +273,16 @@ impl ClientBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set whether connections should emit verbose logs.
|
||||||
|
///
|
||||||
|
/// Enabling this option will emit [log][] messages at the `TRACE` level
|
||||||
|
/// for read and write operations on connections.
|
||||||
|
///
|
||||||
|
/// [log]: https://crates.io/crates/log
|
||||||
|
pub fn connection_verbose(self, verbose: bool) -> ClientBuilder {
|
||||||
|
self.with_inner(move |inner| inner.connection_verbose(verbose))
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP options
|
// HTTP options
|
||||||
|
|
||||||
/// Sets the maximum idle connection per host allowed in the pool.
|
/// Sets the maximum idle connection per host allowed in the pool.
|
||||||
|
|||||||
165
src/connect.rs
165
src/connect.rs
@@ -37,6 +37,7 @@ type HttpConnector = hyper::client::HttpConnector;
|
|||||||
pub(crate) struct Connector {
|
pub(crate) struct Connector {
|
||||||
inner: Inner,
|
inner: Inner,
|
||||||
proxies: Arc<Vec<Proxy>>,
|
proxies: Arc<Vec<Proxy>>,
|
||||||
|
verbose: verbose::Wrapper,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
#[cfg(feature = "__tls")]
|
#[cfg(feature = "__tls")]
|
||||||
nodelay: bool,
|
nodelay: bool,
|
||||||
@@ -73,6 +74,7 @@ impl Connector {
|
|||||||
http.set_nodelay(nodelay);
|
http.set_nodelay(nodelay);
|
||||||
Ok(Connector {
|
Ok(Connector {
|
||||||
inner: Inner::Http(http),
|
inner: Inner::Http(http),
|
||||||
|
verbose: verbose::OFF,
|
||||||
proxies,
|
proxies,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
})
|
})
|
||||||
@@ -98,6 +100,7 @@ impl Connector {
|
|||||||
Ok(Connector {
|
Ok(Connector {
|
||||||
inner: Inner::DefaultTls(http, tls),
|
inner: Inner::DefaultTls(http, tls),
|
||||||
proxies,
|
proxies,
|
||||||
|
verbose: verbose::OFF,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
nodelay,
|
nodelay,
|
||||||
user_agent,
|
user_agent,
|
||||||
@@ -135,6 +138,7 @@ impl Connector {
|
|||||||
tls_proxy,
|
tls_proxy,
|
||||||
},
|
},
|
||||||
proxies,
|
proxies,
|
||||||
|
verbose: verbose::OFF,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
nodelay,
|
nodelay,
|
||||||
user_agent,
|
user_agent,
|
||||||
@@ -145,6 +149,10 @@ impl Connector {
|
|||||||
self.timeout = timeout;
|
self.timeout = timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_verbose(&mut self, enabled: bool) {
|
||||||
|
self.verbose.0 = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "socks")]
|
#[cfg(feature = "socks")]
|
||||||
async fn connect_socks(
|
async fn connect_socks(
|
||||||
&self,
|
&self,
|
||||||
@@ -178,7 +186,7 @@ impl Connector {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
return Ok(Conn {
|
return Ok(Conn {
|
||||||
inner: Box::new(NativeTlsConn { inner: io }),
|
inner: self.verbose.wrap(NativeTlsConn { inner: io }),
|
||||||
is_proxy: false,
|
is_proxy: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -203,7 +211,7 @@ impl Connector {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
return Ok(Conn {
|
return Ok(Conn {
|
||||||
inner: Box::new(RustlsTlsConn { inner: io }),
|
inner: self.verbose.wrap(RustlsTlsConn { inner: io }),
|
||||||
is_proxy: false,
|
is_proxy: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -212,7 +220,10 @@ impl Connector {
|
|||||||
Inner::Http(_) => ()
|
Inner::Http(_) => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
socks::connect(proxy, dst, dns).await
|
socks::connect(proxy, dst, dns).await.map(|tcp| Conn {
|
||||||
|
inner: self.verbose.wrap(tcp),
|
||||||
|
is_proxy: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_with_maybe_proxy(
|
async fn connect_with_maybe_proxy(
|
||||||
@@ -225,7 +236,7 @@ impl Connector {
|
|||||||
Inner::Http(mut http) => {
|
Inner::Http(mut http) => {
|
||||||
let io = http.call(dst).await?;
|
let io = http.call(dst).await?;
|
||||||
Ok(Conn {
|
Ok(Conn {
|
||||||
inner: Box::new(io),
|
inner: self.verbose.wrap(io),
|
||||||
is_proxy,
|
is_proxy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -246,7 +257,7 @@ impl Connector {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
Ok(Conn {
|
Ok(Conn {
|
||||||
inner: Box::new(io),
|
inner: self.verbose.wrap(io),
|
||||||
is_proxy,
|
is_proxy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -270,7 +281,7 @@ impl Connector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Conn {
|
Ok(Conn {
|
||||||
inner: Box::new(io),
|
inner: self.verbose.wrap(io),
|
||||||
is_proxy,
|
is_proxy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -322,7 +333,7 @@ impl Connector {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
return Ok(Conn {
|
return Ok(Conn {
|
||||||
inner: Box::new(NativeTlsConn { inner: io }),
|
inner: self.verbose.wrap(NativeTlsConn { inner: io }),
|
||||||
is_proxy: false,
|
is_proxy: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -359,7 +370,7 @@ impl Connector {
|
|||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
|
|
||||||
return Ok(Conn {
|
return Ok(Conn {
|
||||||
inner: Box::new(RustlsTlsConn { inner: io }),
|
inner: self.verbose.wrap(RustlsTlsConn { inner: io }),
|
||||||
is_proxy: false,
|
is_proxy: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -438,8 +449,11 @@ impl Service<Uri> for Connector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait AsyncConn: AsyncRead + AsyncWrite + Connection {}
|
pub(crate) trait AsyncConn: AsyncRead + AsyncWrite + Connection + Send + Sync + Unpin + 'static {}
|
||||||
impl<T: AsyncRead + AsyncWrite + Connection> AsyncConn for T {}
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite + Connection + Send + Sync + Unpin + 'static> AsyncConn for T {}
|
||||||
|
|
||||||
|
type BoxConn = Box<dyn AsyncConn>;
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
/// Note: the `is_proxy` member means *is plain text HTTP proxy*.
|
/// Note: the `is_proxy` member means *is plain text HTTP proxy*.
|
||||||
@@ -448,7 +462,7 @@ pin_project! {
|
|||||||
/// * absolute-form (`GET http://foo.bar/and/a/path HTTP/1.1`), otherwise.
|
/// * absolute-form (`GET http://foo.bar/and/a/path HTTP/1.1`), otherwise.
|
||||||
pub(crate) struct Conn {
|
pub(crate) struct Conn {
|
||||||
#[pin]
|
#[pin]
|
||||||
inner: Box<dyn AsyncConn + Send + Sync + Unpin + 'static>,
|
inner: BoxConn,
|
||||||
is_proxy: bool,
|
is_proxy: bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -795,11 +809,13 @@ mod rustls_tls_conn {
|
|||||||
|
|
||||||
#[cfg(feature = "socks")]
|
#[cfg(feature = "socks")]
|
||||||
mod socks {
|
mod socks {
|
||||||
use http::Uri;
|
|
||||||
use tokio_socks::tcp::Socks5Stream;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
|
|
||||||
|
use http::Uri;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio_socks::tcp::Socks5Stream;
|
||||||
|
|
||||||
use super::{BoxError, Scheme};
|
use super::{BoxError, Scheme};
|
||||||
use crate::proxy::ProxyScheme;
|
use crate::proxy::ProxyScheme;
|
||||||
|
|
||||||
@@ -812,7 +828,7 @@ mod socks {
|
|||||||
proxy: ProxyScheme,
|
proxy: ProxyScheme,
|
||||||
dst: Uri,
|
dst: Uri,
|
||||||
dns: DnsResolve,
|
dns: DnsResolve,
|
||||||
) -> Result<super::Conn, BoxError> {
|
) -> Result<TcpStream, BoxError> {
|
||||||
let https = dst.scheme() == Some(&Scheme::HTTPS);
|
let https = dst.scheme() == Some(&Scheme::HTTPS);
|
||||||
let original_host = dst
|
let original_host = dst
|
||||||
.host()
|
.host()
|
||||||
@@ -852,10 +868,123 @@ mod socks {
|
|||||||
.map_err(|e| format!("socks connect error: {}", e))?
|
.map_err(|e| format!("socks connect error: {}", e))?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(super::Conn {
|
Ok(stream.into_inner())
|
||||||
inner: Box::new( stream.into_inner() ),
|
}
|
||||||
is_proxy: false,
|
}
|
||||||
})
|
|
||||||
|
mod verbose {
|
||||||
|
use std::fmt;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use hyper::client::connect::{Connected, Connection};
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
pub(super) const OFF: Wrapper = Wrapper(false);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(super) struct Wrapper(pub(super) bool);
|
||||||
|
|
||||||
|
impl Wrapper {
|
||||||
|
pub(super) fn wrap<T: super::AsyncConn>(&self, conn: T) -> super::BoxConn {
|
||||||
|
if self.0 && log::log_enabled!(log::Level::Trace) {
|
||||||
|
Box::new(Verbose {
|
||||||
|
// truncate is fine
|
||||||
|
id: crate::util::fast_random() as u32,
|
||||||
|
inner: conn,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Box::new(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Verbose<T> {
|
||||||
|
id: u32,
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Connection + AsyncRead + AsyncWrite + Unpin> Connection for Verbose<T> {
|
||||||
|
fn connected(&self) -> Connected {
|
||||||
|
self.inner.connected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite + Unpin> AsyncRead for Verbose<T> {
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context,
|
||||||
|
buf: &mut [u8]
|
||||||
|
) -> Poll<std::io::Result<usize>> {
|
||||||
|
match Pin::new(&mut self.inner).poll_read(cx, buf) {
|
||||||
|
Poll::Ready(Ok(n)) => {
|
||||||
|
log::trace!("{:08x} read: {:?}", self.id, Escape(&buf[..n]));
|
||||||
|
Poll::Ready(Ok(n))
|
||||||
|
},
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
Poll::Ready(Err(e))
|
||||||
|
},
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite + Unpin> AsyncWrite for Verbose<T> {
|
||||||
|
fn poll_write(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context,
|
||||||
|
buf: &[u8]
|
||||||
|
) -> Poll<Result<usize, std::io::Error>> {
|
||||||
|
match Pin::new(&mut self.inner).poll_write(cx, buf) {
|
||||||
|
Poll::Ready(Ok(n)) => {
|
||||||
|
log::trace!("{:08x} write: {:?}", self.id, Escape(&buf[..n]));
|
||||||
|
Poll::Ready(Ok(n))
|
||||||
|
},
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
Poll::Ready(Err(e))
|
||||||
|
},
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), std::io::Error>> {
|
||||||
|
Pin::new(&mut self.inner).poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context
|
||||||
|
) -> Poll<Result<(), std::io::Error>> {
|
||||||
|
Pin::new(&mut self.inner).poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Escape<'a>(&'a [u8]);
|
||||||
|
|
||||||
|
impl fmt::Debug for Escape<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "b\"")?;
|
||||||
|
for &c in self.0 {
|
||||||
|
// https://doc.rust-lang.org/reference.html#byte-escapes
|
||||||
|
if c == b'\n' {
|
||||||
|
write!(f, "\\n")?;
|
||||||
|
} else if c == b'\r' {
|
||||||
|
write!(f, "\\r")?;
|
||||||
|
} else if c == b'\t' {
|
||||||
|
write!(f, "\\t")?;
|
||||||
|
} else if c == b'\\' || c == b'"' {
|
||||||
|
write!(f, "\\{}", c as char)?;
|
||||||
|
} else if c == b'\0' {
|
||||||
|
write!(f, "\\0")?;
|
||||||
|
// ASCII printable
|
||||||
|
} else if c >= 0x20 && c < 0x7f {
|
||||||
|
write!(f, "{}", c as char)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "\\x{:02x}", c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "\"")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ if_hyper! {
|
|||||||
pub mod redirect;
|
pub mod redirect;
|
||||||
#[cfg(feature = "__tls")]
|
#[cfg(feature = "__tls")]
|
||||||
mod tls;
|
mod tls;
|
||||||
|
mod util;
|
||||||
}
|
}
|
||||||
|
|
||||||
if_wasm! {
|
if_wasm! {
|
||||||
|
|||||||
35
src/util.rs
Normal file
35
src/util.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// xor-shift
|
||||||
|
pub(crate) fn fast_random() -> u64 {
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::collections::hash_map::RandomState;
|
||||||
|
use std::hash::{BuildHasher, Hasher};
|
||||||
|
use std::num::Wrapping;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static RNG: Cell<Wrapping<u64>> = Cell::new(Wrapping(seed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn seed() -> u64 {
|
||||||
|
let seed = RandomState::new();
|
||||||
|
|
||||||
|
let mut out = 0;
|
||||||
|
let mut cnt = 0;
|
||||||
|
while out == 0 {
|
||||||
|
cnt += 1;
|
||||||
|
let mut hasher = seed.build_hasher();
|
||||||
|
hasher.write_usize(cnt);
|
||||||
|
out = hasher.finish();
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
RNG.with(|rng| {
|
||||||
|
let mut n = rng.get();
|
||||||
|
debug_assert_ne!(n.0, 0);
|
||||||
|
n ^= n >> 12;
|
||||||
|
n ^= n << 25;
|
||||||
|
n ^= n >> 27;
|
||||||
|
rng.set(n);
|
||||||
|
n.0.wrapping_mul(0x2545_f491_4f6c_dd1d)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user