Skip to main content

Crate memory_serve

Crate memory_serve 

Source
Expand description

§Memory serve

memory-serve enables fast static file serving for axum web applications, by keeping all assets in memory.

It loads static web assets like HTML, stylesheets, images and scripts into the rust binary at compile time and exposes them as an axum Router. It automatically adds cache headers and handles file compression.

During development (debug builds) files are served dynamically, they are read and compressed at request time.

In release mode text-based files like HTML or javascript are compressed using brotli at compile time and decompressed at startup, to minimize the binary size.

All files are served with an etag header and If-None-Match requests are handled accordingly.

Text-based files are served in plain or with gzip or brotli compression based on the abilities and preferences of the client.

Assets can optionally be served on a cache-busted route containing a hash of their contents, so that clients can cache them indefinitely.

Routing can be configured in a flexible manner, for instance to accommodate an SPA.

§Compatibility

memory-serve is designed to work with axum

§Usage

Because memory-serve is used both from your build.rs (to load assets at compile time) and from your application code (to serve them), it must be added to both [dependencies] and [build-dependencies] in your Cargo.toml:

[dependencies]
memory-serve = "2"

[build-dependencies]
memory-serve = "2"

Omitting the [build-dependencies] entry results in a compile error from the build script, like error[E0433]: cannot find module or crate 'memory_serve'.

Call load_directory with a relative path from your build.rs. See the Cargo book for more information about build scripts. See the example project in the memory-serve repository for an example build.rs. Calling load_directory makes sure that your assets are loaded at compile time.

Call the load! macro from your application to create a MemoryServe instance.

When an instance of MemoryServe is created, we can bind it to your axum instance. Calling MemoryServe::into_router() on the MemoryServe instance produces an axum Router that can either be merged in another Router or used directly in a server by calling Router::into_make_service().

§Named directories

Multiple directories can be included using load_names_directories from your build.rs script. This takes a list of tuples, with the name and the path of your asset directories, and a boolean indicating whether to embed the assets into the binary.

You can use the names as specified in the load_names_directories call to load the specific MemoryServe instance by passing the name as a string to the load! macro.

§Features

Use the force-embed feature flag to always include assets in the binary - also in debug builds.

§Environment variables

Use MEMORY_SERVE_QUIET=1 to not print log messages at compile time.

§Example

build.rs:

fn main() {
    memory_serve::load_directory("./public");
}

main.rs:

use axum::{response::Html, routing::get, Router};
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    let memory_router = memory_serve::load!()
        .index_file(Some("/index.html"))
        .into_router();

    // possible other routes can be added at this point, like API routes
    let app = Router::new()
        .merge(memory_router);

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

§Configuration options

An instance of the MemoryServe struct can be configured by calling the following configuration methods:

methodDefault valueDescription
MemoryServe::index_fileSome("/index.html")Which file to serve on the route “/”
MemoryServe::index_on_subdirectoriesfalseWhether to serve the corresponding index in subdirectories
MemoryServe::fallbackNoneWhich file to serve if no route matched the request
MemoryServe::fallback_statusStatusCode::NOT_FOUNDThe HTTP status code to serve for routes that did not match
MemoryServe::enable_gziptrue (release) 1Allow serving gzip encoded files
MemoryServe::enable_brotlitrue (release) 1Allow serving brotli encoded files
MemoryServe::html_cache_controlCacheControl::ShortCache control header to serve on HTML files
MemoryServe::cache_controlCacheControl::MediumCache control header to serve on other files
MemoryServe::add_alias[]Create a route / file alias
MemoryServe::enable_clean_urlfalseEnable clean URLs
MemoryServe::enable_hashed_routesfalseAlso serve assets on a cache-busted, hashed route

See Cache control for the cache control options and Cache busting for hashed routes.

§Logging

During compilation, problems that occur with the inclusion or compression of assets are logged as a warning from the build script:

warning: memory-serve-test@0.0.0: Loading static assets from /home/user/project/my-project/static
warning: memory-serve-test@0.0.0: Embedding assets into binary
warning: memory-serve-test@0.0.0: including /blog/index.html 431 -> 175 bytes (compressed)

When running the resulting executable, all registered routes and asset sizes are logged using the tracing crate. To print or log them, use tracing-subscriber. Example output:

 INFO memory_serve: serving /assets/icon.jpg 1366 bytes
 INFO memory_serve: serving /assets/index.css 1552 -> 509 bytes (compressed)
 INFO memory_serve: serving /assets/index.js 20 bytes
 INFO memory_serve: serving /assets/stars.svg 2255 -> 907 bytes (compressed)
 INFO memory_serve: serving /index.html 437 -> 178 bytes (compressed)
 INFO memory_serve: serving /index.html as index on /

With hashed routes enabled, each hashed route is logged as well:

 INFO memory_serve: serving /assets/index.css on hashed route /assets/index.ec4edeea111c8549.css

§Cache control

There are 5 different values to choose from for the cache-control settings:

OptionDescriptionValue
CacheControl::Longclients can keep assets that have cache busting for a yearmax-age=31536000, immutable
CacheControl::Mediumassets without cache busting are revalidated after a day and can be kept for a weekmax-age=604800, stale-while-revalidate=86400
CacheControl::Shortcache kept for max 5 minutes, only at the client (not in a proxy)max-age=300, private
CacheControl::NoCachedo not cache if freshness is really vitalno-cache
CacheControl::CustomCustom valueuser defined

§Cache busting

With MemoryServe::enable_hashed_routes every non-HTML asset is also served on a route that contains a hash of its contents, for instance /assets/index.css is also available as /assets/index.3f9a1c2b7d84e6a0.css. The hashed route changes whenever the file changes, so embedded assets are served there with CacheControl::Long (immutable). The plain route keeps the configured cache control.

Use MemoryServe::manifest to look up the hashed routes, for instance to reference them from your templates. HTML files always map to their plain route, and when hashed routes are disabled all assets do, so templates work regardless of the setting:

let memory_serve = memory_serve::load!().enable_hashed_routes(true);

// keep this around, for instance in your axum state
let manifest = memory_serve.manifest();
// Some("/assets/index.3f9a1c2b7d84e6a0.css")
let css = manifest.get("/assets/index.css");

let router = memory_serve.into_router();

In debug builds (dynamic serving) the hash is computed when the manifest or router is created, and the hashed route is served with the regular cache control so that edited files are not cached as immutable.


  1. Compression defaults to enabled in release builds and disabled in debug builds (where assets are served dynamically). ↩ 1 2

Macros§

load
Include the generated asset manifest and construct a MemoryServe struct.

Structs§

Asset
Represents a static asset that can be served
Manifest
Maps asset routes to the routes they are served on, see MemoryServe::manifest.
MemoryServe
Helper struct to create and configure an axum router to serve static files from memory.

Enums§

CacheControl
Options to choose from to configure the Cache-Control header for served files. See Cache control

Functions§

assets_to_code
Generate code with metadata and contents for the assets
load_directory
Load a directory of assets, keeping an administration of all files and optionally embedding them into the binary
load_directory_with_embed
Load a directory of assets, optionally embedding them into the binary
load_names_directories
Load multiple named directories of assets, optionally embedding them into the binary