diff --git a/Cargo.lock b/Cargo.lock index d07344b..4768944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,28 +8,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "aws-lc-rs" -version = "1.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "base64" version = "0.22.1" @@ -44,13 +22,11 @@ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -60,15 +36,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -85,12 +52,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "equivalent" version = "1.0.2" @@ -140,12 +101,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "getrandom" version = "0.2.17" @@ -157,18 +112,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - [[package]] name = "getrandom" version = "0.4.2" @@ -177,7 +120,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", "wasip2", "wasip3", ] @@ -212,6 +155,7 @@ dependencies = [ "rustls", "rustls-pemfile", "rustls-pki-types", + "tracing", "unicase", "webpki", "webpki-roots", @@ -242,16 +186,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "leb128fmt" version = "0.1.0" @@ -260,9 +194,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "linux-raw-sys" @@ -349,6 +283,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkg-config" version = "0.3.33" @@ -383,12 +323,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -424,11 +358,10 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.39" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ - "aws-lc-rs", "log", "once_cell", "rustls-pki-types", @@ -448,9 +381,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] @@ -461,7 +394,6 @@ version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -583,6 +515,37 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + [[package]] name = "unicase" version = "2.9.0" diff --git a/Cargo.toml b/Cargo.toml index e5b110e..ef256ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,10 @@ unicase = "^2.9" base64 = { version = "^0.22", optional = true } zeroize = { version = "^1.8", features = ["zeroize_derive"], optional = true } native-tls = { version = "^0.2", optional = true } -rustls = { version = "^0.23", optional = true } +rustls = { version = "^0.23", optional = true, default-features = false, features = ["logging", "std"] } rustls-pemfile = { version = "^2.2", optional = true } rustls-pki-types = { version = "^1.14", features = ["alloc"], optional = true } +tracing = { version = "0.1", optional = true } webpki = { version = "^0.22", optional = true } webpki-roots = { version = "^1.0", optional = true } @@ -32,3 +33,4 @@ rust-tls = [ "auth", ] auth = ["base64", "zeroize"] +tracing = ["dep:tracing"] diff --git a/src/request.rs b/src/request.rs index 5f07af3..b06ed36 100644 --- a/src/request.rs +++ b/src/request.rs @@ -20,6 +20,8 @@ use std::{ }; #[cfg(feature = "auth")] use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; +#[cfg(feature = "tracing")] +use tracing::Span; const CR_LF: &str = "\r\n"; const DEFAULT_REDIRECT_LIMIT: usize = 5; @@ -499,7 +501,7 @@ impl<'a> RequestMessage<'a> { /// assert_eq!(response.status_code(), StatusCode::new(200)); /// ``` /// -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Request<'a> { message: RequestMessage<'a>, redirect_policy: RedirectPolicy bool>, @@ -508,6 +510,8 @@ pub struct Request<'a> { write_timeout: Option, timeout: Duration, root_cert_file_pem: Option<&'a Path>, + #[cfg(feature = "rust-tls")] + rustls_config: Option>, } impl<'a> Request<'a> { @@ -534,6 +538,8 @@ impl<'a> Request<'a> { write_timeout: Some(Duration::from_secs(DEFAULT_CALL_TIMEOUT)), timeout: Duration::from_secs(DEFAULT_REQ_TIMEOUT), root_cert_file_pem: None, + #[cfg(feature = "rust-tls")] + rustls_config: None, } } @@ -788,6 +794,15 @@ impl<'a> Request<'a> { self } + /// Sets a custom rustls `ClientConfig` to use for the TLS connection. + /// When set, overrides `root_cert_file_pem` and allows full control over + /// the root store, client certificate, and certificate verifier. + #[cfg(feature = "rust-tls")] + pub fn rustls_config(&mut self, config: std::sync::Arc) -> &mut Self { + self.rustls_config = Some(config); + self + } + /// Sets the redirect policy for the request. /// /// # Examples @@ -828,15 +843,37 @@ impl<'a> Request<'a> { where T: Write, { + #[cfg(feature = "tracing")] + let span = tracing::info_span!( + "http_request", + otel.name = %format!("{} {}", self.message.method, self.message.uri.host().unwrap_or("")), + otel.kind = "client", + http.method = %self.message.method, + http.url = %self.message.uri, + http.status_code = tracing::field::Empty, + http.duration_ms = tracing::field::Empty, + ); + #[cfg(feature = "tracing")] + let _guard = span.enter(); + #[cfg(feature = "tracing")] + let start = Instant::now(); + // Set up a stream. let mut stream = Stream::connect(self.message.uri, self.connect_timeout)?; stream.set_read_timeout(self.read_timeout)?; stream.set_write_timeout(self.write_timeout)?; - #[cfg(any(feature = "native-tls", feature = "rust-tls"))] + #[cfg(feature = "native-tls")] { stream = Stream::try_to_https(stream, self.message.uri, self.root_cert_file_pem)?; } + #[cfg(feature = "rust-tls")] + { + stream = match self.rustls_config.take() { + Some(config) => Stream::try_to_https_with_config(stream, self.message.uri, config)?, + None => Stream::try_to_https(stream, self.message.uri, self.root_cert_file_pem)?, + }; + } // Send the request message to the stream. let request_msg = self.message.parse(); @@ -868,6 +905,13 @@ impl<'a> Request<'a> { raw_response_head.receive(&receiver, deadline)?; let response = Response::from_head(&raw_response_head)?; + #[cfg(feature = "tracing")] + { + let status: u16 = response.status_code().into(); + Span::current().record("http.status_code", status as i64); + Span::current().record("http.duration_ms", start.elapsed().as_millis() as i64); + } + if response.status_code().is_redirect() { if let Some(location) = response.headers().get("Location") { if self.redirect_policy.follow(&location) { diff --git a/src/stream.rs b/src/stream.rs index 851bf6b..564650c 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -73,6 +73,24 @@ impl Stream { } } + /// Tries to establish a secure connection using a pre-built `rustls::ClientConfig`. + /// Use this when you need a custom root store, client certificate, or certificate verifier. + #[cfg(feature = "rust-tls")] + pub fn try_to_https_with_config(stream: Stream, uri: &Uri, config: std::sync::Arc) -> Result { + match stream { + Stream::Http(http_stream) => { + if uri.scheme() == "https" { + let host = uri.host().ok_or(Error::Parse(ParseErr::UriErr))?; + let conn = tls::connect_with_config(config, host, http_stream)?; + Ok(Stream::Https(conn)) + } else { + Ok(Stream::Http(http_stream)) + } + } + Stream::Https(_) => Ok(stream), + } + } + /// Sets the read timeout on the underlying TCP stream. pub fn set_read_timeout(&mut self, dur: Option) -> Result<(), Error> { match self { diff --git a/src/tls.rs b/src/tls.rs index bad4267..7839617 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -27,7 +27,7 @@ pub struct Conn { stream: native_tls::TlsStream, #[cfg(feature = "rust-tls")] - stream: rustls::StreamOwned, + pub stream: rustls::StreamOwned, } impl Conn @@ -200,3 +200,16 @@ impl Config { Ok(Conn { stream }) } } + +/// Establishes a secure connection using a pre-built `rustls::ClientConfig`. +/// Use this when you need a custom root store, client certificate, or certificate verifier. +#[cfg(feature = "rust-tls")] +pub fn connect_with_config(config: std::sync::Arc, hostname: H, stream: S) -> Result, HttpError> +where + H: AsRef, + S: io::Read + io::Write, +{ + let hostname = hostname.as_ref().to_string(); + let session = ClientConnection::new(config, ServerName::try_from(hostname).map_err(|_| HttpError::Tls)?).map_err(|_| HttpError::Tls)?; + Ok(Conn { stream: StreamOwned::new(session, stream) }) +}