From f95e494a8a8a840d4ab40844179ce93cd75772a6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 26 May 2024 20:08:54 +0100 Subject: [PATCH] feat: migrate server to actix-http --- .rustfmt.toml | 3 + Cargo.lock | 193 ++++++++++++++++++++++++-------- Cargo.toml | 4 +- build.rs | 4 +- libs/badge/badge.rs | 4 +- src/engine/fut/crawl.rs | 11 +- src/engine/fut/mod.rs | 3 +- src/engine/machines/analyzer.rs | 3 +- src/engine/machines/crawler.rs | 9 +- src/engine/mod.rs | 27 +++-- src/interactors/crates.rs | 17 +-- src/interactors/github.rs | 14 +-- src/interactors/mod.rs | 15 +-- src/interactors/rustsec.rs | 10 +- src/main.rs | 60 +++++----- src/parsers/manifest.rs | 3 +- src/server/mod.rs | 179 ++++++++++++++++------------- src/server/views/badge.rs | 15 +-- src/server/views/html/error.rs | 19 ++-- src/server/views/html/index.rs | 15 ++- src/server/views/html/mod.rs | 15 +-- src/server/views/html/status.rs | 19 ++-- src/utils/cache.rs | 6 +- src/utils/index.rs | 19 ++-- 24 files changed, 393 insertions(+), 274 deletions(-) create mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..efd3c5b --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +use_field_init_shorthand = true diff --git a/Cargo.lock b/Cargo.lock index fa3da05..9b44ed2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,103 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.5.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb9843d84c775696c37d9a418bbb01b932629d01870722c0f13eb3f95e2536d" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "bitflags 2.5.0", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "futures-core", + "h2 0.3.26", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "mime", + "percent-encoding", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -30,6 +127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -169,6 +267,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] + [[package]] name = "cadence" version = "1.4.0" @@ -1487,17 +1594,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.0" @@ -1517,7 +1613,7 @@ dependencies = [ "bytes", "futures-core", "http 1.1.0", - "http-body 1.0.0", + "http-body", "pin-project-lite", ] @@ -1533,30 +1629,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.3.1" @@ -1568,7 +1640,7 @@ dependencies = [ "futures-util", "h2 0.4.5", "http 1.1.0", - "http-body 1.0.0", + "http-body", "httparse", "itoa", "pin-project-lite", @@ -1585,7 +1657,7 @@ checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper", "hyper-util", "rustls", "rustls-pki-types", @@ -1602,7 +1674,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.3.1", + "hyper", "hyper-util", "native-tls", "tokio", @@ -1620,8 +1692,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", @@ -1701,6 +1773,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lasso" version = "0.7.2" @@ -1728,6 +1806,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.12" @@ -1829,6 +1913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -1983,6 +2068,12 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2271,9 +2362,9 @@ dependencies = [ "futures-util", "h2 0.4.5", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", - "hyper 1.3.1", + "hyper", "hyper-rustls", "hyper-tls", "hyper-util", @@ -2597,6 +2688,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" name = "shiny-robots" version = "0.1.0" dependencies = [ + "actix-http", + "actix-server", + "actix-service", "anyhow", "badge", "cadence", @@ -2607,7 +2701,6 @@ dependencies = [ "futures-util", "gix", "grass", - "hyper 0.14.28", "indexmap", "lru_time_cache", "maud", @@ -2627,6 +2720,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -2860,7 +2962,9 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -3012,6 +3116,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", diff --git a/Cargo.toml b/Cargo.toml index dccc01c..9416560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ edition = "2021" [dependencies] badge = { path = "./libs/badge" } +actix-http = { version = "3", features = ["http2"] } +actix-server = "2" +actix-service = "2" anyhow = "1" cadence = "1" crates-index = { version = "2", default-features = false, features = ["git"] } @@ -21,7 +24,6 @@ derive_more = "0.99" dotenvy = "0.15" font-awesome-as-a-crate = "0.3" futures-util = { version = "0.3", default-features = false, features = ["std"] } -hyper = { version = "0.14.10", features = ["full"] } indexmap = { version = "2", features = ["serde"] } lru_time_cache = "0.11" maud = "0.26" diff --git a/build.rs b/build.rs index 6ac4147..f8e3f0d 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,4 @@ -use std::env; -use std::fs; -use std::path::Path; +use std::{env, fs, path::Path}; use sha1::{Digest, Sha1}; diff --git a/libs/badge/badge.rs b/libs/badge/badge.rs index 146eb69..f328738 100644 --- a/libs/badge/badge.rs +++ b/libs/badge/badge.rs @@ -235,8 +235,8 @@ mod tests { #[test] #[ignore] fn test_to_svg() { - use std::fs::File; - use std::io::Write; + use std::{fs::File, io::Write as _}; + let mut file = File::create("test.svg").unwrap(); let options = BadgeOptions { subject: "latest".to_owned(), diff --git a/src/engine/fut/crawl.rs b/src/engine/fut/crawl.rs index 08165dc..c30d88a 100644 --- a/src/engine/fut/crawl.rs +++ b/src/engine/fut/crawl.rs @@ -2,11 +2,12 @@ use anyhow::Error; use futures_util::{future::BoxFuture, stream::FuturesOrdered, FutureExt as _, StreamExt as _}; use relative_path::RelativePathBuf; -use crate::models::repo::RepoPath; - -use crate::engine::{ - machines::crawler::{ManifestCrawler, ManifestCrawlerOutput}, - Engine, +use crate::{ + engine::{ + machines::crawler::{ManifestCrawler, ManifestCrawlerOutput}, + Engine, + }, + models::repo::RepoPath, }; pub async fn crawl_manifest( diff --git a/src/engine/fut/mod.rs b/src/engine/fut/mod.rs index 9112e18..800118d 100644 --- a/src/engine/fut/mod.rs +++ b/src/engine/fut/mod.rs @@ -1,5 +1,4 @@ mod analyze; mod crawl; -pub use self::analyze::analyze_dependencies; -pub use self::crawl::crawl_manifest; +pub use self::{analyze::analyze_dependencies, crawl::crawl_manifest}; diff --git a/src/engine/machines/analyzer.rs b/src/engine/machines/analyzer.rs index 8ebfb01..4e607c9 100644 --- a/src/engine/machines/analyzer.rs +++ b/src/engine/machines/analyzer.rs @@ -101,9 +101,8 @@ impl DependencyAnalyzer { #[cfg(test)] mod tests { - use crate::models::crates::{CrateDep, CrateDeps, CrateRelease}; - use super::*; + use crate::models::crates::{CrateDep, CrateDeps, CrateRelease}; #[test] fn tracks_latest_without_matching() { diff --git a/src/engine/machines/crawler.rs b/src/engine/machines/crawler.rs index 8eb7ba4..267db8d 100644 --- a/src/engine/machines/crawler.rs +++ b/src/engine/machines/crawler.rs @@ -4,8 +4,10 @@ use anyhow::Error; use indexmap::IndexMap; use relative_path::RelativePathBuf; -use crate::models::crates::{CrateDep, CrateDeps, CrateManifest, CrateName}; -use crate::parsers::manifest::parse_manifest_toml; +use crate::{ + models::crates::{CrateDep, CrateDeps, CrateManifest, CrateName}, + parsers::manifest::parse_manifest_toml, +}; pub struct ManifestCrawlerOutput { pub crates: IndexMap, @@ -118,9 +120,8 @@ mod tests { use relative_path::RelativePath; use semver::VersionReq; - use crate::models::crates::CrateDep; - use super::*; + use crate::models::crates::CrateDep; #[test] fn simple_package_manifest() { diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 0193845..13736f8 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -5,28 +5,33 @@ use std::{ time::{Duration, Instant}, }; +use actix_service::Service; use anyhow::{anyhow, Error}; use cadence::{MetricSink, NopMetricSink, StatsdClient}; - use futures_util::{ future::try_join_all, stream::{self, BoxStream}, StreamExt as _, }; -use hyper::service::Service; use once_cell::sync::Lazy; use relative_path::{RelativePath, RelativePathBuf}; use rustsec::database::Database; use semver::VersionReq; -use crate::interactors::crates::{GetPopularCrates, QueryCrate}; -use crate::interactors::github::GetPopularRepos; -use crate::interactors::rustsec::FetchAdvisoryDatabase; -use crate::interactors::RetrieveFileAtPath; -use crate::models::crates::{AnalyzedDependencies, CrateName, CratePath, CrateRelease}; -use crate::models::repo::{RepoPath, Repository}; -use crate::utils::cache::Cache; -use crate::ManagedIndex; +use crate::{ + interactors::{ + crates::{GetPopularCrates, QueryCrate}, + github::GetPopularRepos, + rustsec::FetchAdvisoryDatabase, + RetrieveFileAtPath, + }, + models::{ + crates::{AnalyzedDependencies, CrateName, CratePath, CrateRelease}, + repo::{RepoPath, Repository}, + }, + utils::cache::Cache, + ManagedIndex, +}; mod fut; mod machines; @@ -272,7 +277,7 @@ impl Engine { ) -> Result { let manifest_path = path.join(RelativePath::new("Cargo.toml")); - let mut service = self.retrieve_file_at_path.clone(); + let service = self.retrieve_file_at_path.clone(); service.call((repo_path.clone(), manifest_path)).await } diff --git a/src/interactors/crates.rs b/src/interactors/crates.rs index e490a64..9cdf83d 100644 --- a/src/interactors/crates.rs +++ b/src/interactors/crates.rs @@ -1,12 +1,11 @@ -use std::{fmt, str, task::Context, task::Poll}; +use std::fmt; +use actix_service::Service; use anyhow::{anyhow, Error}; use crates_index::{Crate, DependencyKind}; use futures_util::FutureExt as _; -use hyper::service::Service; use semver::{Version, VersionReq}; use serde::Deserialize; - use tokio::task::spawn_blocking; use crate::{ @@ -86,11 +85,9 @@ impl Service for QueryCrate { type Error = Error; type Future = BoxFuture>; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, crate_name: CrateName) -> Self::Future { + fn call(&self, crate_name: CrateName) -> Self::Future { let index = self.index.clone(); Self::query(index, crate_name).boxed() } @@ -150,11 +147,9 @@ impl Service<()> for GetPopularCrates { type Error = Error; type Future = BoxFuture>; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, _req: ()) -> Self::Future { + fn call(&self, _req: ()) -> Self::Future { let client = self.client.clone(); Self::query(client).boxed() } diff --git a/src/interactors/github.rs b/src/interactors/github.rs index 32d5c86..daba36c 100644 --- a/src/interactors/github.rs +++ b/src/interactors/github.rs @@ -1,12 +1,8 @@ -use std::{ - fmt, - task::{Context, Poll}, -}; +use std::fmt; +use actix_service::Service; use anyhow::Error; - use futures_util::FutureExt as _; -use hyper::service::Service; use serde::Deserialize; use crate::{ @@ -75,11 +71,9 @@ impl Service<()> for GetPopularRepos { type Error = Error; type Future = BoxFuture>; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, _req: ()) -> Self::Future { + fn call(&self, _req: ()) -> Self::Future { let client = self.client.clone(); Self::query(client).boxed() } diff --git a/src/interactors/mod.rs b/src/interactors/mod.rs index 030174f..7b23936 100644 --- a/src/interactors/mod.rs +++ b/src/interactors/mod.rs @@ -1,11 +1,8 @@ -use std::{ - fmt, - task::{Context, Poll}, -}; +use std::fmt; +use actix_service::Service; use anyhow::{anyhow, Error}; use futures_util::FutureExt as _; -use hyper::service::Service; use relative_path::RelativePathBuf; use crate::{models::repo::RepoPath, BoxFuture}; @@ -45,11 +42,9 @@ impl Service<(RepoPath, RelativePathBuf)> for RetrieveFileAtPath { type Error = Error; type Future = BoxFuture>; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, (repo_path, path): (RepoPath, RelativePathBuf)) -> Self::Future { + fn call(&self, (repo_path, path): (RepoPath, RelativePathBuf)) -> Self::Future { let client = self.client.clone(); Self::query(client, repo_path, path).boxed() } @@ -57,6 +52,6 @@ impl Service<(RepoPath, RelativePathBuf)> for RetrieveFileAtPath { impl fmt::Debug for RetrieveFileAtPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("RetrieveFileAtPath") + f.debug_struct("RetrieveFileAtPath").finish_non_exhaustive() } } diff --git a/src/interactors/rustsec.rs b/src/interactors/rustsec.rs index e28488e..6768d71 100644 --- a/src/interactors/rustsec.rs +++ b/src/interactors/rustsec.rs @@ -1,8 +1,8 @@ -use std::{fmt, sync::Arc, task::Context, task::Poll}; +use std::{fmt, sync::Arc}; +use actix_service::Service; use anyhow::Error; use futures_util::FutureExt as _; -use hyper::service::Service; use rustsec::database::Database; use crate::BoxFuture; @@ -28,11 +28,9 @@ impl Service<()> for FetchAdvisoryDatabase { type Error = Error; type Future = BoxFuture>; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, _req: ()) -> Self::Future { + fn call(&self, _req: ()) -> Self::Future { let client = self.client.clone(); Self::fetch(client).boxed() } diff --git a/src/main.rs b/src/main.rs index 0b2d451..361987f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,18 +4,14 @@ use std::{ env, future::Future, - net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}, + net::{Ipv4Addr, UdpSocket}, pin::Pin, time::Duration, }; +use actix_http::{HttpService, Request}; +use actix_server::Server; use cadence::{QueuingMetricSink, UdpMetricSink}; -use hyper::{ - server::conn::AddrStream, - service::{make_service_fn, service_fn}, - Server, -}; - use reqwest::redirect::Policy as RedirectPolicy; use tracing::Instrument as _; @@ -26,9 +22,7 @@ mod parsers; mod server; mod utils; -use self::engine::Engine; -use self::server::App; -use self::utils::index::ManagedIndex; +use self::{engine::Engine, server::App, utils::index::ManagedIndex}; /// Future crate's BoxFuture without the explicit lifetime parameter. pub type BoxFuture = Pin + Send>>; @@ -62,7 +56,7 @@ fn init_tracing_subscriber() { .init(); } -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { dotenvy::dotenv().ok(); init_tracing_subscriber(); @@ -80,8 +74,6 @@ async fn main() { .parse() .expect("could not read port"); - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port); - let index = ManagedIndex::new(); { @@ -95,25 +87,29 @@ async fn main() { let mut engine = Engine::new(client.clone(), index); engine.set_metrics(metrics); - let make_svc = make_service_fn(move |_socket: &AddrStream| { - let engine = engine.clone(); - - async move { - let server = App::new(engine.clone()); - Ok::<_, hyper::Error>(service_fn(move |req| { - let server = server.clone(); - async move { - let path = req.uri().path().to_owned(); - - server - .handle(req) - .instrument(tracing::info_span!("@", %path)) - .await - } - })) - } - }); - let server = Server::bind(&addr).serve(make_svc); + let server = Server::build() + .bind("deps-rs", (Ipv4Addr::UNSPECIFIED, port), move || { + let engine = engine.clone(); + + let app = App::new(engine.clone()); + + HttpService::build() + .client_disconnect_timeout(Duration::from_secs(5)) + .client_request_timeout(Duration::from_secs(5)) + .finish(move |req: Request| { + let app = app.clone(); + let path = req.path().to_owned(); + + async move { + app.handle(req) + .instrument(tracing::info_span!("@", %path)) + .await + } + }) + .tcp_auto_h2c() + }) + .unwrap() + .run(); tracing::info!("Server running on port {port}"); diff --git a/src/parsers/manifest.rs b/src/parsers/manifest.rs index 64535fd..c461050 100644 --- a/src/parsers/manifest.rs +++ b/src/parsers/manifest.rs @@ -135,9 +135,8 @@ pub fn parse_manifest_toml(input: &str) -> Result { #[cfg(test)] mod tests { - use crate::models::crates::CrateManifest; - use super::*; + use crate::models::crates::CrateManifest; #[test] fn parse_workspace_without_members_declaration() { diff --git a/src/server/mod.rs b/src/server/mod.rs index de32d29..210613e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,11 +1,12 @@ use std::{env, sync::Arc, time::Instant}; -use badge::BadgeStyle; -use futures_util::future; -use hyper::{ +use actix_http::{ + body::MessageBody, header::{CACHE_CONTROL, CONTENT_TYPE, ETAG, LOCATION}, - Body, Error as HyperError, Method, Request, Response, StatusCode, + Method, Request, Response, StatusCode, }; +use badge::BadgeStyle; +use futures_util::future; use once_cell::sync::Lazy; use route_recognizer::{Params, Router}; use semver::VersionReq; @@ -17,10 +18,14 @@ mod views; use self::assets::{ STATIC_LINKS_JS_ETAG, STATIC_LINKS_JS_PATH, STATIC_STYLE_CSS_ETAG, STATIC_STYLE_CSS_PATH, }; -use crate::engine::{AnalyzeDependenciesOutcome, Engine}; -use crate::models::crates::{CrateName, CratePath}; -use crate::models::repo::RepoPath; -use crate::models::SubjectPath; +use crate::{ + engine::{AnalyzeDependenciesOutcome, Engine}, + models::{ + crates::{CrateName, CratePath}, + repo::RepoPath, + SubjectPath, + }, +}; #[derive(Debug, Clone, Copy, PartialEq)] enum StatusFormat { @@ -86,7 +91,10 @@ impl App { } } - pub async fn handle(&self, req: Request) -> Result, HyperError> { + pub async fn handle( + &self, + req: Request, + ) -> Result, actix_http::Error> { let start = Instant::now(); // allows `/path/` to also match `/path` @@ -94,33 +102,39 @@ impl App { let res = if let Ok(route_match) = self.router.recognize(normalized_path) { match (req.method(), route_match.handler()) { - (&Method::GET, Route::Index) => self.index(req, route_match.params().clone()).await, + (&Method::GET, Route::Index) => self + .index(req, route_match.params().clone()) + .await + .map(Response::map_into_boxed_body), - (&Method::GET, Route::RepoStatus(format)) => { - self.repo_status(req, route_match.params().clone(), *format) - .await - } + (&Method::GET, Route::RepoStatus(format)) => self + .repo_status(req, route_match.params().clone(), *format) + .await + .map(Response::map_into_boxed_body), - (&Method::GET, Route::CrateStatus(format)) => { - self.crate_status(req, route_match.params().clone(), *format) - .await - } + (&Method::GET, Route::CrateStatus(format)) => self + .crate_status(req, route_match.params().clone(), *format) + .await + .map(Response::map_into_boxed_body), - (&Method::GET, Route::LatestCrateBadge) => { - self.crate_status(req, route_match.params().clone(), StatusFormat::Svg) - .await - } + (&Method::GET, Route::LatestCrateBadge) => self + .crate_status(req, route_match.params().clone(), StatusFormat::Svg) + .await + .map(Response::map_into_boxed_body), - (&Method::GET, Route::CrateRedirect) => { - self.crate_redirect(req, route_match.params().clone()).await - } + (&Method::GET, Route::CrateRedirect) => self + .crate_redirect(req, route_match.params().clone()) + .await + .map(Response::map_into_boxed_body), - (&Method::GET, Route::Static(file)) => Ok(App::static_file(*file)), + (&Method::GET, Route::Static(file)) => { + Ok(App::static_file(*file).map_into_boxed_body()) + } - _ => Ok(not_found()), + _ => Ok(not_found().map_into_boxed_body()), } } else { - Ok(not_found()) + Ok(not_found().map_into_boxed_body()) }; let end = Instant::now(); @@ -141,9 +155,9 @@ impl App { impl App { async fn index( &self, - _req: Request, + _req: Request, _params: Params, - ) -> Result, HyperError> { + ) -> Result, actix_http::Error> { let engine = self.engine.clone(); let popular = @@ -155,20 +169,21 @@ impl App { let mut response = views::html::error::render("Could not retrieve popular items", ""); *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; - Ok(response) + + Ok(response.map_into_boxed_body()) } Ok((popular_repos, popular_crates)) => { - Ok(views::html::index::render(popular_repos, popular_crates)) + Ok(views::html::index::render(popular_repos, popular_crates).map_into_boxed_body()) } } } async fn repo_status( &self, - req: Request, + req: Request, params: Params, format: StatusFormat, - ) -> Result, HyperError> { + ) -> Result, actix_http::Error> { let server = self.clone(); let site = params.find("site").expect("route param 'site' not found"); @@ -187,7 +202,8 @@ impl App { "Please make sure to provide a valid repository path.", ); *response.status_mut() = StatusCode::BAD_REQUEST; - Ok(response) + + Ok(response.map_into_boxed_body()) } Ok(repo_path) => { @@ -205,7 +221,8 @@ impl App { SubjectPath::Repo(repo_path), extra_knobs, ); - Ok(response) + + Ok(response.map_into_boxed_body()) } Ok(analysis_outcome) => { let response = App::status_format_analysis( @@ -214,7 +231,8 @@ impl App { SubjectPath::Repo(repo_path), extra_knobs, ); - Ok(response) + + Ok(response.map_into_boxed_body()) } } } @@ -223,9 +241,9 @@ impl App { async fn crate_redirect( &self, - _req: Request, + _req: Request, params: Params, - ) -> Result, HyperError> { + ) -> Result, actix_http::Error> { let engine = self.engine.clone(); let name = params.find("name").expect("route param 'name' not found"); @@ -239,7 +257,8 @@ impl App { "Please make sure to provide a valid crate name.", ); *response.status_mut() = StatusCode::BAD_REQUEST; - Ok(response) + + Ok(response.map_into_boxed_body()) } Ok(crate_name) => { @@ -255,7 +274,8 @@ impl App { "Please make sure to provide a valid crate name.", ); *response.status_mut() = StatusCode::NOT_FOUND; - Ok(response) + + Ok(response.map_into_boxed_body()) } Ok(None) => { let mut response = views::html::error::render( @@ -263,7 +283,8 @@ impl App { "Please make sure to provide a valid crate name.", ); *response.status_mut() = StatusCode::NOT_FOUND; - Ok(response) + + Ok(response.map_into_boxed_body()) } Ok(Some(release)) => { let redirect_url = format!( @@ -273,13 +294,11 @@ impl App { release.version ); - let res = Response::builder() - .status(StatusCode::TEMPORARY_REDIRECT) - .header(LOCATION, redirect_url) - .body(Body::empty()) - .unwrap(); + let res = Response::build(StatusCode::TEMPORARY_REDIRECT) + .insert_header((LOCATION, redirect_url)) + .finish(); - Ok(res) + Ok(res.map_into_boxed_body()) } } } @@ -288,10 +307,10 @@ impl App { async fn crate_status( &self, - req: Request, + req: Request, params: Params, format: StatusFormat, - ) -> Result, HyperError> { + ) -> Result, actix_http::Error> { let server = self.clone(); let name = params.find("name").expect("route param 'name' not found"); @@ -307,7 +326,8 @@ impl App { "Please make sure to provide a valid crate name and version.", ); *response.status_mut() = StatusCode::BAD_REQUEST; - return Ok(response); + + return Ok(response.map_into_boxed_body()); } }; @@ -317,7 +337,7 @@ impl App { .await { Ok(Some(latest_rel)) => latest_rel.version.to_string(), - Ok(None) => return Ok(not_found()), + Ok(None) => return Ok(not_found().map_into_boxed_body()), Err(err) => { tracing::error!(%err); let mut response = views::html::error::render( @@ -325,7 +345,8 @@ impl App { "Please make sure to provide a valid crate name.", ); *response.status_mut() = StatusCode::NOT_FOUND; - return Ok(response); + + return Ok(response.map_into_boxed_body()); } } } @@ -342,8 +363,10 @@ impl App { "Please make sure to provide a valid crate name and version.", ); *response.status_mut() = StatusCode::BAD_REQUEST; - Ok(response) + + Ok(response.map_into_boxed_body()) } + Ok(crate_path) => { let analyze_result = server .engine @@ -359,7 +382,8 @@ impl App { SubjectPath::Crate(crate_path), badge_knobs, ); - Ok(response) + + Ok(response.map_into_boxed_body()) } Ok(analysis_outcome) => { let response = App::status_format_analysis( @@ -369,7 +393,7 @@ impl App { badge_knobs, ); - Ok(response) + Ok(response.map_into_boxed_body()) } } } @@ -381,38 +405,41 @@ impl App { format: StatusFormat, subject_path: SubjectPath, badge_knobs: ExtraConfig, - ) -> Response { + ) -> Response { match format { - StatusFormat::Svg => views::badge::response(analysis_outcome.as_ref(), badge_knobs), + StatusFormat::Svg => { + views::badge::response(analysis_outcome.as_ref(), badge_knobs).map_into_boxed_body() + } + StatusFormat::Html => { views::html::status::render(analysis_outcome, subject_path, badge_knobs) + .map_into_boxed_body() } } } - fn static_file(file: StaticFile) -> Response { + fn static_file(file: StaticFile) -> Response { match file { - StaticFile::StyleCss => Response::builder() - .header(CONTENT_TYPE, "text/css; charset=utf-8") - .header(ETAG, STATIC_STYLE_CSS_ETAG) - .header(CACHE_CONTROL, "public, max-age=365000000, immutable") - .body(Body::from(assets::STATIC_STYLE_CSS)) - .unwrap(), - StaticFile::FaviconPng => Response::builder() - .header(CONTENT_TYPE, "image/svg+xml") - .body(Body::from(assets::STATIC_FAVICON)) - .unwrap(), - StaticFile::LinksJs => Response::builder() - .header(CONTENT_TYPE, "text/javascript; charset=utf-8") - .header(ETAG, STATIC_LINKS_JS_ETAG) - .header(CACHE_CONTROL, "public, max-age=365000000, immutable") - .body(Body::from(assets::STATIC_LINKS_JS)) - .unwrap(), + StaticFile::StyleCss => Response::build(StatusCode::OK) + .insert_header((CONTENT_TYPE, "text/css; charset=utf-8")) + .insert_header((ETAG, STATIC_STYLE_CSS_ETAG)) + .insert_header((CACHE_CONTROL, "public, max-age=365000000, immutable")) + .body(assets::STATIC_STYLE_CSS), + + StaticFile::FaviconPng => Response::build(StatusCode::OK) + .insert_header((CONTENT_TYPE, "image/svg+xml")) + .body(assets::STATIC_FAVICON), + + StaticFile::LinksJs => Response::build(StatusCode::OK) + .insert_header((CONTENT_TYPE, "text/javascript; charset=utf-8")) + .insert_header((ETAG, STATIC_LINKS_JS_ETAG)) + .insert_header((CACHE_CONTROL, "public, max-age=365000000, immutable")) + .body(assets::STATIC_LINKS_JS), } } } -fn not_found() -> Response { +fn not_found() -> Response { views::html::error::render_404() } diff --git a/src/server/views/badge.rs b/src/server/views/badge.rs index 0ee55c5..6747f49 100644 --- a/src/server/views/badge.rs +++ b/src/server/views/badge.rs @@ -1,9 +1,7 @@ +use actix_http::{body::MessageBody, header::CONTENT_TYPE, Response, StatusCode}; use badge::{Badge, BadgeOptions}; -use hyper::header::CONTENT_TYPE; -use hyper::{Body, Response}; -use crate::engine::AnalyzeDependenciesOutcome; -use crate::server::ExtraConfig; +use crate::{engine::AnalyzeDependenciesOutcome, server::ExtraConfig}; pub fn badge( analysis_outcome: Option<&AnalyzeDependenciesOutcome>, @@ -75,11 +73,10 @@ pub fn badge( pub fn response( analysis_outcome: Option<&AnalyzeDependenciesOutcome>, badge_knobs: ExtraConfig, -) -> Response { +) -> Response { let badge = badge(analysis_outcome, badge_knobs).to_svg(); - Response::builder() - .header(CONTENT_TYPE, "image/svg+xml; charset=utf-8") - .body(Body::from(badge)) - .unwrap() + Response::build(StatusCode::OK) + .insert_header((CONTENT_TYPE, "image/svg+xml; charset=utf-8")) + .body(badge) } diff --git a/src/server/views/html/error.rs b/src/server/views/html/error.rs index f5802e2..fa7abfc 100644 --- a/src/server/views/html/error.rs +++ b/src/server/views/html/error.rs @@ -1,12 +1,13 @@ -use hyper::{ +use actix_http::{ + body::MessageBody, header::{CACHE_CONTROL, CONTENT_TYPE}, - Body, Response, StatusCode, + Response, StatusCode, }; use maud::html; use crate::server::assets::STATIC_STYLE_CSS_PATH; -pub fn render(title: &str, descr: &str) -> Response { +pub fn render(title: &str, descr: &str) -> Response { super::render_html( title, html! { @@ -26,7 +27,7 @@ pub fn render(title: &str, descr: &str) -> Response { ) } -pub fn render_404() -> Response { +pub fn render_404() -> Response { let rendered = html! { html { head { @@ -55,10 +56,8 @@ pub fn render_404() -> Response { } }; - Response::builder() - .status(StatusCode::NOT_FOUND) - .header(CONTENT_TYPE, "text/html; charset=utf-8") - .header(CACHE_CONTROL, "public, max-age=300, immutable") - .body(Body::from(rendered.0)) - .unwrap() + Response::build(StatusCode::NOT_FOUND) + .insert_header((CONTENT_TYPE, "text/html; charset=utf-8")) + .insert_header((CACHE_CONTROL, "public, max-age=300, immutable")) + .body(rendered.0) } diff --git a/src/server/views/html/index.rs b/src/server/views/html/index.rs index a1ccae0..960465e 100644 --- a/src/server/views/html/index.rs +++ b/src/server/views/html/index.rs @@ -1,10 +1,10 @@ -use hyper::{Body, Response}; +use actix_http::{body::MessageBody, Response}; use maud::{html, Markup}; -use crate::models::crates::CratePath; -use crate::models::repo::Repository; - -use crate::server::assets::STATIC_LINKS_JS_PATH; +use crate::{ + models::{crates::CratePath, repo::Repository}, + server::assets::STATIC_LINKS_JS_PATH, +}; fn link_forms() -> Markup { html! { @@ -161,7 +161,10 @@ fn popular_table(popular_repos: Vec, popular_crates: Vec) } } -pub fn render(popular_repos: Vec, popular_crates: Vec) -> Response { +pub fn render( + popular_repos: Vec, + popular_crates: Vec, +) -> Response { super::render_html( "Keep your dependencies up-to-date", html! { diff --git a/src/server/views/html/mod.rs b/src/server/views/html/mod.rs index 2f3457b..04383cd 100644 --- a/src/server/views/html/mod.rs +++ b/src/server/views/html/mod.rs @@ -1,17 +1,15 @@ use std::time::Duration; -use hyper::header::CONTENT_TYPE; -use hyper::{Body, Response}; +use actix_http::{body::MessageBody, header::CONTENT_TYPE, Response, StatusCode}; use maud::{html, Markup, Render, DOCTYPE}; pub mod error; pub mod index; pub mod status; -use crate::server::assets::STATIC_STYLE_CSS_PATH; -use crate::server::SELF_BASE_URL; +use crate::server::{assets::STATIC_STYLE_CSS_PATH, SELF_BASE_URL}; -fn render_html(title: &str, body: B) -> Response { +fn render_html(title: &str, body: B) -> Response { let rendered = html! { (DOCTYPE) html { @@ -28,10 +26,9 @@ fn render_html(title: &str, body: B) -> Response { } }; - Response::builder() - .header(CONTENT_TYPE, "text/html; charset=utf-8") - .body(Body::from(rendered.0)) - .unwrap() + Response::build(StatusCode::OK) + .insert_header((CONTENT_TYPE, "text/html; charset=utf-8")) + .body(rendered.0) } fn render_navbar() -> Markup { diff --git a/src/server/views/html/status.rs b/src/server/views/html/status.rs index 19c0d23..49a25cb 100644 --- a/src/server/views/html/status.rs +++ b/src/server/views/html/status.rs @@ -1,17 +1,20 @@ +use actix_http::{body::MessageBody, Response}; use font_awesome_as_a_crate::{svg as fa, Type as FaType}; -use hyper::{Body, Response}; use indexmap::IndexMap; use maud::{html, Markup, PreEscaped}; use pulldown_cmark::{html, Parser}; use rustsec::advisory::Advisory; use semver::Version; -use crate::engine::AnalyzeDependenciesOutcome; -use crate::models::crates::{AnalyzedDependencies, AnalyzedDependency, CrateName}; -use crate::models::repo::RepoSite; -use crate::models::SubjectPath; -use crate::server::views::badge; -use crate::server::ExtraConfig; +use crate::{ + engine::AnalyzeDependenciesOutcome, + models::{ + crates::{AnalyzedDependencies, AnalyzedDependency, CrateName}, + repo::RepoSite, + SubjectPath, + }, + server::{views::badge, ExtraConfig}, +}; fn get_crates_url(name: impl AsRef) -> String { format!("https://crates.io/crates/{}", name.as_ref()) @@ -454,7 +457,7 @@ pub fn render( analysis_outcome: Option, subject_path: SubjectPath, extra_config: ExtraConfig, -) -> Response { +) -> Response { let title = match subject_path { SubjectPath::Repo(ref repo_path) => { format!("{} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref()) diff --git a/src/utils/cache.rs b/src/utils/cache.rs index a09c706..cb59778 100644 --- a/src/utils/cache.rs +++ b/src/utils/cache.rs @@ -1,7 +1,7 @@ use std::{fmt, sync::Arc, time::Duration}; +use actix_service::Service; use derive_more::{Display, Error, From}; -use hyper::service::Service; use lru_time_cache::LruCache; use tokio::sync::Mutex; @@ -26,7 +26,7 @@ where fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Cache") .field("inner", &self.inner) - .finish() + .finish_non_exhaustive() } } @@ -65,7 +65,7 @@ where cache = "miss", ); - let mut service = self.inner.clone(); + let service = self.inner.clone(); let fresh = service.call(req.clone()).await?; { diff --git a/src/utils/index.rs b/src/utils/index.rs index 65f3469..641a0e8 100644 --- a/src/utils/index.rs +++ b/src/utils/index.rs @@ -1,13 +1,16 @@ -use std::sync::Arc; -use std::sync::Mutex; -use std::time::Duration; +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; -use crate::models::crates::CrateName; use anyhow::Result; -use crates_index::Crate; -use crates_index::GitIndex; -use tokio::task::spawn_blocking; -use tokio::time::{self, MissedTickBehavior}; +use crates_index::{Crate, GitIndex}; +use tokio::{ + task::spawn_blocking, + time::{self, MissedTickBehavior}, +}; + +use crate::models::crates::CrateName; #[derive(Clone)] pub struct ManagedIndex {