web_net/lib.rs
1//! # web_net
2//!
3//! The web layer on serve-net: what a site is written in.
4//!
5//! A request runs one fixed path, and each step either answers or hands on:
6//!
7//! 1. **site**: the `Host` picks the site (unknown hosts get 421, aliases a
8//! redirect to the canonical host), and its base path is removed, so a
9//! site works installed in a folder.
10//! 2. **url**: the path is decoded once, dot segments are removed, empty
11//! segments collapse, and the trailing-slash policy applies. A path that
12//! was not spelled canonically is redirected to the spelling that is, so
13//! two spellings never reach different routes. An encoded slash, an
14//! encoded backslash, a control character, or bytes that are not UTF-8
15//! are refused with 400.
16//! 3. **authenticate**: the [`Gate`] says who is asking (or the caller did,
17//! with [`App::respond_identified`]).
18//! 4. **route**: a trie compiled at build time matches the path (404 when
19//! nothing does).
20//! 5. **method**: `HEAD` follows `GET`, `OPTIONS` is answered for every
21//! route, anything else the route does not take is a 405 with `Allow`.
22//! 6. **authorize**: the route's rule, checked by the gate. Every route
23//! declares its access, [`public`](Route::public) or
24//! [`allow`](Route::allow); a route without one stops the build.
25//! 7. **hooks and handler**: registered [`Hook`]s for this route, then the
26//! handler, which receives a [`Cx`]: proof the request passed its gate.
27//! 8. **render**: a [`Render`] engine writes the page from a typed [`View`],
28//! no JSON in between.
29//! 9. **errors**: every refusal and error is answered by one function, with
30//! the site's error template or a built-in page, and reported through
31//! [`Events`].
32//!
33//! ```
34//! use web_net::{App, Fields, Value, get};
35//!
36//! let app = App::builder()
37//! .host("example.com")
38//! .alias("www.example.com")
39//! .base("/blog")
40//! .route(get("/", |cx| { cx.html(200, "<h1>Blog</h1>"); Ok(()) }).public())
41//! .route(get("/posts/:slug", |cx| {
42//! let slug = cx.param("slug").unwrap_or_default();
43//! cx.text(200, &slug);
44//! Ok(())
45//! }).public())
46//! .build()?;
47//!
48//! use serve_net::testing::respond;
49//! let r = respond(&app, b"GET /blog/posts/hello HTTP/1.1\r\nHost: example.com\r\n\r\n");
50//! assert_eq!((r.status, r.text()), (200, "hello"));
51//!
52//! let r = respond(&app, b"GET /blog/./posts//hello/ HTTP/1.1\r\nHost: example.com\r\n\r\n");
53//! assert_eq!((r.status, r.header("location")), (301, Some(&b"/blog/posts/hello"[..])));
54//!
55//! let r = respond(&app, b"GET /blog HTTP/1.1\r\nHost: www.example.com\r\n\r\n");
56//! assert_eq!(r.header("location"), Some(&b"http://example.com/blog/"[..]));
57//!
58//! let r = respond(&app, b"GET / HTTP/1.1\r\nHost: other.example\r\n\r\n");
59//! assert_eq!(r.status, 421);
60//! # Ok::<(), web_net::BuildError>(())
61//! ```
62//!
63//! On a server: `serve_net::Server::builder()...serve(app)`, or
64//! `serve_net::http("127.0.0.1:8080", ...)` for a quick start with a closure.
65
66#![doc(html_root_url = "https://docs.rs/web-net")]
67#![cfg_attr(docsrs, feature(doc_cfg))]
68#![forbid(unsafe_code)]
69#![deny(missing_docs)]
70
71mod app;
72mod assets;
73mod cors;
74mod csrf;
75mod cx;
76mod error;
77mod events;
78mod gate;
79mod headers;
80mod policy;
81mod render;
82mod request;
83mod route;
84mod router;
85mod sites;
86mod url;
87
88pub use app::{App, Builder, Classified, DEFAULT_PAGE_LIMIT, Hook, RouteId, Routes};
89pub use assets::{Assets, INLINE_LIMIT, mime_for};
90pub use cors::Cors;
91pub use cx::Cx;
92pub use error::{BuildError, Error, Problem, Result};
93pub use events::{Checkpoint, Event, EventKind, Events, NoEvents, Stage};
94pub use gate::{Denial, DenialKind, Gate, Guest, Identity, NoRule, Open};
95pub use headers::{DEFAULT_CSP, SecurityHeaders};
96pub use policy::{CachePolicy, Cacheable, Compression, Served};
97pub use render::{
98 ErrorPage, Fields, Key, List, NoRender, Output, Render, RenderError, SafeHtml, Shape, ShapeId,
99 TemplateId, Value, View,
100};
101pub use request::{Ambiguous, Request, RequestId};
102pub use route::{Group, Handler, Route, delete, get, patch, post, put};
103pub use router::MAX_PARAMS;
104pub use sites::Sites;
105pub use url::{TrailingSlash, UrlError};
106
107/// cookie-net, at the version web-net uses, for [`Cx::set_cookie`].
108pub use cookie_net;
109/// csrf-sec, at the version web-net uses, for [`Builder::csrf`].
110pub use csrf_sec;
111/// form-net, at the version web-net uses, for [`Cx::form`] and its `form!`
112/// macro.
113pub use form_net;
114
115/// Crate version string, populated by Cargo at build time.
116pub const VERSION: &str = env!("CARGO_PKG_VERSION");
117
118#[cfg(test)]
119mod tests {
120 use super::VERSION;
121
122 #[test]
123 fn version_matches_manifest() {
124 assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
125 assert_eq!(VERSION.split('.').count(), 3, "expected MAJOR.MINOR.PATCH");
126 }
127}