diff --git a/Cargo.lock b/Cargo.lock index 5dde536..f82a9e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,6 +199,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -533,7 +544,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.10", "serde", ] @@ -838,8 +849,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1104,6 +1117,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -1211,6 +1227,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540f1c43aed89909c0cc0cc604e3bb2f7e7a341a3728a9e6cfe760e733cd11ed" + [[package]] name = "memchr" version = "2.7.2" @@ -1329,6 +1351,17 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1336,7 +1369,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -1507,6 +1554,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1624,6 +1680,70 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest-middleware" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45d100244a467870f6cb763c4484d010a6bed6bd610b3676e3825d93fb4cfbd" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "thiserror", + "tower-service", +] + +[[package]] +name = "reqwest-retry" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40f342894422862af74c50e1e9601cf0931accc9c6981e5eb413c46603b616b5" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "futures", + "getrandom", + "http", + "hyper", + "parking_lot 0.11.2", + "reqwest", + "reqwest-middleware", + "retry-policies", + "tokio", + "tracing", + "wasm-timer", +] + +[[package]] +name = "reqwest-tracing" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b253954a1979e02eabccd7e9c3d61d8f86576108baa160775e7f160bb4e800a3" +dependencies = [ + "anyhow", + "async-trait", + "getrandom", + "http", + "matchit", + "reqwest", + "reqwest-middleware", + "tracing", +] + +[[package]] +name = "retry-policies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "493b4243e32d6eedd29f9a398896e35c6943a123b55eec97dcaee98310d25810" +dependencies = [ + "anyhow", + "chrono", + "rand", +] + [[package]] name = "ring" version = "0.17.8" @@ -1660,6 +1780,9 @@ dependencies = [ "once_cell", "process-wrap", "reqwest", + "reqwest-middleware", + "reqwest-retry", + "reqwest-tracing", "semver", "serde", "serde_json", @@ -2071,7 +2194,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -2392,6 +2515,21 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.69" diff --git a/Cargo.toml b/Cargo.toml index d4091c1..4515536 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,9 @@ reqwest = { version = "0.12", default-features = false, features = [ "brotli", "deflate", ] } +reqwest-middleware = "0.3" +reqwest-retry = "0.5" +reqwest-tracing = "0.5" tokio = { version = "1.36", features = ["full"] } tracing = "0.1" diff --git a/lib/sources/client.rs b/lib/sources/client.rs new file mode 100644 index 0000000..4dcd7c5 --- /dev/null +++ b/lib/sources/client.rs @@ -0,0 +1,41 @@ +use std::time::Duration; + +use reqwest::{header::HeaderMap, Client, Error}; +use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; +use reqwest_tracing::TracingMiddleware; + +/* + Adds middleware for: + + - Retrying failed requests with exponential backoff + - Tracing of HTTP requests +*/ +fn add_client_middleware(client: Client) -> ClientWithMiddleware { + ClientBuilder::new(client) + .with(RetryTransientMiddleware::new_with_policy( + ExponentialBackoff::builder().build_with_max_retries(3), + )) + .with(TracingMiddleware::default()) + .build() +} + +/** + Creates a client with: + + - HTTPS only + - Timeouts for connection and response + - All common compression algorithms enabled +*/ +pub fn create_client(default_headers: HeaderMap) -> Result { + let client = Client::builder() + .default_headers(default_headers) + .https_only(true) + .connect_timeout(Duration::from_secs(15)) + .timeout(Duration::from_secs(60)) + .gzip(true) + .brotli(true) + .deflate(true) + .build()?; + Ok(add_client_middleware(client)) +} diff --git a/lib/sources/github/mod.rs b/lib/sources/github/mod.rs index 8958665..9f0da0f 100644 --- a/lib/sources/github/mod.rs +++ b/lib/sources/github/mod.rs @@ -1,5 +1,4 @@ -use std::time::Duration; - +use reqwest_middleware::ClientWithMiddleware; use semver::Version; use serde::de::DeserializeOwned; use tracing::{debug, instrument}; @@ -11,7 +10,7 @@ use reqwest::{ use crate::tool::{ToolId, ToolSpec}; -use super::{Artifact, ArtifactProvider}; +use super::{client::create_client, Artifact, ArtifactProvider}; const BASE_URL: &str = "https://api.github.com"; @@ -24,7 +23,7 @@ pub use self::result::{GithubError, GithubResult}; #[derive(Debug, Clone)] pub struct GithubProvider { - client: reqwest::Client, + client: ClientWithMiddleware, has_auth: bool, } @@ -45,14 +44,7 @@ impl GithubProvider { headers }; - let client = reqwest::Client::builder() - .default_headers(headers) - .gzip(true) - .brotli(true) - .deflate(true) - .connect_timeout(Duration::from_secs(15)) - .timeout(Duration::from_secs(60)) - .build()?; + let client = create_client(headers)?; Ok(Self { client, has_auth }) } diff --git a/lib/sources/github/result.rs b/lib/sources/github/result.rs index 36f64ba..76c8155 100644 --- a/lib/sources/github/result.rs +++ b/lib/sources/github/result.rs @@ -13,6 +13,8 @@ pub enum GithubError { ReleaseNotFound(Box), #[error("failed to build client - invalid header value: {0}")] ReqwestHeader(Box), + #[error("reqwest middleware error: {0}")] + ReqwestMiddleware(Box), #[error("reqwest error: {0}")] Reqwest(Box), #[error("other error: {0}")] @@ -29,6 +31,12 @@ impl From for GithubError { } } +impl From for GithubError { + fn from(err: reqwest_middleware::Error) -> Self { + GithubError::ReqwestMiddleware(err.into()) + } +} + impl From for GithubError { fn from(err: ReqwestError) -> Self { GithubError::Reqwest(err.into()) diff --git a/lib/sources/mod.rs b/lib/sources/mod.rs index 08a61aa..f5e85f1 100644 --- a/lib/sources/mod.rs +++ b/lib/sources/mod.rs @@ -1,4 +1,5 @@ mod artifact; +mod client; mod decompression; mod extraction; mod source;