pub struct App<G: Gate = Open, E: Render = NoRender> { /* private fields */ }Expand description
A web application: one site, its routes, and its gate. Built once at
start-up with App::builder or App::with_gate; immutable after.
listen serves it on an address. It is also a
serve_net::HttpService, so it runs on any serve-net server as it is,
and respond runs it from a wrapper’s own service.
use web_net::{App, get};
let app = App::builder()
.route(get("/", |cx| cx.html(200, "<h1>Hello</h1>")))
.route(get("/users/:id", |cx| {
let id: u64 = cx.param_as("id")?;
cx.text(200, &format!("user {id}"))
}))
.build()?;
let reply = serve_net::testing::respond(&app, b"GET /users/7 HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!((reply.status, reply.text()), (200, "user 7"));
let reply = serve_net::testing::respond(&app, b"GET /users/7/ HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(reply.status, 301);
assert_eq!(reply.header("location"), Some(&b"/users/7"[..]));Implementations§
Source§impl App<Open>
impl App<Open>
Sourcepub fn builder() -> Builder<Open>
pub fn builder() -> Builder<Open>
A builder with the Open gate: every visitor is a guest and every
route is public, so routes declare no access.
use web_net::{App, get};
let app = App::builder().route(get("/", |cx| cx.text(200, "hi"))).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, "hi"));Source§impl<G: Gate> App<G>
impl<G: Gate> App<G>
Sourcepub fn with_gate(gate: G) -> Builder<G>
pub fn with_gate(gate: G) -> Builder<G>
A builder with gate for authentication and authorization. Every
route then declares its access, public or
allow(rule), or the build fails.
use serve_net::testing::respond;
use web_net::{App, Denial, Gate, Guest, Request, get};
enum Rule { Staff }
struct NoStaff;
impl Gate for NoStaff {
type Identity = Guest;
type Rule = Rule;
fn authenticate(&self, _: &Request<'_>) -> Result<Guest, Denial> { Ok(Guest) }
fn authorize(&self, _: &Guest, _: &Rule, _: &Request<'_>) -> Result<(), Denial> {
Err(Denial::forbidden("staff.only"))
}
}
// A route that declares no access stops the build.
assert!(App::with_gate(NoStaff).route(get("/", |cx| cx.text(200, "hi"))).build().is_err());
let app = App::with_gate(NoStaff)
.route(get("/", |cx| cx.text(200, "hi")).public())
.route(get("/admin", |cx| cx.text(200, "admin")).allow(Rule::Staff))
.build()?;
assert_eq!(respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n").status, 200);
assert_eq!(respond(&app, b"GET /admin HTTP/1.1\r\nHost: localhost\r\n\r\n").status, 403);Source§impl<G: Gate, E: Render> App<G, E>
impl<G: Gate, E: Render> App<G, E>
Sourcepub fn listen(self, addr: impl ToSocketAddrs) -> Result<Running, ServeError>
pub fn listen(self, addr: impl ToSocketAddrs) -> Result<Running, ServeError>
Serves the application on addr ("127.0.0.1:8080"): one TCP
listener per address it resolves to, with serve-net’s default
limits, keep-alive, and workers. For TLS, several listeners, or
other limits, hand the app to serve_net::Server::builder()...serve(app).
use web_net::{App, get};
App::builder()
.route(get("/", |cx| cx.html(200, "<h1>Hello</h1>")))
.build()?
.listen("127.0.0.1:8080")?
.wait();§Errors
serve-net’s ServeError: the address does
not resolve, or a port cannot be bound.
Sourcepub fn respond(
&self,
req: &HttpRequest<'_>,
env: &Envelope,
who: Option<&G::Identity>,
reply: &mut HttpReply,
) -> Served
pub fn respond( &self, req: &HttpRequest<'_>, env: &Envelope, who: Option<&G::Identity>, reply: &mut HttpReply, ) -> Served
Runs the pipeline for one request, for a wrapper that holds the app
(a gateway). who is Some when the wrapper authenticated the
request already: web-net takes it as given and does not call the
gate’s authenticate (nor write the gate’s cookie). Authorization
still runs for every route that is not public. None runs the whole
pipeline, as serve-net’s HttpService::call does.
The report says what ran and whether a cache in front may keep the reply for guests.
A wrapper forwards serve-net’s HttpService::done to the app as
well (HttpService::done(&app, env, finished)): a large file sent in
parts keeps a record until its last part or until done, and records
of downloads the client dropped are freed only there. Without it they
accumulate until new large-file replies are refused with 503.
use serve_net::{Envelope, HttpReply, HttpRequest, HttpService, Outcome};
use web_net::{App, Guest, get};
// A front that authenticated every request already.
struct Front(App);
impl HttpService for Front {
fn call(&self, req: &HttpRequest<'_>, env: &Envelope, reply: &mut HttpReply) -> Outcome {
self.0.respond(req, env, Some(&Guest), reply).outcome
}
}
let front = Front(App::builder().route(get("/", |cx| cx.text(200, "hi"))).build()?);
let r = serve_net::testing::respond(&front, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "hi");Sourcepub fn classify(&self, head: &HttpHead<'_>) -> Classified
pub fn classify(&self, head: &HttpHead<'_>) -> Classified
Looks at a head before admission: which route would run and how it behaves. Nothing is answered and no gate is called.
use serve_net::WorkClass;
use web_net::{App, get};
use serve_net::{Envelope, HttpReply, HttpRequest, HttpService, Outcome};
// A dispatcher in front of the app that answers with what `classify` said.
struct Probe(App);
impl HttpService for Probe {
fn call(&self, req: &HttpRequest<'_>, _: &Envelope, reply: &mut HttpReply) -> Outcome {
let c = self.0.classify(req.head());
reply.text(200, &format!("{:?} {:?}", c.route, c.work));
Outcome::Done
}
}
let app = App::builder()
.route(get("/report", |cx| cx.text(200, "report")).work(WorkClass::Io))
.build()?;
let r = serve_net::testing::respond(&Probe(app), b"GET /report HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Some(RouteId(0)) Io");Trait Implementations§
Source§impl<G: Gate, E: Render> HttpService for App<G, E>
impl<G: Gate, E: Render> HttpService for App<G, E>
Source§fn done(&self, env: &Envelope, _finished: &Finished)
fn done(&self, env: &Envelope, _finished: &Finished)
Frees what the request held: the record of a large file going out in parts, when the client left before its end.
Source§fn call(
&self,
request: &HttpRequest<'_>,
env: &Envelope,
reply: &mut HttpReply,
) -> Outcome
fn call( &self, request: &HttpRequest<'_>, env: &Envelope, reply: &mut HttpReply, ) -> Outcome
Source§fn head(&self, head: &HttpHead<'_>, env: &mut Envelope) -> HeadAction
fn head(&self, head: &HttpHead<'_>, env: &mut Envelope) -> HeadAction
Source§fn upgraded(&self, env: &Envelope, conn: UpgradedConn)
fn upgraded(&self, env: &Envelope, conn: UpgradedConn)
head left,
and after done for that request. The conversation itself belongs to
the upgrade target (a WebSocketService, feature ws).