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")))
22/// .build()?;
23/// let shop = App::builder()
24/// .host("shop.example.com")
25/// .route(get("/", |cx| cx.text(200, "shop")))
26/// .build()?;
27/// let main = App::builder()
28/// .host("example.com")
29/// .route(get("/", |cx| cx.text(200, "home")))
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 ///
53 /// ```
54 /// use web_net::Sites;
55 ///
56 /// // With no application, every request is answered with 421.
57 /// let sites: Sites = Sites::new();
58 /// let r = serve_net::testing::respond(&sites, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
59 /// assert_eq!(r.status, 421);
60 /// ```
61 #[must_use]
62 pub fn new() -> Self {
63 Self::default()
64 }
65
66 /// Adds an application. The first one added answers what no other
67 /// claims.
68 ///
69 /// ```
70 /// use serve_net::testing::respond;
71 /// use web_net::{App, Sites, get};
72 ///
73 /// let main = App::builder().host("example.com").route(get("/", |cx| cx.text(200, "home"))).build()?;
74 /// let shop = App::builder().host("shop.example.com").route(get("/", |cx| cx.text(200, "shop"))).build()?;
75 /// let sites = Sites::new().site(main).site(shop);
76 /// assert_eq!(respond(&sites, b"GET / HTTP/1.1\r\nHost: shop.example.com\r\n\r\n").text(), "shop");
77 /// assert_eq!(respond(&sites, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n").text(), "home");
78 /// # Ok::<(), web_net::BuildError>(())
79 /// ```
80 #[must_use]
81 pub fn site(mut self, app: App<G, E>) -> Self {
82 self.apps.push(app);
83 self
84 }
85
86 /// The application for a request, if the table has any.
87 ///
88 /// ```
89 /// use serve_net::{Envelope, HttpReply, HttpRequest, HttpService, Outcome};
90 /// use web_net::{App, Sites, get};
91 ///
92 /// // A front that picks the application itself before it answers.
93 /// struct Front(Sites);
94 /// impl HttpService for Front {
95 /// fn call(&self, req: &HttpRequest<'_>, env: &Envelope, reply: &mut HttpReply) -> Outcome {
96 /// let app = self.0.pick(req).expect("one site is configured");
97 /// app.respond(req, env, None, reply).outcome
98 /// }
99 /// }
100 ///
101 /// let shop = App::builder().host("shop.example.com").route(get("/", |cx| cx.text(200, "shop"))).build()?;
102 /// let front = Front(Sites::new().site(shop));
103 /// let r = serve_net::testing::respond(&front, b"GET / HTTP/1.1\r\nHost: shop.example.com\r\n\r\n");
104 /// assert_eq!(r.text(), "shop");
105 /// # Ok::<(), web_net::BuildError>(())
106 /// ```
107 #[must_use]
108 pub fn pick(&self, req: &HttpRequest<'_>) -> Option<&App<G, E>> {
109 let authority = req.head().authority();
110 let path = req.path();
111 self.apps
112 .iter()
113 .filter_map(|a| a.claim(authority, path).map(|score| (score, a)))
114 .max_by_key(|(score, _)| *score)
115 .map(|(_, a)| a)
116 .or_else(|| self.apps.first())
117 }
118
119 /// Serves every site on `addr`, as [`App::listen`] does for one.
120 ///
121 /// # Errors
122 ///
123 /// As [`App::listen`].
124 ///
125 /// ```no_run
126 /// use web_net::{App, Sites, get};
127 ///
128 /// let main = App::builder().host("example.com").route(get("/", |cx| cx.text(200, "home"))).build()?;
129 /// let shop = App::builder().host("shop.example.com").route(get("/", |cx| cx.text(200, "shop"))).build()?;
130 /// Sites::new().site(main).site(shop).listen("127.0.0.1:8080")?.wait();
131 /// # Ok::<(), Box<dyn std::error::Error>>(())
132 /// ```
133 pub fn listen(
134 self,
135 addr: impl std::net::ToSocketAddrs,
136 ) -> std::result::Result<serve_net::Running, serve_net::ServeError> {
137 crate::app::listen(addr, self)
138 }
139
140 /// As [`App::respond`] on the chosen application.
141 ///
142 /// ```
143 /// use serve_net::{Envelope, HttpReply, HttpRequest, HttpService, Outcome};
144 /// use web_net::{App, Guest, Sites, get};
145 ///
146 /// // A front that authenticated every request already.
147 /// struct Front(Sites);
148 /// impl HttpService for Front {
149 /// fn call(&self, req: &HttpRequest<'_>, env: &Envelope, reply: &mut HttpReply) -> Outcome {
150 /// self.0.respond(req, env, Some(&Guest), reply).outcome
151 /// }
152 /// }
153 ///
154 /// let app = App::builder().route(get("/", |cx| cx.text(200, "hi"))).build()?;
155 /// let r = serve_net::testing::respond(&Front(Sites::new().site(app)), b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
156 /// assert_eq!(r.text(), "hi");
157 /// # Ok::<(), web_net::BuildError>(())
158 /// ```
159 pub fn respond(
160 &self,
161 req: &HttpRequest<'_>,
162 env: &Envelope,
163 who: Option<&G::Identity>,
164 reply: &mut HttpReply,
165 ) -> Served {
166 match self.pick(req) {
167 Some(app) => app.respond(req, env, who, reply),
168 None => {
169 reply.text(421, "no site is configured");
170 Served {
171 outcome: Outcome::Done,
172 route: None,
173 cache: crate::Cacheable::No,
174 }
175 }
176 }
177 }
178}
179
180impl<G: Gate, E: Render> HttpService for Sites<G, E> {
181 fn call(&self, request: &HttpRequest<'_>, env: &Envelope, reply: &mut HttpReply) -> Outcome {
182 self.respond(request, env, None, reply).outcome
183 }
184
185 fn done(&self, env: &Envelope, _finished: &serve_net::Finished) {
186 for app in &self.apps {
187 app.forget(env);
188 }
189 }
190}
191
192impl<G: Gate, E: Render> std::fmt::Debug for Sites<G, E> {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 f.debug_struct("Sites").field("apps", &self.apps).finish()
195 }
196}