Skip to content

Commit

Permalink
Add HTTP cache headers to always retrieve an updated version
Browse files Browse the repository at this point in the history
  • Loading branch information
AitorAstorga committed Feb 7, 2025
1 parent c9980f2 commit b1100dc
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 29 deletions.
53 changes: 24 additions & 29 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,22 @@ mod models;
mod persistent_counter;
mod svg_generator;

use models::{CounterResponse, CounterSetRequest, SvgOptions};
use std::io::Cursor;

use models::{ApiKey, CounterResponse, CounterSetRequest, SvgOptions, SvgResponse};
use persistent_counter::PersistentCounterMap;
use rocket::http::ContentType;

use rocket::http::{ContentType, Status};
use rocket::serde::json::Json;
use rocket::State;
use rocket::{Response, State};

use svg_generator::build_custom_css;

// Optionally load environment variables from .env.
fn init_env() {
dotenv::dotenv().ok();
}

/// Verify that the "x-api-key" header matches the API_KEY environment variable.
pub struct ApiKey(String);

#[rocket::async_trait]
impl<'r> rocket::request::FromRequest<'r> for ApiKey {
type Error = ();

async fn from_request(req: &'r rocket::request::Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
let api_key = req.headers().get_one("x-api-key");
if let Some(api_key) = api_key {
if api_key == std::env::var("API_KEY").expect("API_KEY must be set") {
return rocket::request::Outcome::Success(ApiKey(api_key.to_string()));
}
}
rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, ()))
}
}

/// GET endpoint to return a counter as JSON (without incrementing)
#[get("/api/counter/<name>")]
async fn get_counter_json(name: &str, counters: &State<PersistentCounterMap>) -> Json<CounterResponse> {
Expand Down Expand Up @@ -79,32 +65,41 @@ async fn svg_counter(
name: &str,
options: Option<SvgOptions>,
counters: &State<PersistentCounterMap>,
) -> (ContentType, String) {
) -> Result<SvgResponse, Status> {
// Load the base CSS from assets/style.css.
let base_css = include_str!("../assets/style.css");

// Build custom CSS if parameters are provided.
let custom_css = build_custom_css(options.clone());

// Combine the base CSS with the custom CSS.
let css = format!("{}\n{}", base_css, custom_css);

let label = options
.as_ref()
.and_then(|opts| opts.label.clone())
.unwrap_or_else(|| "Visits".to_string());

// Increment the counter
let count = counters.increment(name);

// Get width and height
let width = options.clone().unwrap_or_default().width.unwrap_or(150);
let height = options.clone().unwrap_or_default().height.unwrap_or(20);

// Generate the SVG
let svg = svg_generator::generate_svg(&label, count, &css, width, height);

(ContentType::new("image", "svg+xml"), svg)

// Build a response with caching headers not to store the response
let response = Response::build()
.header(ContentType::new("image", "svg+xml"))
.raw_header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
.raw_header("Pragma", "no-cache")
.raw_header("Expires", "0")
.sized_body(svg.len(), Cursor::new(svg))
.finalize();

Ok(SvgResponse(response))
}

#[launch]
Expand Down
30 changes: 30 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use rocket::serde::{Deserialize, Serialize};
use rocket::form::FromForm;
use rocket::{Request, Response};
use rocket::response::{Responder, Result as RocketResult};

/// JSON response structure for counter endpoints.
#[derive(Serialize)]
Expand Down Expand Up @@ -46,3 +48,31 @@ pub struct SvgOptions {
pub label_color: Option<String>,
pub counter_color: Option<String>,
}

pub struct SvgResponse(
pub Response<'static>
);

impl<'r> Responder<'r, 'static> for SvgResponse {
fn respond_to(self, _req: &'r Request<'_>) -> RocketResult<'static> {
Ok(self.0)
}
}

/// Verify that the "x-api-key" header matches the API_KEY environment variable.
pub struct ApiKey(String);

#[rocket::async_trait]
impl<'r> rocket::request::FromRequest<'r> for ApiKey {
type Error = ();

async fn from_request(req: &'r rocket::request::Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
let api_key = req.headers().get_one("x-api-key");
if let Some(api_key) = api_key {
if api_key == std::env::var("API_KEY").expect("API_KEY must be set") {
return rocket::request::Outcome::Success(ApiKey(api_key.to_string()));
}
}
rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, ()))
}
}

0 comments on commit b1100dc

Please sign in to comment.