Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/knut

crates/knut-wasm-host/src/axum_host.rs

//! An [`axum::Router`] around [`WasmHost`] — feature `axum`, OFF by default.
//!
//! This is a wrapper, not the crate. The core is a `hyper::service::Service` so
//! that a server which is *already* hyper (korp's, under tonic) can mount it
//! without a web framework appearing in its dependency graph. A caller who
//! already runs axum should not have to write the same twelve lines, so they live
//! here — behind a feature, where nobody else pays for them.
//!
//! [`router`] returns a `Router` with **only a fallback**, which is exactly what
//! makes it composable: merge it with a router of your own data routes and every
//! explicit route of yours wins, while `/` and every bundle file fall through to
//! the host.
//!
//! ```no_run
//! # use knut_wasm_host::{BundleSpec, EntryPoint, WasmHost};
//! # fn demo(host: WasmHost, data: axum::Router) -> axum::Router {
//! data.merge(knut_wasm_host::router(host))
//! # }
//! ```

use axum::Router;
use axum::body::Body;
use axum::extract::Request;
use axum::response::Response as AxumResponse;
use hyper::http::Response;

use crate::response::HostBody;
use crate::service::WasmHost;
use crate::sse::{SSE_CONTENT_TYPE, SseBody};

/// A `Router` whose fallback is `host`: `/` (or the mount prefix) redirects to the
/// loader, every bundle file is served by extension, and anything else is the
/// host's own 404 — which says which rule it broke.
///
/// Merge it with your data routes; yours are explicit and win.
pub fn router(host: WasmHost) -> Router {
    Router::new().fallback(move |req: Request| {
        let host = host.clone();
        async move { into_response(host.respond(req.method(), req.uri().path())) }
    })
}

/// One host response as an axum response — the body is re-wrapped, nothing else
/// changes.
#[must_use]
pub fn into_response(resp: Response<HostBody>) -> AxumResponse {
    let (parts, body) = resp.into_parts();
    AxumResponse::from_parts(parts, Body::new(body))
}

/// An [`SseBody`] as an axum response: `text/event-stream`, no caching, the frames
/// exactly as the caller queued them.
///
/// Note this does NOT go through axum's own `Sse`/`Event` types. Those would
/// re-encode a frame the caller already encoded, and re-encoding a wire is how a
/// wire grows a second writer.
#[must_use]
pub fn sse_response(body: SseBody) -> AxumResponse {
    let (parts, body) = body.into_response().into_parts();
    debug_assert_eq!(
        parts.headers[hyper::header::CONTENT_TYPE],
        SSE_CONTENT_TYPE,
        "the SSE body must carry its own content type"
    );
    AxumResponse::from_parts(parts, Body::new(body))
}