web_net/request.rs
1//! The request as gates and handlers read it.
2
3use std::fmt;
4
5use serve_net::{ClientInfo, Envelope, HttpRequest, Method, Scheme};
6
7/// A request's reference: shown on error pages and carried by every event,
8/// so a visitor's report finds the log line.
9///
10/// It displays as 16 hexadecimal digits, a keyed hash of the connection id
11/// and the request's number with a key made at start-up: the same request
12/// always shows the same reference in one process, and two references say
13/// nothing about how much traffic came between them (the raw counters
14/// would). [`connection`](Self::connection) and [`seq`](Self::seq) give the
15/// raw values for the logs. Not a security token.
16///
17/// ```
18/// use web_net::{App, get};
19///
20/// let app = App::builder().route(get("/", |cx| cx.text(200, &cx.id().to_string()))).build()?;
21/// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
22/// assert_eq!(r.text().len(), 16);
23/// # Ok::<(), web_net::BuildError>(())
24/// ```
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub struct RequestId {
27 connection: u64,
28 seq: u32,
29}
30
31impl RequestId {
32 pub(crate) fn of(env: &Envelope) -> Self {
33 Self {
34 connection: env.connection().get(),
35 seq: env.seq().get(),
36 }
37 }
38
39 /// The connection id.
40 ///
41 /// ```
42 /// use web_net::{App, get};
43 ///
44 /// let app = App::builder()
45 /// .route(get("/", |cx| {
46 /// let connection = cx.id().connection().to_string();
47 /// cx.text(200, &connection)
48 /// }))
49 /// .build()?;
50 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
51 /// assert!(r.text().parse::<u64>().is_ok());
52 /// # Ok::<(), web_net::BuildError>(())
53 /// ```
54 #[must_use]
55 pub const fn connection(&self) -> u64 {
56 self.connection
57 }
58
59 /// The request's number on its connection.
60 ///
61 /// ```
62 /// use web_net::{App, get};
63 ///
64 /// let app = App::builder()
65 /// .route(get("/", |cx| {
66 /// let seq = cx.id().seq().to_string();
67 /// cx.text(200, &seq)
68 /// }))
69 /// .build()?;
70 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
71 /// assert_eq!(r.text(), "1"); // the first request on its connection
72 /// # Ok::<(), web_net::BuildError>(())
73 /// ```
74 #[must_use]
75 pub const fn seq(&self) -> u32 {
76 self.seq
77 }
78}
79
80/// The process's key for references, made once from the standard
81/// library's randomly keyed hasher.
82fn reference_key() -> &'static std::collections::hash_map::RandomState {
83 static KEY: std::sync::OnceLock<std::collections::hash_map::RandomState> =
84 std::sync::OnceLock::new();
85 KEY.get_or_init(std::collections::hash_map::RandomState::new)
86}
87
88impl fmt::Display for RequestId {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 use std::hash::BuildHasher;
91 let h = reference_key().hash_one((self.connection, self.seq));
92 write!(f, "{h:016x}")
93 }
94}
95
96/// A `Cookie` field held the name more than once, or the request carried
97/// more than one `Cookie` field. A gate treats this as no credential, or as
98/// an attack; it never picks one of the values.
99///
100/// ```
101/// use web_net::{Ambiguous, App, get};
102///
103/// let app = App::builder()
104/// .route(get("/", |cx| match cx.request().cookie("sid") {
105/// Err(Ambiguous) => cx.text(400, "sid appears twice"),
106/// Ok(sid) => cx.text(200, sid.unwrap_or("none")),
107/// }))
108/// .build()?;
109/// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nCookie: sid=a; sid=b\r\n\r\n");
110/// assert_eq!(r.status, 400);
111/// # Ok::<(), web_net::BuildError>(())
112/// ```
113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub struct Ambiguous;
115
116impl fmt::Display for Ambiguous {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 f.write_str("the cookie appears more than once")
119 }
120}
121
122impl std::error::Error for Ambiguous {}
123
124/// The request as gates and handlers read it: the parts web-net has checked
125/// (the host matched a site, the path is canonical) and the raw request
126/// underneath.
127///
128/// ```
129/// use web_net::{App, get};
130///
131/// let app = App::builder()
132/// .route(get("/", |cx| {
133/// let req = cx.request();
134/// let line = format!("{} {}", req.host(), req.path());
135/// cx.text(200, &line)
136/// }))
137/// .build()?;
138/// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
139/// assert_eq!(r.text(), "localhost /");
140/// # Ok::<(), web_net::BuildError>(())
141/// ```
142#[derive(Clone, Copy)]
143pub struct Request<'r> {
144 pub(crate) http: &'r HttpRequest<'r>,
145 pub(crate) env: &'r Envelope,
146 pub(crate) path: &'r str,
147 pub(crate) host: &'r str,
148 pub(crate) route: Option<&'r str>,
149 pub(crate) tls: bool,
150 pub(crate) json: bool,
151}
152
153impl<'r> Request<'r> {
154 /// The method.
155 ///
156 /// ```
157 /// use serve_net::Method;
158 /// use web_net::{App, post};
159 ///
160 /// let app = App::builder()
161 /// .route(post("/", |cx| {
162 /// let post = cx.request().method() == Method::Post;
163 /// cx.text(200, if post { "post" } else { "other" })
164 /// }).csrf_exempt("example"))
165 /// .build()?;
166 /// let r = serve_net::testing::respond(&app, b"POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n");
167 /// assert_eq!(r.text(), "post");
168 /// # Ok::<(), web_net::BuildError>(())
169 /// ```
170 #[must_use]
171 pub fn method(&self) -> Method {
172 self.http.method()
173 }
174
175 /// The canonical path inside the site: the base path removed, percent
176 /// escapes in their one canonical form. `/` for the site root.
177 ///
178 /// ```
179 /// use web_net::{App, get};
180 ///
181 /// let app = App::builder()
182 /// .base("/app")
183 /// .route(get("/about", |cx| cx.text(200, cx.request().path())))
184 /// .build()?;
185 /// let r = serve_net::testing::respond(&app, b"GET /app/about HTTP/1.1\r\nHost: localhost\r\n\r\n");
186 /// assert_eq!(r.text(), "/about");
187 /// # Ok::<(), web_net::BuildError>(())
188 /// ```
189 #[must_use]
190 pub fn path(&self) -> &'r str {
191 self.path
192 }
193
194 /// The query after `?`, as received (validated by the parser, not
195 /// decoded).
196 ///
197 /// ```
198 /// use web_net::{App, get};
199 ///
200 /// let app = App::builder()
201 /// .route(get("/search", |cx| cx.text(200, cx.request().query().unwrap_or(""))))
202 /// .build()?;
203 /// let r = serve_net::testing::respond(&app, b"GET /search?q=red+shoes HTTP/1.1\r\nHost: localhost\r\n\r\n");
204 /// assert_eq!(r.text(), "q=red+shoes"); // not decoded
205 /// # Ok::<(), web_net::BuildError>(())
206 /// ```
207 #[must_use]
208 pub fn query(&self) -> Option<&'r str> {
209 self.http.query()
210 }
211
212 /// The host the request named, as received (empty for an HTTP/1.0
213 /// request without one). It matched one of the site's hosts, unless the
214 /// site accepts any host.
215 ///
216 /// ```
217 /// use web_net::{App, get};
218 ///
219 /// let app = App::builder()
220 /// .route(get("/", |cx| cx.text(200, cx.request().host())))
221 /// .build()?;
222 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
223 /// assert_eq!(r.text(), "localhost");
224 /// # Ok::<(), web_net::BuildError>(())
225 /// ```
226 #[must_use]
227 pub fn host(&self) -> &'r str {
228 self.host
229 }
230
231 /// The first value of a field.
232 ///
233 /// ```
234 /// use web_net::{App, get};
235 ///
236 /// let app = App::builder()
237 /// .route(get("/", |cx| {
238 /// let accept = cx.request().header("accept").unwrap_or(b"*/*");
239 /// cx.bytes(200, "text/plain", accept)
240 /// }))
241 /// .build()?;
242 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nAccept: text/html\r\n\r\n");
243 /// assert_eq!(r.text(), "text/html");
244 /// # Ok::<(), web_net::BuildError>(())
245 /// ```
246 #[must_use]
247 pub fn header(&self, name: &str) -> Option<&'r [u8]> {
248 self.http.header(name)
249 }
250
251 /// The first value of a field, if it is UTF-8.
252 ///
253 /// ```
254 /// use web_net::{App, get};
255 ///
256 /// let app = App::builder()
257 /// .route(get("/", |cx| {
258 /// let agent = cx.request().header_str("user-agent").unwrap_or("unknown");
259 /// cx.text(200, agent)
260 /// }))
261 /// .build()?;
262 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nUser-Agent: demo/1\r\n\r\n");
263 /// assert_eq!(r.text(), "demo/1");
264 /// # Ok::<(), web_net::BuildError>(())
265 /// ```
266 #[must_use]
267 pub fn header_str(&self, name: &str) -> Option<&'r str> {
268 self.http
269 .header(name)
270 .and_then(|v| core::str::from_utf8(v).ok())
271 }
272
273 /// The value of the cookie `name`, read with cookie-net. `Ok(None)` when
274 /// there is no such cookie.
275 ///
276 /// ```
277 /// use web_net::{App, get};
278 ///
279 /// let app = App::builder()
280 /// .route(get("/", |cx| {
281 /// let theme = cx.request().cookie("theme").ok().flatten().unwrap_or("light");
282 /// cx.text(200, theme)
283 /// }))
284 /// .build()?;
285 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nCookie: theme=dark\r\n\r\n");
286 /// assert_eq!(r.text(), "dark");
287 /// # Ok::<(), web_net::BuildError>(())
288 /// ```
289 ///
290 /// # Errors
291 ///
292 /// [`Ambiguous`] when the name appears twice, in one `Cookie` field or
293 /// across several (a sign of cookie tossing, never resolved by picking
294 /// one), or when the fields together are longer than cookie-net's
295 /// limit. Several `Cookie` fields are read as one header: HTTP/2 and
296 /// HTTP/3 send the cookie as crumbs (RFC 9113 section 8.2.3).
297 pub fn cookie(&self, name: &str) -> Result<Option<&'r str>, Ambiguous> {
298 let mut found = None;
299 let mut total = 0usize;
300 for field in self.http.head().header_all("cookie") {
301 total = total.saturating_add(field.len());
302 if total > cookie_net::Limits::DEFAULT.header_len() {
303 return Err(Ambiguous);
304 }
305 match cookie_net::get_unique(field, name) {
306 Ok(None) => {}
307 Ok(Some(v)) if found.is_none() => found = Some(v),
308 // In two fields, or twice in one.
309 Ok(Some(_)) | Err(_) => return Err(Ambiguous),
310 }
311 }
312 Ok(found)
313 }
314
315 /// The client, as serve-net established it (the real address behind a
316 /// trusted proxy).
317 ///
318 /// ```
319 /// use web_net::{App, get};
320 ///
321 /// let app = App::builder()
322 /// .route(get("/", |cx| {
323 /// let ip = cx.request().client().addr().map(|a| a.to_string()).unwrap_or_default();
324 /// cx.text(200, &ip)
325 /// }))
326 /// .build()?;
327 /// // serve-net's test client connects from 127.0.0.1.
328 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
329 /// assert_eq!(r.text(), "127.0.0.1");
330 /// # Ok::<(), web_net::BuildError>(())
331 /// ```
332 #[must_use]
333 pub fn client(&self) -> &'r ClientInfo {
334 self.env.client()
335 }
336
337 /// `https` when the request came over TLS (or from a trusted proxy that
338 /// said so).
339 ///
340 /// ```
341 /// use web_net::{App, get};
342 ///
343 /// let app = App::builder()
344 /// .route(get("/", |cx| cx.text(200, cx.request().scheme().as_str())))
345 /// .build()?;
346 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
347 /// assert_eq!(r.text(), "http");
348 /// # Ok::<(), web_net::BuildError>(())
349 /// ```
350 #[must_use]
351 pub fn scheme(&self) -> Scheme {
352 self.env.client().scheme()
353 }
354
355 /// The pattern of the matched route (`/users/:id`), once routing ran.
356 ///
357 /// ```
358 /// use web_net::{App, get};
359 ///
360 /// let app = App::builder()
361 /// .route(get("/users/:id", |cx| cx.text(200, cx.request().route().unwrap_or(""))))
362 /// .build()?;
363 /// let r = serve_net::testing::respond(&app, b"GET /users/7 HTTP/1.1\r\nHost: localhost\r\n\r\n");
364 /// assert_eq!(r.text(), "/users/:id");
365 /// # Ok::<(), web_net::BuildError>(())
366 /// ```
367 #[must_use]
368 pub fn route(&self) -> Option<&'r str> {
369 self.route
370 }
371
372 /// The request's reference.
373 ///
374 /// ```
375 /// use web_net::{App, get};
376 ///
377 /// let app = App::builder()
378 /// .route(get("/", |cx| {
379 /// let same = cx.request().id() == cx.id();
380 /// cx.text(200, if same { "same" } else { "different" })
381 /// }))
382 /// .build()?;
383 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
384 /// assert_eq!(r.text(), "same");
385 /// # Ok::<(), web_net::BuildError>(())
386 /// ```
387 #[must_use]
388 pub fn id(&self) -> RequestId {
389 RequestId::of(self.env)
390 }
391
392 /// serve-net's facts about the request.
393 ///
394 /// ```
395 /// use web_net::{App, get};
396 ///
397 /// let app = App::builder()
398 /// .route(get("/", |cx| {
399 /// let seq = cx.request().envelope().seq().get().to_string();
400 /// cx.text(200, &seq)
401 /// }))
402 /// .build()?;
403 /// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
404 /// assert_eq!(r.text(), "1");
405 /// # Ok::<(), web_net::BuildError>(())
406 /// ```
407 #[must_use]
408 pub fn envelope(&self) -> &'r Envelope {
409 self.env
410 }
411
412 /// The raw request: the target as received, every field, the body.
413 ///
414 /// ```
415 /// use web_net::{App, get};
416 ///
417 /// let app = App::builder()
418 /// .route(get("/page", |cx| cx.text(200, cx.request().raw().head().target())))
419 /// .build()?;
420 /// let r = serve_net::testing::respond(&app, b"GET /page?x=1 HTTP/1.1\r\nHost: localhost\r\n\r\n");
421 /// assert_eq!(r.text(), "/page?x=1");
422 /// # Ok::<(), web_net::BuildError>(())
423 /// ```
424 #[must_use]
425 pub fn raw(&self) -> &'r HttpRequest<'r> {
426 self.http
427 }
428}
429
430impl fmt::Debug for Request<'_> {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 f.debug_struct("Request")
433 .field("method", &self.method())
434 .field("path", &self.path)
435 .field("route", &self.route)
436 .field("id", &self.id())
437 .finish_non_exhaustive()
438 }
439}