web_net/sites.rs
1//! Several sites in one server: the host and the base path pick the
2//! application.
3
4use serve_net::{Envelope, HttpReply, HttpRequest, HttpService, Outcome};
5
6use crate::{App, Gate, NoRender, Open, Render, Served};
7
8/// A table of applications, chosen per request by host and base path: an
9/// application that lists the request's host (or an alias of it) wins over
10/// one that answers any host, and among those, the longest base path that
11/// holds the request's path. A request no application claims goes to the
12/// first one added, which answers it by its own rules (421 for a host it
13/// does not serve, 404 outside its base).
14///
15/// ```
16/// use web_net::{App, Sites, get};
17///
18/// let blog = App::builder()
19/// .host("example.com")
20/// .base("/blog")
21/// .route(get("/", |cx| { cx.text(200, "blog"); Ok(()) }).public())
22/// .build()?;
23/// let shop = App::builder()
24/// .host("shop.example.com")
25/// .route(get("/", |cx| { cx.text(200, "shop"); Ok(()) }).public())
26/// .build()?;
27/// let main = App::builder()
28/// .host("example.com")
29/// .route(get("/", |cx| { cx.text(200, "home"); Ok(()) }).public())
30/// .build()?;
31/// let sites = Sites::new().site(main).site(blog).site(shop);
32///
33/// use serve_net::testing::respond;
34/// assert_eq!(respond(&sites, b"GET /blog/ HTTP/1.1\r\nHost: example.com\r\n\r\n").text(), "blog");
35/// assert_eq!(respond(&sites, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n").text(), "home");
36/// assert_eq!(respond(&sites, b"GET / HTTP/1.1\r\nHost: shop.example.com\r\n\r\n").text(), "shop");
37/// assert_eq!(respond(&sites, b"GET / HTTP/1.1\r\nHost: other.example\r\n\r\n").status, 421);
38/// # Ok::<(), web_net::BuildError>(())
39/// ```
40pub struct Sites<G: Gate = Open, E: Render = NoRender> {
41 apps: Vec<App<G, E>>,
42}
43
44impl<G: Gate, E: Render> Default for Sites<G, E> {
45 fn default() -> Self {
46 Self { apps: Vec::new() }
47 }
48}
49
50impl<G: Gate, E: Render> Sites<G, E> {
51 /// An empty table.
52 #[must_use]
53 pub fn new() -> Self {
54 Self::default()
55 }
56
57 /// Adds an application. The first one added answers what no other
58 /// claims.
59 #[must_use]
60 pub fn site(mut self, app: App<G, E>) -> Self {
61 self.apps.push(app);
62 self
63 }
64
65 /// The application for a request, if the table has any.
66 #[must_use]
67 pub fn pick(&self, req: &HttpRequest<'_>) -> Option<&App<G, E>> {
68 let authority = req.head().authority();
69 let path = req.path();
70 self.apps
71 .iter()
72 .filter_map(|a| a.claim(authority, path).map(|score| (score, a)))
73 .max_by_key(|(score, _)| *score)
74 .map(|(_, a)| a)
75 .or_else(|| self.apps.first())
76 }
77
78 /// As [`App::serve`] on the chosen application.
79 pub fn serve(
80 &self,
81 req: &HttpRequest<'_>,
82 env: &Envelope,
83 who: Option<&G::Identity>,
84 reply: &mut HttpReply,
85 ) -> Served {
86 match self.pick(req) {
87 Some(app) => app.serve(req, env, who, reply),
88 None => {
89 reply.text(421, "no site is configured");
90 Served {
91 outcome: Outcome::Done,
92 route: None,
93 cache: crate::Cacheable::No,
94 }
95 }
96 }
97 }
98}
99
100impl<G: Gate, E: Render> HttpService for Sites<G, E> {
101 fn call(&self, request: &HttpRequest<'_>, env: &Envelope, reply: &mut HttpReply) -> Outcome {
102 self.serve(request, env, None, reply).outcome
103 }
104}
105
106impl<G: Gate, E: Render> std::fmt::Debug for Sites<G, E> {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.debug_struct("Sites").field("apps", &self.apps).finish()
109 }
110}