diff --git a/Cargo.lock b/Cargo.lock
index 6f1c918..15a8c13 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,10 +3,25 @@
version = 3
[[package]]
-name = "anyhow"
-version = "1.0.58"
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
+checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2"
+dependencies = [
+ "alloc-no-stdlib",
+]
[[package]]
name = "autocfg"
@@ -20,6 +35,27 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+[[package]]
+name = "brotli"
+version = "3.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
[[package]]
name = "bumpalo"
version = "3.10.0"
@@ -44,6 +80,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "encoding_rs"
version = "0.8.31"
@@ -53,6 +98,16 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "flate2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -294,6 +349,15 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+[[package]]
+name = "miniz_oxide"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
+dependencies = [
+ "adler",
+]
+
[[package]]
name = "mio"
version = "0.8.4"
@@ -527,16 +591,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
-name = "statue_rs"
+name = "statue"
version = "0.1.0"
dependencies = [
- "anyhow",
+ "brotli",
+ "flate2",
"hyper",
"num_cpus",
"reqwest",
"serde",
"serde_json",
"tokio",
+ "tower",
]
[[package]]
@@ -621,6 +687,23 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62"
+
[[package]]
name = "tower-service"
version = "0.3.2"
@@ -634,6 +717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
dependencies = [
"cfg-if",
+ "log",
"pin-project-lite",
"tracing-core",
]
diff --git a/Cargo.toml b/Cargo.toml
index a56ad7a..96fb1a6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,15 +1,17 @@
[package]
-name = "statue_rs"
+name = "statue"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-anyhow = "1.0.58"
+brotli = "3.3.4"
+flate2 = "1.0.24"
hyper = { version = "0.14.20", default-features = false, features = ["http1", "http2", "server", "tcp", "client"] }
num_cpus = "1.13.1"
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls"] }
serde = { version = "1.0.140", features = ["derive"] }
serde_json = "1.0.82"
tokio = { version = "1.20.1", features = ["rt", "macros", "rt-multi-thread", "fs", "process"] }
+tower = "0.4.13"
diff --git a/src/main.rs b/src/main.rs
index 791b58f..061924e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,16 +1,19 @@
mod api;
+mod util;
-use std::{collections::HashMap, net::ToSocketAddrs};
+use std::{collections::BTreeMap, net::ToSocketAddrs};
+use flate2::Compression;
use hyper::{
header::HeaderValue,
service::{make_service_fn, service_fn},
Body, Request as Request_, Response as Response_,
};
use serde::{Deserialize, Serialize};
+use tower::ServiceBuilder;
type Request = Request_
;
-type Response = Response_;
+type Response = Response_;
pub type Result = core::result::Result;
@@ -27,16 +30,16 @@ struct ConfigSchema {
listen_addr: Option,
title: Option,
description: Option,
- hosts: HashMap,
- services: HashMap,
+ hosts: BTreeMap,
+ services: BTreeMap,
}
pub struct Config {
listen_addr: String,
title: String,
description: String,
- hosts: HashMap,
- services: HashMap,
+ hosts: BTreeMap,
+ services: BTreeMap,
}
fn not_found() -> Result {
@@ -56,9 +59,8 @@ fn internal_server_error() -> Result {
async fn index() -> Result {
let config = read_config().await?;
- let mut resp = Response::new(
- format!(
- r###"
+ let mut resp = Response::new(format!(
+ r###"
@@ -79,13 +81,11 @@ async fn index() -> Result {
"###,
- title = config.title,
- description = config.description,
- style = include_str!("../frontend/style.css"),
- script = include_str!(concat!(env!("OUT_DIR"), "/frontend.js")),
- )
- .into(),
- );
+ title = config.title,
+ description = config.description,
+ style = include_str!("../frontend/style.css"),
+ script = include_str!(concat!(env!("OUT_DIR"), "/frontend.js")),
+ ));
resp.headers_mut().insert(
"content-type",
HeaderValue::from_static("text/html; charset=utf-8"),
@@ -97,7 +97,7 @@ async fn index() -> Result {
fn json(v: &T) -> Result {
let json = serde_json::to_string(&v)?;
- let mut resp = Response::new(json.into());
+ let mut resp = Response::new(json);
resp.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
@@ -157,19 +157,35 @@ pub async fn read_config() -> Result {
async fn serve() -> Result<()> {
let config = read_config().await?;
let addr = config.listen_addr.to_socket_addrs()?.next().unwrap();
- let make_service = make_service_fn(|_conn| async {
- Ok::<_, std::io::Error>(service_fn(|req| async {
- let resp = route(req).await;
+ let service = service_fn(|req| async {
+ let resp = route(req).await;
- match resp {
- Ok(r) => Ok(r),
- Err(e) => {
- eprintln!("{}", e);
+ match resp {
+ Ok(r) => Ok(r),
+ Err(e) => {
+ eprintln!("{}", e);
- internal_server_error()
- }
+ internal_server_error()
}
- }))
+ }
+ });
+ let make_service = make_service_fn(|_conn| async move {
+ let service = ServiceBuilder::new()
+ .layer(util::Log)
+ .layer(util::Compress {
+ algorithms: util::AlgorithmPreferences {
+ brotli: Some((3, util::brotli_default_params(4, true))),
+ gzip: Some((2, Compression::fast())),
+ deflate: Some((1, Compression::fast())),
+ },
+ predicate: util::CompressPredicate {
+ min_size: 256,
+ content_type_predicate: |_: &str| true,
+ },
+ })
+ .service(service);
+
+ Ok::<_, std::convert::Infallible>(service)
});
let server = hyper::server::Server::bind(&addr).serve(make_service);
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..9414537
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,669 @@
+use std::{
+ cmp::Ordering,
+ future::Future,
+ pin::Pin,
+ task::{Context, Poll},
+ time::Instant,
+};
+
+use brotli::{enc::BrotliEncoderParams, CompressorWriter};
+use flate2::{
+ write::{DeflateEncoder, GzEncoder},
+ Compression,
+};
+use hyper::{
+ body::{Buf, Bytes, HttpBody},
+ header::HeaderValue,
+ Method, Request, Response, Uri,
+};
+use tower::{Layer, Service};
+
+#[derive(Clone)]
+pub struct Log;
+
+impl Layer for Log {
+ type Service = LogService;
+
+ fn layer(&self, inner: S) -> Self::Service {
+ LogService::new(inner)
+ }
+}
+
+#[derive(Clone)]
+pub struct LogService {
+ inner: S,
+}
+
+impl LogService {
+ fn new(inner: S) -> Self {
+ Self { inner }
+ }
+}
+
+impl Service> for LogService
+where
+ S: Service, Response = Response>,
+{
+ type Response = S::Response;
+
+ type Error = S::Error;
+
+ type Future = LogServiceFuture;
+
+ fn poll_ready(
+ &mut self,
+ cx: &mut Context<'_>,
+ ) -> Poll> {
+ self.inner.poll_ready(cx)
+ }
+
+ fn call(&mut self, req: Request) -> Self::Future {
+ let uri = req.uri().clone();
+ let method = req.method().clone();
+
+ let fut = self.inner.call(req);
+
+ LogServiceFuture {
+ inner: fut,
+ uri,
+ method,
+ start: None,
+ }
+ }
+}
+
+pub struct LogServiceFuture {
+ uri: Uri,
+ method: Method,
+ inner: InnerFut,
+ start: Option,
+}
+
+impl Future for LogServiceFuture
+where
+ InnerFut: Future