Skip to content

Commit

Permalink
fix: separate index by HOST, add test for deleting cache per path
Browse files Browse the repository at this point in the history
  • Loading branch information
Cyrix126 committed Jul 12, 2024
1 parent cf0f2ba commit 9368ad9
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 12 deletions.
18 changes: 15 additions & 3 deletions src/api/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ use std::str::FromStr;
use crate::index_cache::IndexCache;
use crate::AppState;
use aide::axum::IntoApiResponse;
use axum::extract::Path;
use axum::extract::{Path, Request};
use axum::http::uri::PathAndQuery;
use axum::http::StatusCode;
use axum::{extract::State, response::IntoResponse, Json};
use reqwest::header::HOST;
use reqwest::Method;
use serde::Serialize;
use tracing::{debug, warn};
Expand Down Expand Up @@ -45,15 +46,26 @@ pub async fn delete_entry_per_uuid(
warn!("deletion request for invalid uuid");
StatusCode::NOT_FOUND
}
// delete all entries for a given path, only for method GET
// delete all entries for a given path and HOST, only for method GET
pub async fn delete_entries_per_path(
Path(path): Path<String>,
State(state): State<AppState>,
request: Request,
) -> impl IntoApiResponse {
debug!("new request to delete a cache entry");
let path = ["/", &path].concat();
let mut index_cache = state.index_cache.lock().await;
let mut to_delete = vec![];
if let Some(vec) = index_cache.get(&(Method::GET, PathAndQuery::from_str(&path).unwrap())) {
let host = if let Some(host) = request.headers().get(HOST) {
host
} else {
return StatusCode::NOT_FOUND;
};
if let Some(vec) = index_cache.get(&(
Method::GET,
PathAndQuery::from_str(&path).unwrap(),
host.clone(),
)) {
for e in vec {
state.cache.invalidate(&e.0).await;
to_delete.push(e.0);
Expand Down
11 changes: 8 additions & 3 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub async fn handler(State(state): State<AppState>, request: Request) -> impl In

// if not in cache, make the request to backend service
let req_method = request.method().to_owned();
let req_host = request.headers().get(HOST).cloned();
let req_headers = request.headers().to_owned();
let req_uri = request
.uri()
Expand All @@ -52,7 +53,7 @@ pub async fn handler(State(state): State<AppState>, request: Request) -> impl In
.config
.lock()
.await
.to_backend_uri(&req_uri, request.headers().get(HOST));
.to_backend_uri(&req_uri, &req_host);
debug!("Request URI retrieved: {req_uri}");
debug!("Request URL transmitted:{url_backend}");
let req = state
Expand Down Expand Up @@ -88,11 +89,15 @@ pub async fn handler(State(state): State<AppState>, request: Request) -> impl In
);

spawn(enc!((uuid, axum_rep, index) async move {
debug!("adding the new response to the cache and indexing");
if let Some(host) = req_host {
// add entry to index cache
index.lock().await.add_entry(uuid, req_method, req_uri, req_headers_match_vary);
debug!("adding the new response to the cache and indexing");
index.lock().await.add_entry(uuid, req_method, req_uri, host, req_headers_match_vary);
// add response to cache
cache.insert(uuid, axum_rep).await;
} else {
warn!("request does not have a HOST header, not adding any entry to cache");
}

}));
debug!("serving new response with added header Etag");
Expand Down
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl Default for Config {
}

impl Config {
pub fn to_backend_uri(&self, uri_req: &PathAndQuery, host: Option<&HeaderValue>) -> Url {
pub fn to_backend_uri(&self, uri_req: &PathAndQuery, host: &Option<HeaderValue>) -> Url {
//todo use regex to get the start of the line
if let Some(host) = host {
if let Ok(host) = host.to_str() {
Expand Down
15 changes: 10 additions & 5 deletions src/index_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ use axum::http::uri::PathAndQuery;
use axum::http::HeaderValue;
use axum::http::{HeaderMap, Request};
use derive_more::{Deref, DerefMut};
use reqwest::header::HOST;
use reqwest::Method;
use uuid::Uuid;
#[derive(Deref, DerefMut, Clone)]
/// IndexCache will store entry for each combination of uri/method with a vec of uuid per HeaderMap. HeaderMap here are request headers that match the headers name in the Vary header value response.
pub struct IndexCache(pub HashMap<(axum::http::Method, PathAndQuery), Vec<(Uuid, HeaderMap)>>);
#[derive(Deref, DerefMut, Clone, Debug)]
/// IndexCache will store entry for each combination of method/uri/host with a vec of uuid per HeaderMap. HeaderMap here are request headers that match the headers name in the Vary header value response.
pub struct IndexCache(
pub HashMap<(axum::http::Method, PathAndQuery, HeaderValue), Vec<(Uuid, HeaderMap)>>,
);

impl IndexCache {
pub fn new() -> Self {
Expand All @@ -20,9 +23,10 @@ impl IndexCache {
uuid: Uuid,
req_method: Method,
req_uri: PathAndQuery,
req_host: HeaderValue,
req_headers_match_vary: HeaderMap,
) {
let key = (req_method, req_uri);
let key = (req_method, req_uri, req_host);
let value = (uuid, req_headers_match_vary);
// check if entry exist for method/uri

Expand All @@ -43,8 +47,9 @@ impl IndexCache {
.path_and_query()
.cloned()
.unwrap_or(PathAndQuery::from_static(""));
let host = request.headers().get(HOST)?;
let headermap = request.headers();
if let Some(uuids) = self.get(&(method, uri.clone())) {
if let Some(uuids) = self.get(&(method, uri.clone(), host.clone())) {
return uuids
.iter()
.find(|(_, headermap_object)| {
Expand Down
26 changes: 26 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,30 @@ mod test {
// response should only contains header not modified without the body
Ok(())
}
#[tokio::test]
async fn invalidate_cache_per_path() -> Result<()> {
// tracing_subscriber::fmt::init();
let app = app().await.unwrap();
// send get request for the first time
let rep = app
.get("/abc")
.add_header(HOST, HeaderValue::from_static("example.com"))
.await;
// wait for the cache to save the entry.
sleep(Duration::from_millis(100)).await;
// delete the entry per path
let etag = rep.headers().get(ETAG).unwrap();
let uri = "/api/1/cache/path/abc";
app.delete(uri)
.add_header(HOST, HeaderValue::from_static("example.com"))
.await
.assert_status_ok();
let uri_uuid = format!("/api/1/cache/{}", etag.to_str().unwrap());
app.get(&uri_uuid)
.add_header(HOST, HeaderValue::from_static("example.com"))
.await
.assert_status_not_found();

Ok(())
}
}

0 comments on commit 9368ad9

Please sign in to comment.