add cache busting filenames and split off assets
i abstraced the file handling out to a global static struct called "Files" that is only written to once, in main inside a OnceLock. i also split out asset handling into its own module folder called assets which has all the asset handling code. i also have a new crate called "proc_macros" which provides a attribute macro that adds the base_template field to each struct i decorate with it using the syn and quote crates.
This commit is contained in:
parent
1654821763
commit
53193d84b8
|
@ -614,6 +614,12 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.9"
|
||||
|
@ -844,6 +850,12 @@ dependencies = [
|
|||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
|
@ -856,8 +868,11 @@ version = "0.2.0"
|
|||
dependencies = [
|
||||
"askama",
|
||||
"askama_rocket",
|
||||
"hex",
|
||||
"log",
|
||||
"md5",
|
||||
"notify",
|
||||
"proc_macros",
|
||||
"rocket",
|
||||
"rocket_cors",
|
||||
"rust-embed",
|
||||
|
@ -1096,6 +1111,14 @@ dependencies = [
|
|||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.28"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = ["cli", "shared"]
|
||||
members = ["cli", "shared", "proc-macros"]
|
||||
|
||||
[package]
|
||||
name = "meowy-webring"
|
||||
|
@ -15,11 +15,13 @@ lto = "thin"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4"
|
||||
log = "0.4"
|
||||
md5 = "0.7"
|
||||
|
||||
[dependencies.rocket]
|
||||
version = "=0.5.0-rc.3"
|
||||
default_features = false
|
||||
default-features = false
|
||||
features = ["json"]
|
||||
|
||||
[dependencies.rust-embed]
|
||||
|
@ -60,3 +62,6 @@ features = ["macos_fsevent"]
|
|||
[dependencies.rocket_cors]
|
||||
version = "=0.6.0-alpha2"
|
||||
default_features = false
|
||||
|
||||
[dependencies.proc_macros]
|
||||
path = "./proc-macros"
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "proc_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
syn = "2.0"
|
||||
quote = "1.0"
|
|
@ -0,0 +1,35 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse::Parser, parse_macro_input, DeriveInput};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn uses_base_template(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = parse_macro_input!(item as DeriveInput);
|
||||
|
||||
let base_template_field = syn::Field::parse_named
|
||||
.parse2(
|
||||
quote! {
|
||||
pub base_template: BaseTemplate
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
match &mut input.data {
|
||||
syn::Data::Struct(ref mut struct_data) => {
|
||||
match &mut struct_data.fields {
|
||||
syn::Fields::Named(fields) => {
|
||||
fields.named.push(base_template_field);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
return quote! {
|
||||
#input
|
||||
}
|
||||
.into();
|
||||
}
|
||||
_ => {
|
||||
panic!("bad")
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
use askama_rocket::Template;
|
||||
use rocket::{http::Status, response::content::RawCss};
|
||||
use rust_embed::{EmbeddedFile, RustEmbed};
|
||||
use shared::names::Site;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "public/"]
|
||||
pub struct PublicAssets;
|
||||
|
||||
impl PublicAssets {
|
||||
fn get_asset(file_path: &str) -> Result<EmbeddedFile, Status> {
|
||||
match PublicAssets::get(file_path) {
|
||||
Some(asset) => Ok(asset),
|
||||
None => Err(Status::NotFound),
|
||||
}
|
||||
}
|
||||
fn get_string(file_path: &str) -> Result<String, Status> {
|
||||
let asset = PublicAssets::get_asset(file_path)?;
|
||||
match std::str::from_utf8(&asset.data) {
|
||||
Ok(string) => Ok(string.to_string()),
|
||||
Err(_) => Err(Status::InternalServerError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(status = 200, content_type = "font/woff2")]
|
||||
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(status = 200, content_type = "font/woff")]
|
||||
pub struct RawWoffFont(pub Cow<'static, [u8]>);
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "error.html")]
|
||||
pub struct ErrorTemplate<'a> {
|
||||
pub error: &'a str,
|
||||
pub error_description: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
pub struct IndexTemplate {
|
||||
pub sites: Vec<Site>,
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
pub struct ErrorTemplateResponder<'a> {
|
||||
template: ErrorTemplate<'a>,
|
||||
}
|
||||
|
||||
#[get("/css/<style>")]
|
||||
pub fn style(style: &str) -> Result<rocket::response::content::RawCss<String>, Status> {
|
||||
let style_name = "style.css";
|
||||
let hyperlegible_name = "hyperlegible.css";
|
||||
|
||||
if style == style_name {
|
||||
let string = PublicAssets::get_string(style_name)?;
|
||||
Ok(RawCss::<String>(string))
|
||||
} else if style == hyperlegible_name {
|
||||
let string = PublicAssets::get_string(hyperlegible_name)?;
|
||||
Ok(RawCss::<String>(string))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/woff2/<font>")]
|
||||
pub fn woff2_font(font: &str) -> Result<RawWoff2Font, Status> {
|
||||
let latin = "atkinson-hyperlegible-latin-400-normal.woff2";
|
||||
let latin_ext = "atkinson-hyperlegible-latin-ext-400-normal.woff2";
|
||||
|
||||
if font == latin {
|
||||
Ok(RawWoff2Font(PublicAssets::get_asset(latin)?.data))
|
||||
} else if font == latin_ext {
|
||||
Ok(RawWoff2Font(PublicAssets::get_asset(latin_ext)?.data))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/woff/<font>")]
|
||||
pub fn woff_font(font: &str) -> Result<RawWoffFont, Status> {
|
||||
let all = "atkinson-hyperlegible-all-400-normal.woff";
|
||||
|
||||
if font == all {
|
||||
Ok(RawWoffFont(PublicAssets::get_asset(all)?.data))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
use rocket::http::Status;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::{borrow::Cow, sync::OnceLock};
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "public/"]
|
||||
struct Assets;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Files {
|
||||
pub atkinson_latin_woff2: BinaryFile,
|
||||
pub atkinson_latin_ext_woff2: BinaryFile,
|
||||
pub atkinson_all_woff: BinaryFile,
|
||||
pub style: TextFile,
|
||||
pub hyperlegible: TextFile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileMetadata {
|
||||
pub filename: String,
|
||||
pub extension: String,
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
impl FileMetadata {
|
||||
pub fn get_hash_filename(&self) -> String {
|
||||
let mut hash = self.hash.clone();
|
||||
hash.truncate(8);
|
||||
format!("{}.{}.{}", self.filename, hash, self.extension)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BinaryFile {
|
||||
pub data: Cow<'static, [u8]>,
|
||||
pub metadata: FileMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextFile {
|
||||
pub text: String,
|
||||
pub metadata: FileMetadata,
|
||||
}
|
||||
|
||||
pub static FILES: OnceLock<Files> = OnceLock::new();
|
||||
|
||||
pub fn get_file_wrapper() -> Result<&'static Files, Status> {
|
||||
match FILES.get() {
|
||||
Some(files) => Ok(files),
|
||||
None => Err(Status::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_binary_file(filename: &str, extension: &str) -> Option<BinaryFile> {
|
||||
match Assets::get(&format!("{}.{}", filename, extension)) {
|
||||
Some(file) => {
|
||||
let metadata = FileMetadata {
|
||||
filename: filename.into(),
|
||||
extension: extension.into(),
|
||||
hash: hex::encode(file.metadata.sha256_hash()),
|
||||
};
|
||||
Some(BinaryFile {
|
||||
data: file.data,
|
||||
metadata: metadata,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_text_file(filename: &str, extension: &str) -> Option<TextFile> {
|
||||
let file = get_binary_file(filename, extension)?;
|
||||
match std::str::from_utf8(&file.data) {
|
||||
Ok(string) => Some(TextFile {
|
||||
text: string.into(),
|
||||
metadata: file.metadata,
|
||||
}),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_files() {
|
||||
let files = Files {
|
||||
atkinson_latin_woff2: get_binary_file("atkinson-hyperlegible-latin-400-normal", "woff2")
|
||||
.unwrap(),
|
||||
atkinson_latin_ext_woff2: get_binary_file(
|
||||
"atkinson-hyperlegible-latin-ext-400-normal",
|
||||
"woff2",
|
||||
)
|
||||
.unwrap(),
|
||||
atkinson_all_woff: get_binary_file("atkinson-hyperlegible-all-400-normal", "woff").unwrap(),
|
||||
style: get_text_file("style", "css").unwrap(),
|
||||
hyperlegible: get_text_file("hyperlegible", "css").unwrap(),
|
||||
};
|
||||
FILES.set(files).unwrap();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
pub mod files;
|
||||
mod routes;
|
||||
pub mod templates;
|
||||
|
||||
pub use routes::style;
|
||||
pub use routes::woff2_font;
|
||||
pub use routes::woff_font;
|
|
@ -0,0 +1,62 @@
|
|||
use super::{files::get_file_wrapper, templates::ErrorTemplate};
|
||||
use rocket::{http::Status, response::content::RawCss};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(status = 200, content_type = "font/woff2")]
|
||||
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(status = 200, content_type = "font/woff")]
|
||||
pub struct RawWoffFont(pub Cow<'static, [u8]>);
|
||||
|
||||
#[derive(Responder)]
|
||||
pub struct ErrorTemplateResponder<'a> {
|
||||
template: ErrorTemplate<'a>,
|
||||
}
|
||||
|
||||
#[get("/css/<style>")]
|
||||
pub fn style(style: &str) -> Result<RawCss<String>, Status> {
|
||||
let style_file = &get_file_wrapper()?.style;
|
||||
let hyperlegible_file = &get_file_wrapper()?.hyperlegible;
|
||||
|
||||
let style_name = style_file.metadata.get_hash_filename();
|
||||
let hyperlegible_name = hyperlegible_file.metadata.get_hash_filename();
|
||||
|
||||
if style == style_name {
|
||||
Ok(RawCss::<String>(style_file.text.clone()))
|
||||
} else if style == hyperlegible_name {
|
||||
Ok(RawCss::<String>(hyperlegible_file.text.clone()))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/woff2/<font>")]
|
||||
pub fn woff2_font(font: &str) -> Result<RawWoff2Font, Status> {
|
||||
let latin_file = &get_file_wrapper()?.atkinson_latin_woff2;
|
||||
let latin_ext_file = &get_file_wrapper()?.atkinson_latin_ext_woff2;
|
||||
|
||||
let latin = latin_file.metadata.get_hash_filename();
|
||||
let latin_ext = latin_file.metadata.get_hash_filename();
|
||||
|
||||
if font == latin {
|
||||
Ok(RawWoff2Font(latin_file.data.clone()))
|
||||
} else if font == latin_ext {
|
||||
Ok(RawWoff2Font(latin_ext_file.data.clone()))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/woff/<font>")]
|
||||
pub fn woff_font(font: &str) -> Result<RawWoffFont, Status> {
|
||||
let all_file = &get_file_wrapper()?.atkinson_all_woff;
|
||||
let all = all_file.metadata.get_hash_filename();
|
||||
|
||||
if font == all {
|
||||
Ok(RawWoffFont(all_file.data.clone()))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
use askama::Template;
|
||||
use proc_macros::uses_base_template;
|
||||
use shared::names::Site;
|
||||
|
||||
pub struct BaseTemplate {
|
||||
pub style_filename: String,
|
||||
pub atkinson_latin_woff2_filename: String,
|
||||
pub atkinson_latin_ext_woff2_filename: String,
|
||||
pub atkinson_all_woff_filename: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "error.html")]
|
||||
#[uses_base_template]
|
||||
pub struct ErrorTemplate<'a> {
|
||||
pub error: &'a str,
|
||||
pub error_description: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
#[uses_base_template]
|
||||
pub struct IndexTemplate {
|
||||
pub sites: Vec<Site>,
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use crate::watcher::hot_reloading;
|
||||
use assets::files::initialize_files;
|
||||
use rocket::tokio;
|
||||
use sites::init_names;
|
||||
|
||||
|
@ -14,6 +15,7 @@ mod watcher;
|
|||
#[launch]
|
||||
async fn rocket() -> _ {
|
||||
init_names().unwrap();
|
||||
initialize_files();
|
||||
let cors = rocket_cors::CorsOptions::default().to_cors().unwrap();
|
||||
tokio::task::spawn_blocking(hot_reloading);
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::{
|
||||
assets::{ErrorTemplate, IndexTemplate},
|
||||
links::{next_url, previous_url, previous_name, next_name},
|
||||
assets::{
|
||||
files::{get_file_wrapper, FileMetadata},
|
||||
templates::{BaseTemplate, ErrorTemplate, IndexTemplate},
|
||||
},
|
||||
links::{next_name, next_url, previous_name, previous_url},
|
||||
sites::get_global_names,
|
||||
};
|
||||
|
||||
use rocket::{
|
||||
http::Status,
|
||||
response::Redirect,
|
||||
|
@ -11,10 +13,38 @@ use rocket::{
|
|||
};
|
||||
use rocket_cors::{Guard, Responder};
|
||||
|
||||
const NOT_FOUND_ERROR: ErrorTemplate = ErrorTemplate {
|
||||
error: "Not Found",
|
||||
error_description: "this URL could not be found on the webring.",
|
||||
};
|
||||
fn get_hash_filename(metadata: &FileMetadata) -> Result<String, Status> {
|
||||
Ok(metadata.get_hash_filename())
|
||||
}
|
||||
|
||||
fn get_base_template() -> Result<BaseTemplate, Status> {
|
||||
let files = get_file_wrapper()?;
|
||||
|
||||
let style_filename = get_hash_filename(&files.style.metadata)?;
|
||||
let atkinson_latin_woff2_filename = get_hash_filename(&files.atkinson_latin_woff2.metadata)?;
|
||||
let atkinson_latin_ext_woff2_filename =
|
||||
get_hash_filename(&files.atkinson_latin_ext_woff2.metadata)?;
|
||||
let atkinson_all_woff_filename = get_hash_filename(&files.atkinson_all_woff.metadata)?;
|
||||
|
||||
let template = BaseTemplate {
|
||||
style_filename,
|
||||
atkinson_latin_woff2_filename,
|
||||
atkinson_latin_ext_woff2_filename,
|
||||
atkinson_all_woff_filename,
|
||||
};
|
||||
|
||||
Ok(template)
|
||||
}
|
||||
|
||||
fn not_found_error() -> Result<ErrorTemplate<'static>, Status> {
|
||||
let base_template = get_base_template()?;
|
||||
let template = ErrorTemplate {
|
||||
error: "Not Found",
|
||||
error_description: "this URL could not be found on the webring.",
|
||||
base_template,
|
||||
};
|
||||
Ok(template)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
|
@ -24,10 +54,13 @@ pub struct JsonResponse {
|
|||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index() -> IndexTemplate {
|
||||
IndexTemplate {
|
||||
pub async fn index() -> Result<IndexTemplate, Status> {
|
||||
let base_template = get_base_template()?;
|
||||
let template = IndexTemplate {
|
||||
sites: get_global_names().await,
|
||||
}
|
||||
base_template,
|
||||
};
|
||||
Ok(template)
|
||||
}
|
||||
|
||||
#[get("/previous?<source_url>")]
|
||||
|
@ -66,6 +99,6 @@ pub async fn name(
|
|||
}
|
||||
|
||||
#[catch(404)]
|
||||
pub fn not_found() -> ErrorTemplate<'static> {
|
||||
NOT_FOUND_ERROR
|
||||
pub fn not_found() -> Result<ErrorTemplate<'static>, Status> {
|
||||
not_found_error()
|
||||
}
|
||||
|
|
|
@ -5,8 +5,52 @@
|
|||
{% block head %}
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="stylesheet" href="/public/css/hyperlegible.css" />
|
||||
<link rel="stylesheet" href="/public/css/style.css" />
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: Atkinson Hyperlegible;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url("/public/woff2/{{ base_template.atkinson_latin_ext_woff2_filename }}") format("woff2"),
|
||||
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
|
||||
unicode-range: U+0100-024F,
|
||||
U+0259,
|
||||
U+1E00-1EFF,
|
||||
U+2020,
|
||||
U+20A0-20AB,
|
||||
U+20AD-20CF,
|
||||
U+2113,
|
||||
U+2C60-2C7F,
|
||||
U+A720-A7FF
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Atkinson Hyperlegible;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url("/public/woff2/{{ base_template.atkinson_latin_woff2_filename }}") format("woff2"),
|
||||
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
|
||||
unicode-range: U+0000-00FF,
|
||||
U+0131,
|
||||
U+0152-0153,
|
||||
U+02BB-02BC,
|
||||
U+02C6,
|
||||
U+02DA,
|
||||
U+02DC,
|
||||
U+2000-206F,
|
||||
U+2074,
|
||||
U+20AC,
|
||||
U+2122,
|
||||
U+2191,
|
||||
U+2193,
|
||||
U+2212,
|
||||
U+2215,
|
||||
U+FEFF,
|
||||
U+FFFD
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/public/css/{{ base_template.style_filename }}" />
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
|
Loading…
Reference in New Issue