diff options
-rw-r--r-- | Cargo.lock | 174 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/frontend/onboarding.rs | 6 | ||||
-rw-r--r-- | src/indieauth/mod.rs | 9 | ||||
-rw-r--r-- | src/lib.rs | 6 | ||||
-rw-r--r-- | src/login.rs | 6 | ||||
-rw-r--r-- | src/main.rs | 10 | ||||
-rw-r--r-- | src/micropub/mod.rs | 35 | ||||
-rw-r--r-- | src/tokenauth.rs | 6 | ||||
-rw-r--r-- | src/webmentions/mod.rs | 6 |
10 files changed, 233 insertions, 27 deletions
diff --git a/Cargo.lock b/Cargo.lock index be9a1b2..7edf736 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,6 +239,17 @@ dependencies = [ ] [[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] name = "async-trait" version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -445,6 +456,15 @@ dependencies = [ ] [[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -716,6 +736,24 @@ dependencies = [ ] [[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] name = "crossbeam-queue" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -972,6 +1010,16 @@ dependencies = [ ] [[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] name = "faker_rand" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1418,6 +1466,61 @@ dependencies = [ ] [[package]] +name = "http-cache" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ffb12b95bb2a369fe47ca8924016c72c2fa0e6059ba98bd1516f558696c5a8" +dependencies = [ + "async-trait", + "bincode", + "http", + "http-cache-semantics", + "httpdate", + "moka", + "serde", + "url", +] + +[[package]] +name = "http-cache-reqwest" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be3e27c4e2e510571cbcc601407b639667146aa9a4e818d5cc1d97c8b4b27d61" +dependencies = [ + "anyhow", + "async-trait", + "http", + "http-cache", + "http-cache-semantics", + "reqwest", + "reqwest-middleware", + "serde", + "url", +] + +[[package]] +name = "http-cache-semantics" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92baf25cf0b8c9246baecf3a444546360a97b569168fdf92563ee6a47829920c" +dependencies = [ + "http", + "http-serde", + "serde", + "time", +] + +[[package]] +name = "http-serde" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f056c8559e3757392c8d091e796416e4649d8e49e88b8d76df6c002f05027fd" +dependencies = [ + "http", + "serde", +] + +[[package]] name = "httparse" version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1632,6 +1735,7 @@ dependencies = [ "futures-util", "hex", "html5ever 0.27.0", + "http-cache-reqwest", "hyper", "kittybox-frontend-renderer", "kittybox-indieauth", @@ -1647,6 +1751,7 @@ dependencies = [ "redis", "relative-path", "reqwest", + "reqwest-middleware", "serde", "serde_json", "serde_urlencoded", @@ -2021,6 +2126,30 @@ dependencies = [ ] [[package]] +name = "moka" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e0d88686dc561d743b40de8269b26eaf0dc58781bde087b0984646602021d08" +dependencies = [ + "async-lock", + "async-trait", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener", + "futures-util", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid", +] + +[[package]] name = "multer" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2562,6 +2691,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] name = "quinn" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2698,6 +2842,15 @@ dependencies = [ ] [[package]] +name = "raw-cpuid" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] name = "redis" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2837,6 +2990,21 @@ dependencies = [ ] [[package]] +name = "reqwest-middleware" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "thiserror", + "tower-service", +] + +[[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3786,6 +3954,12 @@ dependencies = [ ] [[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index b5346b4..6d56c89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,8 @@ webauthn = { version = "0.5.0", package = "webauthn-rs", features = ["danger-all base64 = "0.22.1" html5ever = "0.27.0" mime = "0.3.17" +http-cache-reqwest = { version = "0.14.0", default-features = false, features = ["manager-moka"] } +reqwest-middleware = "0.3.3" [dependencies.tokio] version = "^1.29.1" features = ["full", "tracing"] # TODO determine if my app doesn't need some features diff --git a/src/frontend/onboarding.rs b/src/frontend/onboarding.rs index be1669f..a8d2ae6 100644 --- a/src/frontend/onboarding.rs +++ b/src/frontend/onboarding.rs @@ -53,7 +53,7 @@ async fn onboard<D: Storage + 'static>( db: D, user_uid: url::Url, data: OnboardingData, - http: reqwest::Client, + http: reqwest_middleware::ClientWithMiddleware, jobset: Arc<Mutex<JoinSet<()>>>, ) -> Result<(), FrontendError> { // Create a user to pass to the backend @@ -126,7 +126,7 @@ async fn onboard<D: Storage + 'static>( pub async fn post<D: Storage + 'static>( State(db): State<D>, Host(host): Host, - State(http): State<reqwest::Client>, + State(http): State<reqwest_middleware::ClientWithMiddleware>, State(jobset): State<Arc<Mutex<JoinSet<()>>>>, Json(data): Json<OnboardingData>, ) -> axum::response::Response { @@ -165,7 +165,7 @@ pub fn router<St, S>() -> axum::routing::MethodRouter<St> where S: Storage + FromRef<St> + 'static, Arc<Mutex<JoinSet<()>>>: FromRef<St>, - reqwest::Client: FromRef<St>, + reqwest_middleware::ClientWithMiddleware: FromRef<St>, St: Clone + Send + Sync + 'static, { axum::routing::get(get) diff --git a/src/indieauth/mod.rs b/src/indieauth/mod.rs index 322a0e2..b3db77f 100644 --- a/src/indieauth/mod.rs +++ b/src/indieauth/mod.rs @@ -131,7 +131,7 @@ async fn authorization_endpoint_get<A: AuthBackend, D: Storage + 'static>( Host(host): Host, Query(request): Query<AuthorizationRequest>, State(db): State<D>, - State(http): State<reqwest::Client>, + State(http): State<reqwest_middleware::ClientWithMiddleware>, State(auth): State<A> ) -> Response { let me: url::Url = format!("https://{host}/").parse().unwrap(); @@ -148,7 +148,10 @@ async fn authorization_endpoint_get<A: AuthBackend, D: Storage + 'static>( tracing::debug!("Sending request to {} to fetch metadata", request.client_id); let metadata_request = http.get(request.client_id.clone()) .header("Accept", "application/json, text/html"); - match metadata_request.send().await.and_then(|res| res.error_for_status()) { + match metadata_request.send().await + .and_then(|res| res.error_for_status() + .map_err(reqwest_middleware::Error::Reqwest)) + { Ok(response) if response.headers().typed_get::<ContentType>().to_owned().map(mime::Mime::from).map(|m| m.type_() == "text" && m.subtype() == "html").unwrap_or(false) => { let url = response.url().clone(); let text = response.text().await.unwrap(); @@ -847,7 +850,7 @@ pub fn router<St, A, S>() -> axum::Router<St> where S: Storage + FromRef<St> + 'static, A: AuthBackend + FromRef<St>, - reqwest::Client: FromRef<St>, + reqwest_middleware::ClientWithMiddleware: FromRef<St>, St: Clone + Send + Sync + 'static { use axum::routing::{Router, get, post}; diff --git a/src/lib.rs b/src/lib.rs index 596ffc0..5fe3b18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ Q: JobQueue<webmentions::Webmention> + Sized pub storage: S, pub media_store: M, pub job_queue: Q, - pub http: reqwest::Client, + pub http: reqwest_middleware::ClientWithMiddleware, pub background_jobs: Arc<Mutex<JoinSet<()>>>, pub cookie_key: Key, pub session_store: SessionStore @@ -156,7 +156,7 @@ where A: AuthBackend, S: Storage, M: MediaStore, Q: JobQueue<webmentions::Webmen } } -impl<A, S, M, Q> FromRef<AppState<A, S, M, Q>> for reqwest::Client +impl<A, S, M, Q> FromRef<AppState<A, S, M, Q>> for reqwest_middleware::ClientWithMiddleware where A: AuthBackend, S: Storage, M: MediaStore, Q: JobQueue<webmentions::Webmention> { fn from_ref(input: &AppState<A, S, M, Q>) -> Self { @@ -291,7 +291,7 @@ A: AuthBackend + 'static + FromRef<St>, S: Storage + 'static + FromRef<St>, M: MediaStore + 'static + FromRef<St>, Q: kittybox_util::queue::JobQueue<crate::webmentions::Webmention> + FromRef<St>, -reqwest::Client: FromRef<St>, +reqwest_middleware::ClientWithMiddleware: FromRef<St>, Arc<Mutex<JoinSet<()>>>: FromRef<St>, crate::SessionStore: FromRef<St>, axum_extra::extract::cookie::Key: FromRef<St>, diff --git a/src/login.rs b/src/login.rs index bfa84b3..e105bcc 100644 --- a/src/login.rs +++ b/src/login.rs @@ -48,7 +48,7 @@ struct LoginForm { async fn post( Host(host): Host, mut cookies: SignedCookieJar, - State(http): State<reqwest::Client>, + State(http): State<reqwest_middleware::ClientWithMiddleware>, Form(form): Form<LoginForm>, ) -> axum::response::Response { let code_verifier = kittybox_indieauth::PKCEVerifier::new(); @@ -204,7 +204,7 @@ async fn callback( Host(host): Host, Query(result): Query<AuthorizationResponse>, cookie_jar: SignedCookieJar, - State(http): State<reqwest::Client>, + State(http): State<reqwest_middleware::ClientWithMiddleware>, State(session_store): State<crate::SessionStore>, ) -> axum::response::Response { let client_id: url::Url = format!("https://{}/.kittybox/login/client_metadata", host).parse().unwrap(); @@ -355,7 +355,7 @@ pub fn router<St, S>() -> axum::routing::Router<St> where St: Clone + Send + Sync + 'static, cookie::Key: FromRef<St>, - reqwest::Client: FromRef<St>, + reqwest_middleware::ClientWithMiddleware: FromRef<St>, crate::SessionStore: FromRef<St>, S: Storage + FromRef<St> + Send + Sync + 'static, { diff --git a/src/main.rs b/src/main.rs index f272a63..3aee6c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,7 +92,7 @@ async fn main() { let jobset: Arc<Mutex<JoinSet<()>>> = Default::default(); let session_store: kittybox::SessionStore = Default::default(); - let http: reqwest::Client = { + let http: reqwest_middleware::ClientWithMiddleware = { #[allow(unused_mut)] let mut builder = reqwest::Client::builder() .user_agent(concat!( @@ -137,7 +137,13 @@ async fn main() { builder = builder.danger_accept_invalid_certs(true); } - builder.build().unwrap() + reqwest_middleware::ClientBuilder::new(builder.build().unwrap()) + .with(http_cache_reqwest::Cache(http_cache_reqwest::HttpCache { + mode: http_cache_reqwest::CacheMode::Default, + manager: http_cache_reqwest::MokaManager::default(), + options: http_cache_reqwest::HttpCacheOptions::default(), + })) + .build() }; let backend_type = backend_uri.scheme(); diff --git a/src/micropub/mod.rs b/src/micropub/mod.rs index 08150d2..8f95085 100644 --- a/src/micropub/mod.rs +++ b/src/micropub/mod.rs @@ -82,7 +82,7 @@ fn populate_reply_context( async fn background_processing<D: 'static + Storage>( db: D, mf2: serde_json::Value, - http: reqwest::Client, + http: reqwest_middleware::ClientWithMiddleware, ) -> () { // TODO: Post-processing the post (aka second write pass) // - [x] Download rich reply contexts @@ -232,7 +232,7 @@ pub(crate) async fn _post<D: 'static + Storage>( uid: String, mf2: serde_json::Value, db: D, - http: reqwest::Client, + http: reqwest_middleware::ClientWithMiddleware, jobset: Arc<Mutex<JoinSet<()>>>, ) -> Result<Response, MicropubError> { // Here, we have the following guarantees: @@ -501,7 +501,7 @@ async fn dispatch_body( #[tracing::instrument(skip(db, http))] pub(crate) async fn post<D: Storage + 'static, A: AuthBackend>( State(db): State<D>, - State(http): State<reqwest::Client>, + State(http): State<reqwest_middleware::ClientWithMiddleware>, State(jobset): State<Arc<Mutex<JoinSet<()>>>>, TypedHeader(content_type): TypedHeader<ContentType>, user: User<A>, @@ -655,7 +655,7 @@ pub fn router<A, S, St: Send + Sync + Clone + 'static>() -> axum::routing::Metho where S: Storage + FromRef<St> + 'static, A: AuthBackend + FromRef<St>, - reqwest::Client: FromRef<St>, + reqwest_middleware::ClientWithMiddleware: FromRef<St>, Arc<Mutex<JoinSet<()>>>: FromRef<St> { axum::routing::get(query::<S, A>) @@ -758,7 +758,14 @@ mod tests { }; let (uid, mf2) = super::normalize_mf2(post, &user); - let err = super::_post(&user, uid, mf2, db.clone(), reqwest::Client::new(), Arc::new(Mutex::new(tokio::task::JoinSet::new()))) + let err = super::_post( + &user, uid, mf2, db.clone(), + reqwest_middleware::ClientWithMiddleware::new( + reqwest::Client::new(), + Box::default() + ), + Arc::new(Mutex::new(tokio::task::JoinSet::new())) + ) .await .unwrap_err(); @@ -788,7 +795,14 @@ mod tests { }; let (uid, mf2) = super::normalize_mf2(post, &user); - let err = super::_post(&user, uid, mf2, db.clone(), reqwest::Client::new(), Arc::new(Mutex::new(tokio::task::JoinSet::new()))) + let err = super::_post( + &user, uid, mf2, db.clone(), + reqwest_middleware::ClientWithMiddleware::new( + reqwest::Client::new(), + Box::default() + ), + Arc::new(Mutex::new(tokio::task::JoinSet::new())) + ) .await .unwrap_err(); @@ -816,7 +830,14 @@ mod tests { }; let (uid, mf2) = super::normalize_mf2(post, &user); - let res = super::_post(&user, uid, mf2, db.clone(), reqwest::Client::new(), Arc::new(Mutex::new(tokio::task::JoinSet::new()))) + let res = super::_post( + &user, uid, mf2, db.clone(), + reqwest_middleware::ClientWithMiddleware::new( + reqwest::Client::new(), + Box::default() + ), + Arc::new(Mutex::new(tokio::task::JoinSet::new())) + ) .await .unwrap(); diff --git a/src/tokenauth.rs b/src/tokenauth.rs index 244a045..414454a 100644 --- a/src/tokenauth.rs +++ b/src/tokenauth.rs @@ -173,7 +173,7 @@ where let Extension(TokenEndpoint(token_endpoint)): Extension<TokenEndpoint> = Extension::from_request(req).await.unwrap(); - let Extension(http): Extension<reqwest::Client> = + let Extension(http): Extension<reqwest_middleware::ClientWithMiddleware> = Extension::from_request(req).await.unwrap(); match http @@ -253,8 +253,8 @@ mod tests { } #[inline] - fn get_http_client() -> reqwest::Client { - reqwest::Client::new() + fn get_http_client() -> reqwest_middleware::ClientWithMiddleware { + reqwest_middleware::ClientWithMiddleware::new() } fn request<A: Into<Option<&'static str>>>( diff --git a/src/webmentions/mod.rs b/src/webmentions/mod.rs index d5a617e..22701b4 100644 --- a/src/webmentions/mod.rs +++ b/src/webmentions/mod.rs @@ -102,7 +102,7 @@ enum Error<Q: std::error::Error + std::fmt::Debug + Send + 'static> { Storage(StorageError) } -async fn process_webmentions_from_queue<Q: JobQueue<Webmention>, S: Storage + 'static>(queue: Q, db: S, http: reqwest::Client) -> Result<std::convert::Infallible, Error<Q::Error>> { +async fn process_webmentions_from_queue<Q: JobQueue<Webmention>, S: Storage + 'static>(queue: Q, db: S, http: reqwest_middleware::ClientWithMiddleware) -> Result<std::convert::Infallible, Error<Q::Error>> { use futures_util::StreamExt; use self::queue::Job; @@ -177,11 +177,11 @@ pub fn supervised_webmentions_task<St: Send + Sync + 'static, S: Storage + FromR state: &St, cancellation_token: tokio_util::sync::CancellationToken ) -> SupervisedTask -where reqwest::Client: FromRef<St> +where reqwest_middleware::ClientWithMiddleware: FromRef<St> { let queue = Q::from_ref(state); let storage = S::from_ref(state); - let http = reqwest::Client::from_ref(state); + let http = reqwest_middleware::ClientWithMiddleware::from_ref(state); supervisor::<Error<Q::Error>, _, _>(move || process_webmentions_from_queue( queue.clone(), storage.clone(), http.clone() ), cancellation_token) |