//! Response caching module //! //! Considerations: //! - check cache header //! - ignore responses that get too large //! - ignore requests with body (or too large body) //! - LRU cache pruning //! - different backends: //! - in memory (HashMap) //! - on disk (redb? filesystem?) //! - external db? use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; use crate::{config::DynNode, error::ServiceError}; use anyhow::Result; use bytes::Bytes; use http::Response; use http_body_util::BodyExt; use serde::Deserialize; use serde_yaml::Value; use sha2::{Digest, Sha256}; use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; pub struct CacheKind; #[derive(Deserialize)] struct CacheConfig { next: DynNode, } struct Cache { entries: HashMap<[u8; 32], Response>, config: CacheConfig, } impl NodeKind for CacheKind { fn name(&self) -> &'static str { "cache" } fn instanciate(&self, config: Value) -> Result> { let config = serde_yaml::from_value::(config)?; Ok(Arc::new(Cache { config, entries: HashMap::new(), })) } } impl Node for Cache { fn handle<'a>( &'a self, context: &'a mut NodeContext, request: NodeRequest, ) -> Pin> + Send + Sync + 'a>> { Box::pin(async move { // totally wrong... let mut hasher = Sha256::new(); hasher.update(request.method().as_str()); hasher.update(request.uri().path()); if let Some(q) = request.uri().query() { hasher.update(q); } let key: [u8; 32] = hasher.finalize().try_into().unwrap(); if let Some(resp) = self.entries.get(&key) { // Ok(resp.to_owned().map(|b| b.map_err(|e| match e {}).boxed())) } let response = self.config.next.handle(context, request).await?; Ok(response) }) } }