Add connection_verbose setting to log IO events (#774)
This commit is contained in:
		| @@ -64,6 +64,7 @@ struct Config { | ||||
|     #[cfg(feature = "__tls")] | ||||
|     certs_verification: bool, | ||||
|     connect_timeout: Option<Duration>, | ||||
|     connection_verbose: bool, | ||||
|     max_idle_per_host: usize, | ||||
|     #[cfg(feature = "__tls")] | ||||
|     identity: Option<Identity>, | ||||
| @@ -111,6 +112,7 @@ impl ClientBuilder { | ||||
|                 #[cfg(feature = "__tls")] | ||||
|                 certs_verification: true, | ||||
|                 connect_timeout: None, | ||||
|                 connection_verbose: false, | ||||
|                 max_idle_per_host: std::usize::MAX, | ||||
|                 proxies: Vec::new(), | ||||
|                 auto_sys_proxy: true, | ||||
| @@ -234,6 +236,7 @@ impl ClientBuilder { | ||||
|         }; | ||||
|  | ||||
|         connector.set_timeout(config.connect_timeout); | ||||
|         connector.set_verbose(config.connection_verbose); | ||||
|  | ||||
|         let mut builder = hyper::Client::builder(); | ||||
|         if config.http2_only { | ||||
| @@ -489,6 +492,17 @@ impl ClientBuilder { | ||||
|         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 | ||||
|  | ||||
|     /// Sets the maximum idle connection per host allowed in the pool. | ||||
|   | ||||
| @@ -487,6 +487,8 @@ impl PercentEncoding { | ||||
| } | ||||
|  | ||||
| fn gen_boundary() -> String { | ||||
|     use crate::util::fast_random as random; | ||||
|  | ||||
|     let a = random(); | ||||
|     let b = random(); | ||||
|     let c = random(); | ||||
| @@ -495,42 +497,6 @@ fn gen_boundary() -> String { | ||||
|     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)] | ||||
| mod tests { | ||||
|     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 | ||||
|  | ||||
|     /// 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 { | ||||
|     inner: Inner, | ||||
|     proxies: Arc<Vec<Proxy>>, | ||||
|     verbose: verbose::Wrapper, | ||||
|     timeout: Option<Duration>, | ||||
|     #[cfg(feature = "__tls")] | ||||
|     nodelay: bool, | ||||
| @@ -73,6 +74,7 @@ impl Connector { | ||||
|         http.set_nodelay(nodelay); | ||||
|         Ok(Connector { | ||||
|             inner: Inner::Http(http), | ||||
|             verbose: verbose::OFF, | ||||
|             proxies, | ||||
|             timeout: None, | ||||
|         }) | ||||
| @@ -98,6 +100,7 @@ impl Connector { | ||||
|         Ok(Connector { | ||||
|             inner: Inner::DefaultTls(http, tls), | ||||
|             proxies, | ||||
|             verbose: verbose::OFF, | ||||
|             timeout: None, | ||||
|             nodelay, | ||||
|             user_agent, | ||||
| @@ -135,6 +138,7 @@ impl Connector { | ||||
|                 tls_proxy, | ||||
|             }, | ||||
|             proxies, | ||||
|             verbose: verbose::OFF, | ||||
|             timeout: None, | ||||
|             nodelay, | ||||
|             user_agent, | ||||
| @@ -145,6 +149,10 @@ impl Connector { | ||||
|         self.timeout = timeout; | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn set_verbose(&mut self, enabled: bool) { | ||||
|         self.verbose.0 = enabled; | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "socks")] | ||||
|     async fn connect_socks( | ||||
|         &self, | ||||
| @@ -178,7 +186,7 @@ impl Connector { | ||||
|                         .await | ||||
|                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; | ||||
|                     return Ok(Conn { | ||||
|                         inner: Box::new(NativeTlsConn { inner: io }), | ||||
|                         inner: self.verbose.wrap(NativeTlsConn { inner: io }), | ||||
|                         is_proxy: false, | ||||
|                     }); | ||||
|                 } | ||||
| @@ -203,7 +211,7 @@ impl Connector { | ||||
|                         .await | ||||
|                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; | ||||
|                     return Ok(Conn { | ||||
|                         inner: Box::new(RustlsTlsConn { inner: io }), | ||||
|                         inner: self.verbose.wrap(RustlsTlsConn { inner: io }), | ||||
|                         is_proxy: false, | ||||
|                     }); | ||||
|                 } | ||||
| @@ -212,7 +220,10 @@ impl Connector { | ||||
|             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( | ||||
| @@ -225,7 +236,7 @@ impl Connector { | ||||
|             Inner::Http(mut http) => { | ||||
|                 let io = http.call(dst).await?; | ||||
|                 Ok(Conn { | ||||
|                     inner: Box::new(io), | ||||
|                     inner: self.verbose.wrap(io), | ||||
|                     is_proxy, | ||||
|                 }) | ||||
|             } | ||||
| @@ -246,7 +257,7 @@ impl Connector { | ||||
|                 //} | ||||
|  | ||||
|                 Ok(Conn { | ||||
|                     inner: Box::new(io), | ||||
|                     inner: self.verbose.wrap(io), | ||||
|                     is_proxy, | ||||
|                 }) | ||||
|             } | ||||
| @@ -270,7 +281,7 @@ impl Connector { | ||||
|                 } | ||||
|  | ||||
|                 Ok(Conn { | ||||
|                     inner: Box::new(io), | ||||
|                     inner: self.verbose.wrap(io), | ||||
|                     is_proxy, | ||||
|                 }) | ||||
|             } | ||||
| @@ -322,7 +333,7 @@ impl Connector { | ||||
|                         .await | ||||
|                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; | ||||
|                     return Ok(Conn { | ||||
|                         inner: Box::new(NativeTlsConn { inner: io }), | ||||
|                         inner: self.verbose.wrap(NativeTlsConn { inner: io }), | ||||
|                         is_proxy: false, | ||||
|                     }); | ||||
|                 } | ||||
| @@ -359,7 +370,7 @@ impl Connector { | ||||
|                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; | ||||
|  | ||||
|                     return Ok(Conn { | ||||
|                         inner: Box::new(RustlsTlsConn { inner: io }), | ||||
|                         inner: self.verbose.wrap(RustlsTlsConn { inner: io }), | ||||
|                         is_proxy: false, | ||||
|                     }); | ||||
|                 } | ||||
| @@ -438,8 +449,11 @@ impl Service<Uri> for Connector | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(crate) trait AsyncConn: AsyncRead + AsyncWrite + Connection {} | ||||
| impl<T: AsyncRead + AsyncWrite + Connection> AsyncConn for T {} | ||||
| pub(crate) trait AsyncConn: AsyncRead + AsyncWrite + Connection + Send + Sync + Unpin + 'static {} | ||||
|  | ||||
| impl<T: AsyncRead + AsyncWrite + Connection + Send + Sync + Unpin + 'static> AsyncConn for T {} | ||||
|  | ||||
| type BoxConn = Box<dyn AsyncConn>; | ||||
|  | ||||
| pin_project! { | ||||
|     /// 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. | ||||
|     pub(crate) struct Conn { | ||||
|         #[pin] | ||||
|         inner: Box<dyn AsyncConn + Send + Sync + Unpin + 'static>, | ||||
|         inner: BoxConn, | ||||
|         is_proxy: bool, | ||||
|     } | ||||
| } | ||||
| @@ -795,11 +809,13 @@ mod rustls_tls_conn { | ||||
|  | ||||
| #[cfg(feature = "socks")] | ||||
| mod socks { | ||||
|     use http::Uri; | ||||
|     use tokio_socks::tcp::Socks5Stream; | ||||
|     use std::io; | ||||
|     use std::net::ToSocketAddrs; | ||||
|  | ||||
|     use http::Uri; | ||||
|     use tokio::net::TcpStream; | ||||
|     use tokio_socks::tcp::Socks5Stream; | ||||
|  | ||||
|     use super::{BoxError, Scheme}; | ||||
|     use crate::proxy::ProxyScheme; | ||||
|  | ||||
| @@ -812,7 +828,7 @@ mod socks { | ||||
|         proxy: ProxyScheme, | ||||
|         dst: Uri, | ||||
|         dns: DnsResolve, | ||||
|     ) -> Result<super::Conn, BoxError> { | ||||
|     ) -> Result<TcpStream, BoxError> { | ||||
|         let https = dst.scheme() == Some(&Scheme::HTTPS); | ||||
|         let original_host = dst | ||||
|             .host() | ||||
| @@ -852,10 +868,123 @@ mod socks { | ||||
|                 .map_err(|e| format!("socks connect error: {}", e))? | ||||
|         }; | ||||
|  | ||||
|         Ok(super::Conn { | ||||
|             inner: Box::new( stream.into_inner() ), | ||||
|             is_proxy: false, | ||||
|         }) | ||||
|         Ok(stream.into_inner()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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; | ||||
|     #[cfg(feature = "__tls")] | ||||
|     mod tls; | ||||
|     mod util; | ||||
| } | ||||
|  | ||||
| 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