#HttpIngress Patterns Cookbook
Version: 0.36.0 | Updated: 2026-04-03 | Applies to: ranvier-http 0.36+ | Category: Cookbook
#Overview
HttpIngress is a circuit builder, not a web server. It wires Axon circuits to HTTP
routes, manages Guards, and delegates to hyper for serving. This cookbook covers the
most common patterns for routing, body parsing, and static assets.
#1. `post()` vs `post_typed::()` Decision Tree
flowchart TD
Q{"Does the route accept a JSON request body?"}
Q -->|NO| P["post()\nAxon‹(), Out, E, R›\nBody is ignored; circuit receives ()"]
Q -->|YES| PT["post_typed::‹T›()\nAxon‹T, Out, E, R›\nBody is deserialized as T automatically\nT: DeserializeOwned + Serialize + JsonSchema"]#`post()` -- No body
use ranvier_http::prelude::*;
let trigger_circuit = Axon::simple::<String>("trigger")
.then(start_job);
Ranvier::http()
.post("/api/jobs/start", trigger_circuit)
.run(())
.await?;#`post_typed::()` -- Typed JSON body
use ranvier_http::prelude::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct CreateOrder {
product_id: String,
quantity: u32,
shipping_address: String,
}
let order_circuit = Axon::typed::<CreateOrder, String>("create-order")
.then(validate_order)
.then(persist_order);
Ranvier::http()
.post_typed::<CreateOrder, _, _>("/api/orders", order_circuit)
.run(())
.await?;post_typed automatically:
- Parses the request body as JSON into
T - Returns 400 Bad Request on parse failure
- Records the
JsonSchemafor OpenAPI generation
The same pattern works for put_typed::<T>() and patch_typed::<T>().
#`schemars` Feature Requirements
JsonSchema derives require additional schemars features depending on the field
types used in your request struct. If the derive fails to compile, consult this table:
| Field Type | Required Feature | Cargo.toml Example |
|---|---|---|
Uuid |
uuid1 |
schemars = { version = "1", features = ["derive", "uuid1"] } |
NaiveDate, DateTime<Utc> |
chrono04 |
schemars = { version = "1", features = ["derive", "chrono04"] } |
Decimal |
rust_decimal1 |
schemars = { version = "1", features = ["derive", "rust_decimal1"] } |
Url |
url2 |
schemars = { version = "1", features = ["derive", "url2"] } |
Multiple features can be combined: features = ["derive", "uuid1", "chrono04"].
#2. Path Parameter Extraction
Use :param syntax in the route path. Parameters are available via PathParams
in the Bus.
use ranvier_http::prelude::*;
use ranvier_core::prelude::*;
let get_user = Axon::simple::<String>("get-user")
.then_fn("extract-id", |_input, _res, bus| async move {
let params = bus.require::<PathParams>();
let user_id = params.get("id").unwrap_or("unknown");
Outcome::next(format!("User: {}", user_id))
});
Ranvier::http()
.get("/api/users/:id", get_user)
.get("/api/users/:id/posts/:slug", get_user_post)
.run(())
.await?;Multiple parameters are supported: /api/orgs/:org_id/teams/:team_id.
#`PathParams::get()` Returns `&str`
PathParams::get() returns Option<&str>, not Option<&String>. When you need
an owned String, use .map(|s| s.to_string()) instead of .cloned():
// Reading a path parameter as an owned String
let id: Option<String> = bus.get_cloned::<PathParams>().ok()
.and_then(|params| params.get("id").map(|s| s.to_string()));
// Parsing a path parameter as Uuid
let uuid: Option<uuid::Uuid> = bus.get_cloned::<PathParams>().ok()
.and_then(|params| params.get("id").and_then(|s| s.parse().ok()));Migration note: If migrating from a
HashMap<String, String>pattern, replace.cloned()with.map(|s| s.to_string())sincePathParams::get()returns&str, not&String.
#3. Bus Injector for Request Context
Use bus_injector() to extract HTTP request data into the Bus before any Guard
or circuit runs. This is the escape hatch for data that built-in Guards do not
extract.
use ranvier_http::prelude::*;
use ranvier_core::bus::Bus;
#[derive(Debug, Clone)]
struct UserAgent(String);
#[derive(Debug, Clone)]
struct AcceptLanguage(String);
Ranvier::http()
.bus_injector(|parts: &http::request::Parts, bus: &mut Bus| {
if let Some(ua) = parts.headers.get("user-agent") {
if let Ok(s) = ua.to_str() {
bus.insert(UserAgent(s.to_string()));
}
}
if let Some(lang) = parts.headers.get("accept-language") {
if let Ok(s) = lang.to_str() {
bus.insert(AcceptLanguage(s.to_string()));
}
}
})
.get("/api/data", data_circuit)
.run(())
.await?;Inside the circuit, read the injected values:
let circuit = Axon::simple::<String>("data")
.then_fn("use-context", |_input, _res, bus| async move {
let ua = bus.get_cloned::<UserAgent>()
.map(|u| u.0)
.unwrap_or_else(|_| "unknown".into());
Outcome::next(format!("Hello from {}", ua))
});#4. Guard + Route Integration
Guards and routes compose naturally. Global Guards apply to all routes; per-route Guards apply to specific endpoints only.
use ranvier_http::{guards, prelude::*};
use ranvier_guard::prelude::*;
Ranvier::http()
// Global: every request gets these
.guard(RequestIdGuard::new())
.guard(AccessLogGuard::new())
.guard(CorsGuard::<()>::permissive())
// Read endpoints: no extra guards
.get("/api/products", list_products)
.get("/api/products/:id", get_product)
// Write endpoints: per-route guards
.post_with_guards("/api/products", create_product, guards![
ContentTypeGuard::json(),
RequestSizeLimitGuard::max_2mb(),
])
.put_with_guards("/api/products/:id", update_product, guards![
ContentTypeGuard::json(),
])
.delete("/api/products/:id", delete_product)
.run(())
.await?;#5. Static Assets with `serve_dir()`
Mount a directory to serve static files. Ranvier handles MIME type detection, 304 caching, directory indexes, and pre-compressed variants.
Use this when one process should serve both API routes and a built frontend. For pure static hosting or CDN-first asset delivery, prefer nginx, Caddy, object storage/CDN, or
tower-http::ServeDir.
#Basic Setup
use ranvier_http::prelude::*;
Ranvier::http()
.serve_dir("/static", "./public")
.get("/api/data", data_circuit)
.run(())
.await?;#SPA with Fallback
Ranvier::http()
.serve_dir("/", "./dist")
.directory_index("index.html")
.spa_fallback("./dist/index.html")
.run(())
.await?;#Production Static Assets
Ranvier::http()
.serve_dir("/assets", "./build/assets")
.directory_index("index.html")
.static_cache_control("public, max-age=3600")
.immutable_cache() // hashed filenames get max-age=31536000
.serve_precompressed() // check for .br / .gz variants first
.enable_range_requests() // support Range: bytes=X-Y (206 responses)
.get("/api/data", data_circuit)
.run(())
.await?;immutable_cache() detects filenames with content hashes (e.g., app.a1b2c3.js)
and applies Cache-Control: public, max-age=31536000, immutable.
serve_precompressed() checks for .br and .gz pre-compressed variants of
static files before serving the original. This pairs well with build tools that
generate pre-compressed output.
enable_range_requests() adds support for partial content responses (HTTP 206),
useful for large file downloads and media streaming.
#6. Health Endpoints
use ranvier_http::prelude::*;
Ranvier::http()
.health_endpoint("/health")
.readiness_liveness("/ready", "/live")
.health_check("database", |resources| async move {
// Your health check logic
Ok(())
})
.get("/api/data", data_circuit)
.run(())
.await?;Or use the default paths (/health/ready, /health/live):
Ranvier::http()
.readiness_liveness_default()
.run(())
.await?;#7. WebSocket Routes
use ranvier_http::prelude::*;
Ranvier::http()
.ws("/ws/chat", |mut conn, _resources, _bus| async move {
conn.send(WebSocketEvent::text("Welcome!")).await.ok();
while let Ok(Some(msg)) = conn.next_json::<serde_json::Value>().await {
let echo = WebSocketEvent::json(&msg).unwrap();
if conn.send(echo).await.is_err() {
break;
}
}
})
.run(())
.await?;#8. Error Handlers
Register custom error-to-response converters for specific routes:
use ranvier_http::prelude::*;
Ranvier::http()
.post_with_error("/api/orders", order_circuit, |error| {
match error.to_string().as_str() {
e if e.contains("duplicate") => (409, "Conflict: duplicate order"),
e if e.contains("invalid") => (400, "Bad Request: invalid input"),
_ => (500, "Internal Server Error"),
}
})
.run(())
.await?;#See Also
- Guard Patterns Cookbook -- Guard composition patterns
- Bus Access Patterns -- Bus read/write decision guide
- Deployment Guide -- production deployment configuration