Expand description
§Servable: a simple web framework
A minimal, convenient web micro-framework built around htmx, Axum, and Maud.
This powers my homepage. See example usage here.
§Features
servable provides abstractions that implement common utilities needed by an http server.
- response headers and cache-busting utilities
- client device detection (mobile / desktop)
- server-side image optimization (see the
imagefeature below) - ergonomic htmx integration (see
htmx-*features below)
§Quick Start
use servable::{ServableRouter, servable::StaticAsset, mime::MimeType};
#[tokio::main]
async fn main() {
let route = ServableRouter::new()
.add_page(
"/hello",
StaticAsset {
bytes: b"Hello, World!",
mime: MimeType::Text,
},
);
// usual axum startup routine
let app = route.into_router();
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}§Core Concepts
§The Servable trait
The Servable trait is the foundation of this stack.
servable provides implementations for a few common servables:
-
StaticAsset, for static files like CSS, JavaScript, images, or plain bytes:use servable::{StaticAsset, mime::MimeType}; let asset = StaticAsset { bytes: b"body { color: red; }", mime: MimeType::Css, ttl: StaticAsset::DEFAULT_TTL }; -
Redirect, for simple http redirects:use servable::Redirect; let redirect = Redirect::new("/new-location").unwrap(); -
HtmlPage, for dynamically-rendered HTML pagesuse servable::{HtmlPage, PageMetadata}; use maud::html; use std::pin::Pin; let page = HtmlPage::default() .with_meta(PageMetadata { title: "My Page".into(), description: Some("A great page".into()), ..Default::default() }) .with_render(|_page, ctx| { Box::pin(async move { html! { h1 { "Welcome!" } p { "Route: " (ctx.route) } } }) });HtmlPageautomatically generates a<head>and wraps its rendered html in<html><body>.
§ServableRouter
A ServableRouter exposes a collection of Servables under different routes. It implements tower’s Service trait, and can be easily be converted into an Axum Router. Construct one as follows:
let route = ServableRouter::new()
.add_page("/", home_page)
.add_page("/about", about_page)
.add_page("/style.css", stylesheet)
.with_404(custom_404_page); // override default 404§Features
-
image: enable image transformation via query parameters. This makestokioa dependency.
When this is enabled, allStaticAssetswith a valid mimetype can take an optionalt=query parameter.
See theTransformerEnumin this crate’s documentation for details.When
imageis enabled, the image below…let route = ServableRouter::new() .add_page( "/image.png", StaticAsset { bytes: b"fake image data", mime: MimeType::Png, ttl: StaticAsset::DEFAULT_TTL } );…may be accessed as follows:
# Original image GET /image.png # Resize to max 800px on longest side GET /image.png?t=maxdim(800,800) # Crop to a 400x400 square at the center of the image GET /image.png?t=crop(400,400,c) # Chain transformations and transcode GET /image.png?t=maxdim(800,800);crop(400,400);format(webp) -
htmx-2.0.8: Include htmx sources in the compiled executable.
Use as follows:let route = ServableRouter::new() .add_page("/htmx.js", servable::HTMX_2_0_8) .add_page("/htmx-json-enc.js", servable::EXT_JSON_1_19_12);
§Caching and cache-busting
Control caching behavior per servable:
use chrono::TimeDelta;
use servable::HtmlPage;
let page = HtmlPage::default()
.with_ttl(Some(TimeDelta::hours(1)))
.with_private(false);Headers are automatically generated:
Cache-Control: public, max-age=3600(default)Cache-Control: private, max-age=31536000(ifprivateis true)
We also provide a static CACHE_BUST_STR, which may be formatted into urls to force cache refresh
whenever the server is restarted:
use chrono::TimeDelta;
use servable::{HtmlPage, CACHE_BUST_STR, ServableWithRoute, StaticAsset, ServableRouter};
use servable::mime::MimeType;
pub static HTMX: ServableWithRoute<StaticAsset> = ServableWithRoute::new(
|| format!("/{}/main.css", *CACHE_BUST_STR),
StaticAsset {
bytes: "div{}".as_bytes(),
mime: MimeType::Css,
ttl: StaticAsset::DEFAULT_TTL,
},
);
let route = HTMX.route();
println!("Css is at {route}");
let router = ServableRouter::new()
.add_page_with_route(&HTMX);§TODO:
- cache-busting fonts in css is not possible, we need to dynamic replace urls
Modules§
Structs§
- Client
Info - Inferred information about the client that requested a certain route.
- Html
Page - A complete, dynamically-rendered blob of HTML.
- Page
Metadata - Redirect
- A simple http edirect
- Render
Context - Additional context available to crate::servable::Servables when generating their content
- Rendered
- An asset to return from an http route
- Servable
Router - A set of related Servables under one route.
- Servable
With Route - A Servable and the route it is available at
- Static
Asset - A static blob of bytes
Enums§
- Device
Type - The type of device that requested a page
- Redirect
Code - Rendered
Body - The contents of a response produced by a crate::servable::Servable
- Script
Source
Statics§
- CACHE_
BUST_ STR - A unique string that can be used for cache-busting.
Traits§
- Rendered
Body Type - A utility trait, used to control the kind of body Rendered contains.
- Servable
- Something that may be served over http. If implementing this trait, refer to sample implementations in redirect::Redirect, asset::StaticAsset and html::HtmlPage.