Skip to main content

App

Struct App 

Source
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>

Source

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>

Source

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>

Source

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.

Source

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");
Source

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");
Source

pub fn gate(&self) -> &G

The gate.

use web_net::{App, Open};

let app = App::builder().build()?;
let gate: &Open = app.gate();

Trait Implementations§

Source§

impl<G: Gate, E: Render> Debug for App<G, E>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<G: Gate, E: Render> HttpService for App<G, E>

Source§

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

The request is complete.
Source§

const WORK: WorkClass = WorkClass::Quick

How the service behaves.
Source§

fn head(&self, head: &HttpHead<'_>, env: &mut Envelope) -> HeadAction

The head is complete; before admission and before any body byte.
Source§

fn upgraded(&self, env: &Envelope, conn: UpgradedConn)

After the 101 for an upgrade this service asked for in head left, and after done for that request. The conversation itself belongs to the upgrade target (a WebSocketService, feature ws).

Auto Trait Implementations§

§

impl<G = Open, E = NoRender> !Freeze for App<G, E>

§

impl<G = Open, E = NoRender> !RefUnwindSafe for App<G, E>

§

impl<G = Open, E = NoRender> !UnwindSafe for App<G, E>

§

impl<G, E> Forget for App<G, E>

§

impl<G, E> Move for App<G, E>

§

impl<G, E> Send for App<G, E>
where Shared<E>: Send, Vec<Entry<G, E>>: Send,

§

impl<G, E> Sync for App<G, E>
where Shared<E>: Sync, Vec<Entry<G, E>>: Sync,

§

impl<G, E> Unpin for App<G, E>
where G: Unpin, Shared<E>: Unpin, Vec<Entry<G, E>>: Unpin,

§

impl<G, E> UnsafeUnpin for App<G, E>
where G: UnsafeUnpin, Shared<E>: UnsafeUnpin, Vec<Entry<G, E>>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<S> Service<HttpFamily> for S
where S: HttpService,

Source§

const WORK: WorkClass = <S as HttpService>::WORK

How the service behaves.
Source§

fn head(&self, head: &HttpHead<'_>, env: &mut Envelope) -> HeadAction

The head is complete; before admission and any body byte.
Source§

fn call( &self, request: &HttpRequest<'_>, env: &Envelope, reply: &mut Reply<HttpFamily>, ) -> Outcome

The request is complete.
Source§

fn upgraded(&self, env: &Envelope, conn: UpgradedConn)

After a switch response the service asked for left.
Source§

fn done(&self, env: &Envelope, finished: &Finished)

Exactly once per request that reached head.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.