Skip to main content

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}