feat: add caching headers
This commit is contained in:
parent
fe820fa698
commit
6b865aaf5a
4 changed files with 95 additions and 18 deletions
44
Cargo.lock
generated
44
Cargo.lock
generated
|
@ -1506,6 +1506,15 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.84"
|
||||
|
@ -1715,9 +1724,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330"
|
||||
checksum = "27059f51958c5f8496a6f79511e7c0ac396dd815dc8894e9b6e2efb5779cf6f0"
|
||||
dependencies = [
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
|
@ -1725,12 +1734,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25"
|
||||
checksum = "e6132d64df104c0b3ea7a6ad7766a43f587bd773a4a9cf4cd59296d426afaf3a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
|
@ -2210,6 +2220,23 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
|
@ -2707,6 +2734,15 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
|
|
|
@ -33,7 +33,7 @@ serde_json = "1.0.117"
|
|||
thiserror = "1.0.61"
|
||||
tokio = { version = "1.37.0", features = ["macros", "fs", "rt-multi-thread"] }
|
||||
tokio-util = { version = "0.7.11", features = ["io"] }
|
||||
tower-http = { version = "0.5.2", features = ["trace"] }
|
||||
tower-http = { version = "0.5.2", features = ["trace", "set-header"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
url = "2.5.0"
|
||||
|
@ -44,7 +44,7 @@ yarte_helpers = "0.15.8"
|
|||
|
||||
[dev-dependencies]
|
||||
proptest = "1.4.0"
|
||||
rstest = { version = "0.19.0", default-features = false }
|
||||
rstest = { version = "0.20.0", default-features = false }
|
||||
|
||||
[workspace]
|
||||
members = [".", "crates/*"]
|
||||
|
|
35
src/app.rs
35
src/app.rs
|
@ -7,7 +7,7 @@ use axum::{
|
|||
http::{Response, Uri},
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{any, get, post},
|
||||
Form, Json, Router,
|
||||
Form, Router,
|
||||
};
|
||||
use headers::HeaderMapExt;
|
||||
use http::{HeaderMap, StatusCode};
|
||||
|
@ -20,11 +20,14 @@ use tokio_util::{
|
|||
compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt},
|
||||
io::ReaderStream,
|
||||
};
|
||||
use tower_http::trace::{DefaultOnResponse, TraceLayer};
|
||||
use tower_http::{
|
||||
set_header::SetResponseHeaderLayer,
|
||||
trace::{DefaultOnResponse, TraceLayer},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
artifact_api::{Artifact, ArtifactApi},
|
||||
cache::{Cache, CacheEntry, GetFileResult, GetFileResultFile, IndexEntry},
|
||||
artifact_api::ArtifactApi,
|
||||
cache::{Cache, CacheEntry, GetFileResult, GetFileResultFile},
|
||||
config::Config,
|
||||
error::Error,
|
||||
gzip_reader::{PrecompressedGzipReader, GZIP_EXTRA_LEN},
|
||||
|
@ -98,7 +101,8 @@ impl App {
|
|||
tracing::error_span!("request", url = util::full_url_from_request(request), ip)
|
||||
})
|
||||
.on_response(DefaultOnResponse::new().level(tracing::Level::INFO)),
|
||||
);
|
||||
)
|
||||
.layer(SetResponseHeaderLayer::appending(http::header::X_CONTENT_TYPE_OPTIONS, http::HeaderValue::from_static("nosniff")));
|
||||
axum::serve(
|
||||
listener,
|
||||
router.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
|
@ -122,6 +126,7 @@ impl App {
|
|||
}
|
||||
Ok(Response::builder()
|
||||
.typed_header(headers::ContentType::html())
|
||||
.cache()
|
||||
.body(templates::Index::default().to_string().into())?)
|
||||
} else {
|
||||
let query = Query::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?;
|
||||
|
@ -185,6 +190,7 @@ impl App {
|
|||
|
||||
Ok(Response::builder()
|
||||
.typed_header(headers::ContentType::html())
|
||||
.cache_immutable()
|
||||
.body(tmpl.to_string().into())?)
|
||||
}
|
||||
}
|
||||
|
@ -214,6 +220,7 @@ impl App {
|
|||
};
|
||||
Ok(Response::builder()
|
||||
.typed_header(headers::ContentType::html())
|
||||
.cache()
|
||||
.body(tmpl.to_string().into())?)
|
||||
}
|
||||
}
|
||||
|
@ -267,7 +274,8 @@ impl App {
|
|||
let mut resp = Response::builder()
|
||||
.status(res.status)
|
||||
.typed_header(headers::AcceptRanges::bytes())
|
||||
.typed_header(headers::LastModified::from(entry.last_modified));
|
||||
.typed_header(headers::LastModified::from(entry.last_modified))
|
||||
.cache_immutable();
|
||||
if let Some(mime) = res.mime {
|
||||
resp = resp.typed_header(headers::ContentType::from(mime));
|
||||
}
|
||||
|
@ -366,24 +374,24 @@ impl App {
|
|||
async fn get_artifacts(
|
||||
State(state): State<AppState>,
|
||||
Host(host): Host,
|
||||
) -> Result<Json<Vec<Artifact>>, ErrorJson> {
|
||||
) -> Result<Response<Body>, ErrorJson> {
|
||||
let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?;
|
||||
let query = Query::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?;
|
||||
state.i.cfg.check_filterlist(&query)?;
|
||||
let artifacts = state.i.api.list(&query.into_runquery()).await?;
|
||||
Ok(Json(artifacts))
|
||||
Ok(Response::builder().cache().json(&artifacts)?)
|
||||
}
|
||||
|
||||
/// API endpoint to get the metadata of the current artifact
|
||||
async fn get_artifact(
|
||||
State(state): State<AppState>,
|
||||
Host(host): Host,
|
||||
) -> Result<Json<Artifact>, ErrorJson> {
|
||||
) -> Result<Response<Body>, ErrorJson> {
|
||||
let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?;
|
||||
let query = Query::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?;
|
||||
state.i.cfg.check_filterlist(&query)?;
|
||||
let artifact = state.i.api.fetch(&query.try_into_artifactquery()?).await?;
|
||||
Ok(Json(artifact))
|
||||
Ok(Response::builder().cache().json(&artifact)?)
|
||||
}
|
||||
|
||||
/// API endpoint to get a file listing
|
||||
|
@ -391,7 +399,7 @@ impl App {
|
|||
State(state): State<AppState>,
|
||||
Host(host): Host,
|
||||
request: Request,
|
||||
) -> Result<Json<Vec<IndexEntry>>, ErrorJson> {
|
||||
) -> Result<Response<Body>, ErrorJson> {
|
||||
let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?;
|
||||
let ip = util::get_ip_address(&request, state.i.cfg.load().real_ip_header.as_deref())?;
|
||||
let query = Query::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?;
|
||||
|
@ -405,7 +413,10 @@ impl App {
|
|||
state.garbage_collect();
|
||||
}
|
||||
let files = entry_res.entry.get_files();
|
||||
Ok(Json(files))
|
||||
Ok(Response::builder()
|
||||
.typed_header(headers::LastModified::from(entry_res.entry.last_modified))
|
||||
.cache_immutable()
|
||||
.json(&files)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
30
src/util.rs
30
src/util.rs
|
@ -23,6 +23,9 @@ use crate::error::{Error, Result};
|
|||
pub trait ResponseBuilderExt {
|
||||
/// Inserts a typed header to this response.
|
||||
fn typed_header<T: Header>(self, header: T) -> Self;
|
||||
fn cache_none(self) -> Self;
|
||||
fn cache(self) -> Self;
|
||||
fn cache_immutable(self) -> Self;
|
||||
/// Consumes this builder, using the provided json-serializable `val` to return a constructed [`Response`]
|
||||
fn json<T: Serialize>(self, val: &T) -> core::result::Result<Response, http::Error>;
|
||||
}
|
||||
|
@ -35,6 +38,27 @@ impl ResponseBuilderExt for axum::http::response::Builder {
|
|||
self
|
||||
}
|
||||
|
||||
fn cache_none(self) -> Self {
|
||||
self.header(
|
||||
http::header::CACHE_CONTROL,
|
||||
http::HeaderValue::from_static("no-cache"),
|
||||
)
|
||||
}
|
||||
|
||||
fn cache(self) -> Self {
|
||||
self.header(
|
||||
http::header::CACHE_CONTROL,
|
||||
http::HeaderValue::from_static("max-age=1800,public"),
|
||||
)
|
||||
}
|
||||
|
||||
fn cache_immutable(self) -> Self {
|
||||
self.header(
|
||||
http::header::CACHE_CONTROL,
|
||||
http::HeaderValue::from_static("max-age=31536000,public,immutable"),
|
||||
)
|
||||
}
|
||||
|
||||
fn json<T: Serialize>(self, val: &T) -> core::result::Result<Response, http::Error> {
|
||||
// copied from axum::json::into_response
|
||||
// Use a small initial capacity of 128 bytes like serde_json::to_vec
|
||||
|
@ -240,6 +264,12 @@ impl From<Error> for ErrorJson {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<http::Error> for ErrorJson {
|
||||
fn from(value: http::Error) -> Self {
|
||||
Self::from(Error::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for ErrorJson {
|
||||
fn into_response(self) -> Response {
|
||||
Response::builder().json(&self).unwrap()
|
||||
|
|
Loading…
Add table
Reference in a new issue