Web

Routing

A Router is a list of routes checked in order: the first one whose method and path match answers the request. Handlers are plain functions from &Request to Response.

Declaring routes#

use rustclamp::web::{Response, Router, error, json, redirect};

pub fn routes() -> Router {
    Router::new()
        .view("/", "welcome")                      // GET / serves the built view welcome.html
        .get("/blog/{slug}", show_post)            // {slug} matches one path segment
        .get("/search", search)
        .post("/subscribe", |request| {
            let email = request.form("email").unwrap_or_default();
            if email.contains('@') { redirect("/thanks") } else { error(422) }
        })
        .route("DELETE", "/api/posts/{id}", |request| {
            Response::text(200, &format!("deleted {}", request.param("id").unwrap_or("?")))
        })
}
MethodAnswers
get(path, handler)GET path
post(path, handler)POST path
route(method, path, handler)Any method: PUT, PATCH, DELETE…
view(path, name)GET path with the built page name

Paths match exactly, segment by segment. A {name} segment matches any one non-empty segment and is read with request.param("name"); the value is not URL-decoded. A GET that matches no route falls through to a file in public/, and anything else is 404.

Controllers#

Rust has no base class, so a controller is a function in app/http/controllers/ that starts with use super::controller::*;, the shared imports:

use super::controller::*;

/// `GET /blog/{slug}`
pub fn show_post(request: &Request) -> Response {
    match request.param("slug") {
        Some("hello-world") => render("post", &[("title", &"Hello, world")]),
        _ => error(404),
    }
}

A handler that can fail returns web::Result and uses ?, as Laravel lets an exception bubble up. The error is logged with the method and path, and the visitor gets a 500:

pub fn index(request: &Request) -> web::Result {
    let posts = Post::published(request.db())?;
    Ok(render("blog", &[("posts", &posts)]))
}

router.up("/up") adds a health check like Laravel's: {"status":"ok"}, or 503 when the app's database does not answer.

Reading the request#

CallReturns
request.method, request.pathThe method and path, without the query string
request.param("slug")A {slug} route segment, not decoded
request.query("q")A decoded query-string value
request.form("email")A decoded field of a URL-encoded form body
request.header("accept")A header, matched case-insensitively
request.cookie("theme")A cookie value
request.session()The visitor's session, behind Sessions
request.flash("Saved.")A one-time message for the next page, shown as flash

Building responses#

json(r#"{"status":"ok"}"#)                  // 200, application/json, a pre-serialized body
redirect("/thanks")                         // 302 with Location
view("welcome")                             // a built page
render("post", &[("title", &title)]) // a built view with data
error(404)                                  // your error page, or the built-in one
Response::text(201, "created")              // plain text with any status
Response::new(200, "text/csv", bytes)       // any content type
    .with_header("Content-Disposition", "attachment; filename=report.csv")

with_header turns line breaks into spaces, so a value taken from the request cannot inject headers of its own. See Views and assets for pages and error views.