server: Set a custom header if the cache is public
This can be used as a signal to improve caching. Only done for the Nix Binary Cache APIs for now.
This commit is contained in:
parent
6c4d04da74
commit
05a5e9cca8
7
attic/src/api/binary_cache.rs
Normal file
7
attic/src/api/binary_cache.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
//! Nix Binary Cache server.
|
||||||
|
//!
|
||||||
|
//! This module contains Attic-specific extensions to the
|
||||||
|
//! Nix Binary Cache API.
|
||||||
|
|
||||||
|
/// Header indicating a cache's visibility.
|
||||||
|
pub const ATTIC_CACHE_VISIBILITY: &str = "X-Attic-Cache-Visibility";
|
|
@ -1 +1,2 @@
|
||||||
|
pub mod binary_cache;
|
||||||
pub mod v1;
|
pub mod v1;
|
||||||
|
|
|
@ -85,6 +85,8 @@ async fn get_nix_cache_info(
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
req_state.set_public_cache(cache.is_public);
|
||||||
|
|
||||||
let info = NixCacheInfo {
|
let info = NixCacheInfo {
|
||||||
want_mass_query: true,
|
want_mass_query: true,
|
||||||
store_dir: cache.store_dir.into(),
|
store_dir: cache.store_dir.into(),
|
||||||
|
@ -137,6 +139,8 @@ async fn get_store_path_info(
|
||||||
.get_permission_for_cache(&cache_name, cache.is_public);
|
.get_permission_for_cache(&cache_name, cache.is_public);
|
||||||
permission.require_pull()?;
|
permission.require_pull()?;
|
||||||
|
|
||||||
|
req_state.set_public_cache(cache.is_public);
|
||||||
|
|
||||||
let mut narinfo = object.to_nar_info(&nar)?;
|
let mut narinfo = object.to_nar_info(&nar)?;
|
||||||
|
|
||||||
if narinfo.signature().is_none() {
|
if narinfo.signature().is_none() {
|
||||||
|
@ -189,6 +193,8 @@ async fn get_nar(
|
||||||
.get_permission_for_cache(&cache_name, cache.is_public);
|
.get_permission_for_cache(&cache_name, cache.is_public);
|
||||||
permission.require_pull()?;
|
permission.require_pull()?;
|
||||||
|
|
||||||
|
req_state.set_public_cache(cache.is_public);
|
||||||
|
|
||||||
database.bump_object_last_accessed(object.id).await?;
|
database.bump_object_last_accessed(object.id).await?;
|
||||||
|
|
||||||
let remote_file = nar.remote_file.0;
|
let remote_file = nar.remote_file.0;
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub mod oobe;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ use attic::cache::CacheName;
|
||||||
use config::{Config, StorageConfig};
|
use config::{Config, StorageConfig};
|
||||||
use database::migration::{Migrator, MigratorTrait};
|
use database::migration::{Migrator, MigratorTrait};
|
||||||
use error::{ErrorKind, ServerError, ServerResult};
|
use error::{ErrorKind, ServerError, ServerResult};
|
||||||
use middleware::{init_request_state, restrict_host};
|
use middleware::{init_request_state, restrict_host, set_visibility_header};
|
||||||
use storage::{LocalBackend, S3Backend, StorageBackend};
|
use storage::{LocalBackend, S3Backend, StorageBackend};
|
||||||
|
|
||||||
type State = Arc<StateInner>;
|
type State = Arc<StateInner>;
|
||||||
|
@ -79,6 +80,12 @@ struct RequestStateInner {
|
||||||
|
|
||||||
/// Whether the client claims the connection is HTTPS or not.
|
/// Whether the client claims the connection is HTTPS or not.
|
||||||
client_claims_https: bool,
|
client_claims_https: bool,
|
||||||
|
|
||||||
|
/// Whether the cache the client's interacting with is public.
|
||||||
|
///
|
||||||
|
/// This is purely informational and used to add the `X-Attic-Cache-Visibility`.
|
||||||
|
/// header in responses.
|
||||||
|
public_cache: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateInner {
|
impl StateInner {
|
||||||
|
@ -167,6 +174,11 @@ impl RequestStateInner {
|
||||||
fn substituter_endpoint(&self, cache: CacheName) -> ServerResult<String> {
|
fn substituter_endpoint(&self, cache: CacheName) -> ServerResult<String> {
|
||||||
Ok(format!("{}{}", self.api_endpoint()?, cache.as_str()))
|
Ok(format!("{}{}", self.api_endpoint()?, cache.as_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates whether the cache the client is interacting with is public.
|
||||||
|
fn set_public_cache(&self, public: bool) {
|
||||||
|
self.public_cache.store(public, Ordering::Relaxed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fallback route.
|
/// The fallback route.
|
||||||
|
@ -192,6 +204,7 @@ pub async fn run_api_server(cli_listen: Option<SocketAddr>, config: Config) -> R
|
||||||
.fallback(fallback)
|
.fallback(fallback)
|
||||||
// middlewares
|
// middlewares
|
||||||
.layer(axum::middleware::from_fn(apply_auth))
|
.layer(axum::middleware::from_fn(apply_auth))
|
||||||
|
.layer(axum::middleware::from_fn(set_visibility_header))
|
||||||
.layer(axum::middleware::from_fn(init_request_state))
|
.layer(axum::middleware::from_fn(init_request_state))
|
||||||
.layer(axum::middleware::from_fn(restrict_host))
|
.layer(axum::middleware::from_fn(restrict_host))
|
||||||
.layer(Extension(state.clone()))
|
.layer(Extension(state.clone()))
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Extension, Host},
|
extract::{Extension, Host},
|
||||||
http::Request,
|
http::{HeaderValue, Request},
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::Response,
|
response::Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{AuthState, RequestStateInner, State};
|
use super::{AuthState, RequestState, RequestStateInner, State};
|
||||||
use crate::error::{ErrorKind, ServerResult};
|
use crate::error::{ErrorKind, ServerResult};
|
||||||
|
use attic::api::binary_cache::ATTIC_CACHE_VISIBILITY;
|
||||||
|
|
||||||
/// Initializes per-request state.
|
/// Initializes per-request state.
|
||||||
pub async fn init_request_state<B>(
|
pub async fn init_request_state<B>(
|
||||||
|
@ -31,6 +33,7 @@ pub async fn init_request_state<B>(
|
||||||
api_endpoint: state.config.api_endpoint.to_owned(),
|
api_endpoint: state.config.api_endpoint.to_owned(),
|
||||||
host,
|
host,
|
||||||
client_claims_https,
|
client_claims_https,
|
||||||
|
public_cache: AtomicBool::new(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
req.extensions_mut().insert(req_state);
|
req.extensions_mut().insert(req_state);
|
||||||
|
@ -55,3 +58,20 @@ pub async fn restrict_host<B>(
|
||||||
|
|
||||||
Ok(next.run(req).await)
|
Ok(next.run(req).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the `X-Attic-Cache-Visibility` header in responses.
|
||||||
|
pub(crate) async fn set_visibility_header<B>(
|
||||||
|
Extension(req_state): Extension<RequestState>,
|
||||||
|
req: Request<B>,
|
||||||
|
next: Next<B>,
|
||||||
|
) -> ServerResult<Response> {
|
||||||
|
let mut response = next.run(req).await;
|
||||||
|
|
||||||
|
if req_state.public_cache.load(Ordering::Relaxed) {
|
||||||
|
response
|
||||||
|
.headers_mut()
|
||||||
|
.append(ATTIC_CACHE_VISIBILITY, HeaderValue::from_static("public"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue