pub struct Context<'r, I, E: Render = NoRender> { /* private fields */ }Expand description
The context a handler receives: proof that the request passed its route’s access check, the identity it carries, and the reply.
There is no public way to make one: web-net builds it only after the gate cleared the request.
use web_net::{App, Context, Guest, get};
fn home(cx: &mut Context<'_, Guest>) -> web_net::Result {
cx.text(200, "home")
}
let app = App::builder().route(get("/", home)).build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "home");Implementations§
Source§impl<'r, I, E: Render> Context<'r, I, E>
impl<'r, I, E: Render> Context<'r, I, E>
Sourcepub fn request(&self) -> &Request<'r>
pub fn request(&self) -> &Request<'r>
The request.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| {
let host = cx.request().host();
cx.text(200, host)
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "localhost");Sourcepub fn method(&self) -> Method
pub fn method(&self) -> Method
The method.
use serve_net::Method;
use web_net::{App, Context, Guest, Route};
fn which(cx: &mut Context<'_, Guest>) -> web_net::Result {
let m = if cx.method() == Method::Post { "post" } else { "get" };
cx.text(200, m)
}
let app = App::builder()
.route(Route::at("/").get(which).post(which).csrf_exempt("example"))
.build()?;
let r = serve_net::testing::respond(&app, b"POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n");
assert_eq!(r.text(), "post");Sourcepub fn path(&self) -> &'r str
pub fn path(&self) -> &'r str
The canonical path inside the site.
use web_net::{App, get};
let app = App::builder()
.base("/app")
.route(get("/about", |cx| cx.text(200, cx.path())))
.build()?;
let r = serve_net::testing::respond(&app, b"GET /app/about HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "/about");Sourcepub fn header(&self, name: &str) -> Option<&'r [u8]>
pub fn header(&self, name: &str) -> Option<&'r [u8]>
The first value of a request field.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| {
let agent = cx.header("user-agent").unwrap_or(b"unknown");
cx.bytes(200, "text/plain", agent)
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nUser-Agent: demo/1\r\n\r\n");
assert_eq!(r.text(), "demo/1");Sourcepub fn identity(&self) -> &'r I
pub fn identity(&self) -> &'r I
Who the request comes from: always present, possibly a guest.
use web_net::{App, Context, Guest, Identity, get};
let app = App::builder()
.route(get("/", |cx: &mut Context<'_, Guest>| {
let who = if cx.identity().is_guest() { "guest" } else { "member" };
cx.text(200, who)
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "guest");Sourcepub fn id(&self) -> RequestId
pub fn id(&self) -> RequestId
The request’s reference, as shown on error pages.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| {
let reference = cx.id().to_string();
cx.text(200, &reference)
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text().len(), 16);Sourcepub fn param(&self, name: &str) -> Result<Cow<'r, str>>
pub fn param(&self, name: &str) -> Result<Cow<'r, str>>
A route parameter (:slug or *rest), percent-decoded.
The path is canonical (no dot segments), but a *rest value is part
of a URL, not a safe file path: it can hold a drive (C:/Windows)
or names a file system reads specially. Serve files with
Assets, which refuses those; never join a
parameter onto a folder.
use web_net::{App, get};
let app = App::builder()
.route(get("/posts/:slug", |cx| cx.text(200, &cx.param("slug")?)))
.build()?;
let r = serve_net::testing::respond(&app, b"GET /posts/hello%20world HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "hello world");§Errors
500 route.no_param when the route’s pattern has no parameter
name (a mistake in the handler).
Sourcepub fn param_as<T: FromStr>(&self, name: &str) -> Result<T>
pub fn param_as<T: FromStr>(&self, name: &str) -> Result<T>
A route parameter parsed as T. A value that does not parse means
the page does not exist (/users/abc for a numeric id): 404.
use web_net::{App, get};
let app = App::builder()
.route(get("/users/:id", |cx| {
let id: u64 = cx.param_as("id")?;
cx.text(200, &format!("user {id}"))
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET /users/abc HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.status, 404);§Errors
404 route.param when the value does not parse; as
param for a name the pattern does not have.
Sourcepub fn query(&self, name: &str) -> Result<Option<Cow<'r, str>>>
pub fn query(&self, name: &str) -> Result<Option<Cow<'r, str>>>
The first value of a query parameter, decoded (+ is a space).
use web_net::{App, get};
let app = App::builder()
.route(get("/search", |cx| {
let q = cx.query("q")?.unwrap_or_default();
cx.text(200, &q)
}))
.build()?;
let at = |target: &str| serve_net::testing::respond(&app, format!("GET {target} HTTP/1.1\r\nHost: localhost\r\n\r\n").as_bytes());
assert_eq!(at("/search?q=red+shoes").text(), "red shoes");
assert_eq!(at("/search?q=a&q=b").status, 400);§Errors
400 query.duplicate when the name appears twice (parameter
pollution is refused, never resolved by picking one), 400
query.invalid when the value does not decode to UTF-8.
Sourcepub fn body(&self) -> Result<&'r [u8]>
pub fn body(&self) -> Result<&'r [u8]>
The raw request body. On an unsafe method its CSRF token was checked before the handler ran.
use web_net::{App, post};
let app = App::builder()
.route(post("/echo", |cx| {
let body = cx.body()?;
cx.bytes(200, "text/plain", body)
}).csrf_exempt("example: echoes the body"))
.build()?;
let r = serve_net::testing::respond(&app, b"POST /echo HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nhello");
assert_eq!(r.text(), "hello");§Errors
None today; reading the body stays fallible so a content inspector can refuse it in a later version without changing handlers.
Sourcepub fn form<T: FromForm>(&mut self) -> Result<T, FormError>
pub fn form<T: FromForm>(&mut self) -> Result<T, FormError>
Reads the body as a typed form (urlencoded or multipart text fields),
with the route’s form_limits.
On an unsafe method the CSRF token was checked before the handler
ran. A form type that is posted with a token lists the token field in
its ignore list (ignore = ["csrf_token"]).
The error keeps form-net’s detail, so an action can render the form
again with 422; ? turns it into an Error with its status.
use web_net::form_net::{self, FromForm};
form_net::form! {
pub struct Signup { pub email: String }
ignore = ["csrf_token"];
}
let app = web_net::App::builder()
.route(web_net::post("/signup", |cx| {
let form: Signup = cx.form()?;
cx.text(200, &form.email)
}).csrf_exempt("example without a session"))
.build()?;
let r = serve_net::testing::respond(&app,
b"POST /signup HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 15\r\n\r\nemail=a%40b.com");
assert_eq!(r.text(), "a@b.com");§Errors
form-net’s FormError: 415 for another media
type, 413 for a budget passed, 400 for malformed input, 422 for
fields that do not fit T, 403 for a CSRF token.
Sourcepub fn form_inspected<T: FromForm, N: Inspector>(
&mut self,
inspector: &mut N,
) -> Result<T, FormError>
pub fn form_inspected<T: FromForm, N: Inspector>( &mut self, inspector: &mut N, ) -> Result<T, FormError>
As form, with an inspector beside the parse (a
content policy that sees every part and field first).
use web_net::form_net::{self, FromForm, Inspector, Refusal, Verdict};
form_net::form! {
pub struct Signup { pub email: String }
ignore = ["csrf_token"];
}
/// Refuses addresses at one domain.
struct NoSpam;
impl Inspector for NoSpam {
fn field(&mut self, name: &str, value: &[u8]) -> Verdict {
if name == "email" && value.ends_with(b"@spam.example") {
return Verdict::Refuse(Refusal::new(422, "signup.spam"));
}
Verdict::Continue
}
}
let app = web_net::App::builder()
.route(web_net::post("/signup", |cx| {
let form: Signup = cx.form_inspected(&mut NoSpam)?;
cx.text(200, &form.email)
}).csrf_exempt("example without a session"))
.build()?;
let r = serve_net::testing::respond(&app,
b"POST /signup HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 22\r\n\r\nemail=a%40spam.example");
assert_eq!(r.status, 422);§Errors
As form, plus the inspector’s refusals.
Sourcepub fn upload(&mut self, store: &Store) -> Result<Upload, FormError>
Available on crate feature uploads only.
pub fn upload(&mut self, store: &Store) -> Result<Upload, FormError>
uploads only.Reads a multipart upload into store (feature uploads): text
fields in memory, files in the store’s private temporary files, with
the route’s form limits. The CSRF token was checked before the
handler ran, so a forged one stores nothing. Turn the result into a typed form with
T::from_upload(upload).
use web_net::form_net::Store;
let store = Store::memory_only();
let app = web_net::App::builder()
.route(web_net::post("/upload", move |cx| {
let upload = cx.upload(&store)?;
let stored = upload.file("photo").is_some();
cx.text(200, if stored { "stored" } else { "none" })
}).csrf_exempt("example without a session"))
.build()?;
let body = "--XyZ\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"a.txt\"\r\nContent-Type: text/plain\r\n\r\nabc\r\n--XyZ--\r\n";
let req = format!("POST /upload HTTP/1.1\r\nHost: localhost\r\nContent-Type: multipart/form-data; boundary=XyZ\r\nContent-Length: {}\r\n\r\n{body}", body.len());
let r = serve_net::testing::respond(&app, req.as_bytes());
assert_eq!(r.text(), "stored");§Errors
As form, plus the store’s errors.
Sourcepub fn upload_inspected<N: Inspector>(
&mut self,
store: &Store,
inspector: &mut N,
) -> Result<Upload, FormError>
Available on crate feature uploads only.
pub fn upload_inspected<N: Inspector>( &mut self, store: &Store, inspector: &mut N, ) -> Result<Upload, FormError>
uploads only.As upload, with an inspector beside the parse.
use web_net::form_net::{Inspector, PartHead, Refusal, Store, Verdict};
/// Takes text fields only.
struct NoFiles;
impl Inspector for NoFiles {
fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
if head.is_file() {
return Verdict::Refuse(Refusal::new(422, "upload.no_files"));
}
Verdict::Continue
}
}
let store = Store::memory_only();
let app = web_net::App::builder()
.route(web_net::post("/upload", move |cx| {
let _upload = cx.upload_inspected(&store, &mut NoFiles)?;
cx.text(200, "ok")
}).csrf_exempt("example without a session"))
.build()?;
let body = "--XyZ\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"a.txt\"\r\n\r\nabc\r\n--XyZ--\r\n";
let req = format!("POST /upload HTTP/1.1\r\nHost: localhost\r\nContent-Type: multipart/form-data; boundary=XyZ\r\nContent-Length: {}\r\n\r\n{body}", body.len());
let r = serve_net::testing::respond(&app, req.as_bytes());
assert_eq!(r.status, 422);§Errors
As upload, plus the inspector’s refusals.
Sourcepub fn csrf_token(&self) -> Result<String>
pub fn csrf_token(&self) -> Result<String>
A CSRF token for the visitor’s session, to put in a form’s hidden
csrf_token field or a script’s X-CSRF-Token header. Different on
every call (masked), so a compression side channel cannot recover it.
use web_net::{App, Context, Denial, Gate, Identity, Request, csrf_sec::Csrf, get};
struct Visitor;
impl Identity for Visitor {
fn is_guest(&self) -> bool { false }
fn csrf_binding(&self) -> Option<&[u8]> { Some(b"the session id") }
}
// `Sessions` is a gate whose identity is `Visitor`.
let app = App::with_gate(Sessions)
.csrf(Csrf::new(*b"a persisted 32-byte secret key!!").unwrap())
.route(get("/form", |cx: &mut Context<'_, Visitor>| {
let token = cx.csrf_token()?;
cx.html(200, &format!("<input type=hidden name=csrf_token value=\"{token}\">"))
}).public())
.build()?;
let r = serve_net::testing::respond(&app, b"GET /form HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert!(r.text().starts_with("<input type=hidden name=csrf_token value=\""));§Errors
500 csrf.no_engine without Builder::csrf;
500 csrf.no_binding when the identity has no binding.
Sourcepub fn precondition(
&self,
tag: Option<&str>,
modified: Option<SystemTime>,
) -> Result
pub fn precondition( &self, tag: Option<&str>, modified: Option<SystemTime>, ) -> Result
Checks If-Match and If-Unmodified-Since against the current
version of what an unsafe request would change (RFC 9110 section
13.2): tag is its entity tag’s opaque part, modified its
modification time. Call it before changing anything.
use web_net::{App, put};
let app = App::builder()
.route(put("/doc", |cx| {
cx.precondition(Some("v2"), None)?; // the stored version is "v2"
cx.text(200, "saved")
}).csrf_exempt("example"))
.build()?;
let send = |tag: &str| serve_net::testing::respond(&app, format!("PUT /doc HTTP/1.1\r\nHost: localhost\r\nIf-Match: \"{tag}\"\r\nContent-Length: 0\r\n\r\n").as_bytes()).status;
assert_eq!(send("v1"), 412);
assert_eq!(send("v2"), 200);§Errors
412 precondition.failed when a condition is false. Without either
field, Ok.
Sourcepub fn status(&mut self, status: u16) -> &mut Self
pub fn status(&mut self, status: u16) -> &mut Self
Sets the status (default 200) for a reply written by
render or reply_buffer;
html, text, and bytes
take theirs as an argument.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| {
cx.status(202).set_header("content-type", "text/plain");
cx.reply_buffer().extend_from_slice(b"queued");
Ok(())
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!((r.status, r.text()), (202, "queued"));Sourcepub fn set_header(&mut self, name: &str, value: impl AsRef<[u8]>) -> &mut Self
pub fn set_header(&mut self, name: &str, value: impl AsRef<[u8]>) -> &mut Self
Adds a reply field. A name that is not a token, a value with a
control character, or a framing field (content-length, …) turns
the reply into a 500 when the handler returns.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| {
cx.set_header("x-version", "7");
cx.text(200, "ok")
}))
.route(get("/bad", |cx| {
cx.set_header("content-length", "1"); // framing is serve-net's
cx.text(200, "ok")
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.header("x-version"), Some(&b"7"[..]));
let r = serve_net::testing::respond(&app, b"GET /bad HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.status, 500);Sourcepub fn bytes(&mut self, status: u16, content_type: &str, body: &[u8]) -> Result
pub fn bytes(&mut self, status: u16, content_type: &str, body: &[u8]) -> Result
Status, content_type, and the body. A content-type the handler
set with set_header wins.
use web_net::{App, get};
let app = App::builder()
.route(get("/logo.svg", |cx| cx.bytes(200, "image/svg+xml", b"<svg/>")))
.build()?;
let r = serve_net::testing::respond(&app, b"GET /logo.svg HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.header("content-type"), Some(&b"image/svg+xml"[..]));§Errors
None; it returns Result so a handler can end with it.
Sourcepub fn html(&mut self, status: u16, html: &str) -> Result
pub fn html(&mut self, status: u16, html: &str) -> Result
Status, content-type: text/html; charset=utf-8, and the HTML.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| cx.html(200, "<h1>Hello</h1>")))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!((r.status, r.text()), (200, "<h1>Hello</h1>"));§Errors
None; it returns Result so a handler can end with it.
Sourcepub fn text(&mut self, status: u16, text: &str) -> Result
pub fn text(&mut self, status: u16, text: &str) -> Result
Status, content-type: text/plain; charset=utf-8, and the text.
use web_net::{App, get};
let app = App::builder().route(get("/", |cx| cx.text(200, "ok"))).build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "ok");§Errors
None; it returns Result so a handler can end with it.
Sourcepub fn reply_buffer(&mut self) -> &mut Vec<u8> ⓘ
pub fn reply_buffer(&mut self) -> &mut Vec<u8> ⓘ
The reply body’s buffer, emptied, for a writer that appends to a
Vec<u8> (a template engine with its own API writing straight in).
Set the status with status and content-type with
set_header; the page budget does not apply here.
use std::io::Write;
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| {
write!(cx.reply_buffer(), "{} + {} = {}", 1, 2, 3).map_err(|_| web_net::Error::internal("write"))?;
cx.set_header("content-type", "text/plain");
Ok(())
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "1 + 2 = 3");Sourcepub fn accept(&self, offers: &[&'static str]) -> Option<&'static str>
pub fn accept(&self, offers: &[&'static str]) -> Option<&'static str>
The best of offers for the request’s Accept (["text/html", "application/json"]), or None when the request accepts none of
them.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| match cx.accept(&["text/html", "application/json"]) {
Some("application/json") => cx.bytes(200, "application/json", b"{}"),
_ => cx.html(200, "<p>hi</p>"),
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n");
assert_eq!(r.text(), "{}");Sourcepub fn accept_language(&self, offers: &[&'static str]) -> Option<&'static str>
pub fn accept_language(&self, offers: &[&'static str]) -> Option<&'static str>
The best of offers for the request’s Accept-Language
(["en", "fr"]), or None when the request states no preference
any offer meets.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| {
let lang = cx.accept_language(&["en", "fr"]).unwrap_or("en");
cx.text(200, lang)
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nAccept-Language: fr-CA, en;q=0.5\r\n\r\n");
assert_eq!(r.text(), "fr");Adds a Set-Cookie field built with cookie-net (re-exported as
web_net::cookie_net).
use web_net::{App, get, cookie_net::SetCookie};
let app = App::builder()
.route(get("/", |cx| {
cx.set_cookie(&SetCookie::new("theme", "dark").map_err(|_| web_net::Error::internal("cookie"))?);
cx.text(200, "ok")
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert!(r.header("set-cookie").is_some());Sourcepub fn not_modified(&mut self, tag: &str) -> bool
pub fn not_modified(&mut self, tag: &str) -> bool
The conditional fast path for a page: sets ETag: "<tag>" and, when
the request’s If-None-Match matches it (a GET or HEAD), turns the
reply into 304 Not Modified and returns true, so the handler
returns before building the page.
tag is the opaque part (letters, digits, and -._~ are safe), for
example a revision number. An unusable tag sets nothing and returns
false.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| {
if cx.not_modified("rev-7") {
return Ok(());
}
cx.html(200, "<h1>page</h1>")
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nIf-None-Match: \"rev-7\"\r\n\r\n");
assert_eq!(r.status, 304);Sourcepub fn url(&self, path: &str) -> Result<String>
pub fn url(&self, path: &str) -> Result<String>
The site path path as a URL path: the site’s base path in front
(/blog + /posts/1), so the site works installed in a folder.
path is used as it is (a query and a fragment included); for a
path built from a visitor’s input, use url_path,
which encodes each segment.
use web_net::{App, get};
let app = App::builder()
.base("/blog")
.route(get("/", |cx| {
let about = cx.url("/about")?;
cx.html(200, &format!("<a href=\"{about}\">about</a>"))
}))
.build()?;
let r = serve_net::testing::respond(&app, b"GET /blog/ HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "<a href=\"/blog/about\">about</a>");§Errors
500 url.not_local when path is not a path of this site: it must
start with one / (never // or /\, which browsers read as
another host) and hold only visible ASCII.
Sourcepub fn url_path(&self, segments: &[&str]) -> String
pub fn url_path(&self, segments: &[&str]) -> String
A URL path from segments, each percent-encoded as a path segment, the
base path in front: ["tags", tag] gives /blog/tags/a%2Fb for the
tag a/b. A segment cannot add a path level, a query, a fragment, or
another host (/, ?, #, %, and spaces are encoded; characters a
path segment allows, such as & and +, stay).
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| cx.text(200, &cx.url_path(&["tags", "c# & c++"]))))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "/tags/c%23%20&%20c++");Sourcepub fn next_path(&self) -> Result<Option<String>>
pub fn next_path(&self) -> Result<Option<String>>
The page a sign-in redirect asked for: the next query parameter
web-net puts on the redirect to the sign-in page, as a site-relative
path safe to pass to redirect once the visitor
signed in. None when there is none, or when it is not a path of this
site (an attacker’s link to the sign-in page cannot turn it into an
open redirect).
use web_net::{App, get};
let app = App::builder()
.base("/app")
.route(get("/login", |cx| {
let next = cx.next_path()?.unwrap_or_else(|| "/".into());
cx.text(200, &next)
}))
.build()?;
let at = |q: &str| serve_net::testing::respond(&app, format!("GET /app/login?{q} HTTP/1.1\r\nHost: localhost\r\n\r\n").as_bytes()).text().to_owned();
assert_eq!(at("next=/app/account%3Ftab%3D2"), "/account?tab=2");
assert_eq!(at("next=//evil.example"), "/");
assert_eq!(at("next=/elsewhere"), "/");§Errors
As query: 400 when next is given twice or does not
decode.
Sourcepub fn redirect_external(&mut self, status: u16, url: &str) -> Result
pub fn redirect_external(&mut self, status: u16, url: &str) -> Result
A redirect to another site, only to a host listed with
Builder::redirect_hosts:
https:// (or http://), the host, and a visible-ASCII rest.
use web_net::{App, get};
let app = App::builder()
.redirect_hosts(&["docs.example.com"])
.route(get("/docs", |cx| cx.redirect_external(302, "https://docs.example.com/start")))
.route(get("/away", |cx| cx.redirect_external(302, "https://elsewhere.example/")))
.build()?;
let r = serve_net::testing::respond(&app, b"GET /docs HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.header("location"), Some(&b"https://docs.example.com/start"[..]));
let r = serve_net::testing::respond(&app, b"GET /away HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.status, 500);§Errors
500 redirect.not_allowed for any other target, 500
redirect.status for a status that is not a redirect.
Sourcepub fn redirect(&mut self, status: u16, path: &str) -> Result
pub fn redirect(&mut self, status: u16, path: &str) -> Result
A redirect with status to a site-relative path, the site’s base
path put in front. 303 answers a form that was accepted, so a reload
does not post it again; 301 and 308 are permanent; 302 and 307
temporary.
use web_net::{App, post};
let app = App::builder()
.base("/app")
.route(post("/contact", |cx| cx.redirect(303, "/thanks")).csrf_exempt("example"))
.build()?;
let r = serve_net::testing::respond(&app, b"POST /app/contact HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n");
assert_eq!((r.status, r.header("location")), (303, Some(&b"/app/thanks"[..])));§Errors
500 redirect.not_local when path is not a local path (it must
start with one /): redirects to other hosts are never built from a
string a handler passes along (use
redirect_external). 500
redirect.status for a status that is not 301, 302, 303, 307, or 308.
Sourcepub fn render(&mut self, view: &E::View<'_>) -> Result
pub fn render(&mut self, view: &E::View<'_>) -> Result
Renders the route’s template (declared with
Route::template) against view into the
body. The status stays as set (200 by default); content-type is
HTML unless the handler set one.
use web_net::{App, Context, Guest, Output, Render, RenderError, TemplateId, get};
struct Page<'a> { title: &'a str }
struct Pages;
impl Render for Pages {
type View<'a> = Page<'a>;
fn template(&self, name: &str) -> Option<TemplateId> {
(name == "page").then_some(TemplateId(0))
}
fn render(&self, _: TemplateId, page: &Page<'_>, out: &mut Output<'_>) -> Result<(), RenderError> {
out.write_str(page.title) // a real engine escapes
}
}
let app = App::builder()
.render(Pages)
.route(get("/", |cx: &mut Context<'_, Guest, Pages>| cx.render(&Page { title: "Home" })).template("page"))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Home");
assert_eq!(r.header("content-type"), Some(&b"text/html; charset=utf-8"[..]));§Errors
500 render.no_engine or render.no_template when nothing can
render; the engine’s own RenderError as a 500;
a checkpoint’s denial.
Sourcepub fn render_named(&mut self, name: &str, view: &E::View<'_>) -> Result
pub fn render_named(&mut self, name: &str, view: &E::View<'_>) -> Result
Renders the template name against view. The name is resolved by
the engine on every call; prefer render with a
template declared on the route, resolved once at build.
use web_net::{App, Context, Guest, Output, Render, RenderError, TemplateId, get};
struct Page<'a> { title: &'a str }
struct Pages;
impl Render for Pages {
type View<'a> = Page<'a>;
fn template(&self, name: &str) -> Option<TemplateId> {
(name == "page").then_some(TemplateId(0))
}
fn render(&self, _: TemplateId, page: &Page<'_>, out: &mut Output<'_>) -> Result<(), RenderError> {
out.write_str(page.title) // a real engine escapes
}
}
let app = App::builder()
.render(Pages)
.route(get("/", |cx: &mut Context<'_, Guest, Pages>| cx.render_named("page", &Page { title: "Named" })))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Named");§Errors
As render.
Source§impl<I, E> Context<'_, I, E>
impl<I, E> Context<'_, I, E>
Sourcepub fn render_view(&mut self, view: &dyn View) -> Result
pub fn render_view(&mut self, view: &dyn View) -> Result
render for an engine that reads any View (a
dynamic engine): takes &dyn View, so a Fields
or any view of yours goes in as it is.
use web_net::{App, Fields, Output, Render, RenderError, TemplateId, Value, View, get};
/// Writes the view's `title` field.
struct Echo;
impl Render for Echo {
type View<'a> = dyn View + 'a;
fn template(&self, _: &str) -> Option<TemplateId> { Some(TemplateId(0)) }
fn render(&self, _: TemplateId, view: &dyn View, out: &mut Output<'_>) -> Result<(), RenderError> {
let key = view.shape().key("title").ok_or(RenderError::new("echo.no_title"))?;
out.write_str(view.get(key).as_text().unwrap_or("")) // a real engine escapes
}
}
let app = App::builder()
.render(Echo)
.route(get("/", |cx| cx.render_view(&Fields(&[("title", Value::Text("Home"))]))).template("page"))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Home");§Errors
As render.
Sourcepub fn render_view_named(&mut self, name: &str, view: &dyn View) -> Result
pub fn render_view_named(&mut self, name: &str, view: &dyn View) -> Result
render_named for an engine that reads any
View.
use web_net::{App, Fields, Output, Render, RenderError, TemplateId, Value, View, get};
/// Writes the view's `title` field.
struct Echo;
impl Render for Echo {
type View<'a> = dyn View + 'a;
fn template(&self, _: &str) -> Option<TemplateId> { Some(TemplateId(0)) }
fn render(&self, _: TemplateId, view: &dyn View, out: &mut Output<'_>) -> Result<(), RenderError> {
let key = view.shape().key("title").ok_or(RenderError::new("echo.no_title"))?;
out.write_str(view.get(key).as_text().unwrap_or("")) // a real engine escapes
}
}
let app = App::builder()
.render(Echo)
.route(get("/", |cx| cx.render_view_named("page", &Fields(&[("title", Value::Text("Named"))]))))
.build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Named");§Errors
As render.