Skip to main content

web_net/
events.rs

1//! What web-net tells the rest of the system, and where it asks whether a
2//! request may continue.
3//!
4//! A security component that cannot report what it sees is a detour, not a
5//! defence. Every refusal, denial, and failure is reported through
6//! [`Events`], so a detection or gate layer hears about a probe the moment
7//! web-net turns it away. Events carry ids, codes, and statuses, never
8//! request bytes.
9
10use serve_net::{Envelope, Verdict};
11
12use crate::RequestId;
13
14/// What kind of thing happened.
15///
16/// ```
17/// use std::sync::Arc;
18/// use std::sync::atomic::{AtomicU32, Ordering};
19/// use web_net::{App, Event, EventKind, Events, get};
20///
21/// #[derive(Default)]
22/// struct Refusals(AtomicU32);
23/// impl Events for Refusals {
24///     fn event(&self, e: &Event<'_>) {
25///         if e.kind == EventKind::Refused {
26///             self.0.fetch_add(1, Ordering::Relaxed);
27///         }
28///     }
29/// }
30///
31/// let refusals = Arc::new(Refusals::default());
32/// let app = App::builder().events(Arc::clone(&refusals)).build()?;
33/// serve_net::testing::respond(&app, b"GET /missing HTTP/1.1\r\nHost: localhost\r\n\r\n");
34/// assert_eq!(refusals.0.load(Ordering::Relaxed), 1);
35/// # Ok::<(), web_net::BuildError>(())
36/// ```
37#[non_exhaustive]
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum EventKind {
40    /// Refused before a handler: a malformed path, an unknown host, no
41    /// route, a method the route does not take.
42    Refused,
43    /// A gate denied the request.
44    Denied,
45    /// A handler or render failed (any handler error, and every 5xx).
46    Failed,
47    /// A handler panicked; answered with 500.
48    Panicked,
49    /// A checkpoint stopped the request in flight.
50    Revoked,
51}
52
53/// One event.
54///
55/// ```
56/// use std::sync::{Arc, Mutex};
57/// use web_net::{App, Event, Events, get};
58///
59/// #[derive(Default)]
60/// struct Codes(Mutex<Vec<(u16, &'static str)>>);
61/// impl Events for Codes {
62///     fn event(&self, e: &Event<'_>) {
63///         self.0.lock().unwrap().push((e.status, e.code));
64///     }
65/// }
66///
67/// let codes = Arc::new(Codes::default());
68/// let app = App::builder().events(Arc::clone(&codes)).route(get("/", |cx| cx.text(200, "hi"))).build()?;
69/// serve_net::testing::respond(&app, b"GET /missing HTTP/1.1\r\nHost: localhost\r\n\r\n");
70/// assert_eq!(*codes.0.lock().unwrap(), [(404, "route.none")]);
71/// # Ok::<(), web_net::BuildError>(())
72/// ```
73#[non_exhaustive]
74#[derive(Debug)]
75pub struct Event<'a> {
76    /// What happened.
77    pub kind: EventKind,
78    /// The request's reference.
79    pub id: RequestId,
80    /// The matched route's pattern, if routing got that far.
81    pub route: Option<&'a str>,
82    /// The status answered.
83    pub status: u16,
84    /// A stable code (`"url.encoded_slash"`, a gate's code, a handler's).
85    pub code: &'static str,
86    /// The underlying error, for the logs only.
87    pub source: Option<&'a (dyn std::error::Error + 'static)>,
88}
89
90/// Where web-net reports. Every method has an empty default; the default
91/// implementation, [`NoEvents`], reports nothing.
92///
93/// Called on the request path: keep it to a counter or a queue push.
94///
95/// ```
96/// use std::sync::atomic::{AtomicU32, Ordering};
97/// use web_net::{Event, Events};
98///
99/// // Counts every event; the app reports refusals, denials, and failures here.
100/// #[derive(Default)]
101/// struct Counter(AtomicU32);
102/// impl Events for Counter {
103///     fn event(&self, _: &Event<'_>) {
104///         self.0.fetch_add(1, Ordering::Relaxed);
105///     }
106/// }
107///
108/// let app = web_net::App::builder().events(Counter::default()).build()?;
109/// # let _ = app;
110/// # Ok::<(), web_net::BuildError>(())
111/// ```
112pub trait Events: Send + Sync + 'static {
113    /// One event.
114    fn event(&self, event: &Event<'_>) {
115        let _ = event;
116    }
117}
118
119/// Reports nothing.
120///
121/// ```
122/// use web_net::{App, NoEvents};
123///
124/// // The default; setting it again turns reporting off.
125/// let app = App::builder().events(NoEvents).build()?;
126/// # let _ = app;
127/// # Ok::<(), web_net::BuildError>(())
128/// ```
129#[derive(Clone, Copy, Debug, Default)]
130pub struct NoEvents;
131
132impl Events for NoEvents {}
133
134/// A sink the application keeps a handle to, to read what it counted.
135///
136/// ```
137/// use std::sync::Arc;
138/// use std::sync::atomic::{AtomicU32, Ordering};
139/// use web_net::{App, Event, Events, get};
140///
141/// #[derive(Default)]
142/// struct Refusals(AtomicU32);
143/// impl Events for Refusals {
144///     fn event(&self, _: &Event<'_>) {
145///         self.0.fetch_add(1, Ordering::Relaxed);
146///     }
147/// }
148///
149/// let seen = Arc::new(Refusals::default());
150/// let app = App::builder()
151///     .events(Arc::clone(&seen))
152///     .route(get("/", |cx| cx.text(200, "hi")))
153///     .build()?;
154/// serve_net::testing::respond(&app, b"GET /missing HTTP/1.1\r\nHost: localhost\r\n\r\n");
155/// assert_eq!(seen.0.load(Ordering::Relaxed), 1);
156/// # Ok::<(), web_net::BuildError>(())
157/// ```
158impl<T: Events + ?Sized> Events for std::sync::Arc<T> {
159    fn event(&self, event: &Event<'_>) {
160        (**self).event(event);
161    }
162}
163
164/// A point in web-net's pipeline where a checkpoint runs.
165///
166/// ```
167/// use serve_net::{Envelope, Verdict};
168/// use web_net::{App, Checkpoint, Stage, get};
169///
170/// // Stops requests once their access check passed.
171/// struct AfterAccess;
172/// impl Checkpoint for AfterAccess {
173///     fn check(&self, _: &Envelope, at: Stage) -> Verdict {
174///         if at == Stage::Authorized { Verdict::Deny { status: 503 } } else { Verdict::Pass }
175///     }
176/// }
177///
178/// let app = App::builder().checkpoint(AfterAccess).route(get("/", |cx| cx.text(200, "hi"))).build()?;
179/// let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
180/// assert_eq!(r.status, 503);
181/// # Ok::<(), web_net::BuildError>(())
182/// ```
183#[non_exhaustive]
184#[derive(Clone, Copy, Debug, PartialEq, Eq)]
185pub enum Stage {
186    /// After the route's access check passed, before any hook or handler.
187    Authorized,
188    /// Before a page is rendered.
189    BeforeRender,
190}
191
192/// Revocation in flight: a ban, a revoked session, or a lockdown that
193/// arrives while a request is being handled. serve-net's own checkpoint
194/// covers admission and the moment before the reply leaves; this one covers
195/// the points in between that only web-net knows.
196///
197/// A checkpoint backed by remote state fails closed (denies) when the
198/// remote cannot be reached.
199///
200/// ```
201/// use std::sync::Arc;
202/// use std::sync::atomic::{AtomicBool, Ordering};
203/// use serve_net::testing::respond;
204/// use serve_net::{Envelope, Verdict};
205/// use web_net::{App, Checkpoint, Stage, get};
206///
207/// #[derive(Default)]
208/// struct Lockdown(AtomicBool);
209/// impl Checkpoint for Lockdown {
210///     fn check(&self, _: &Envelope, _: Stage) -> Verdict {
211///         if self.0.load(Ordering::Relaxed) { Verdict::Deny { status: 503 } } else { Verdict::Pass }
212///     }
213/// }
214///
215/// let lockdown = Arc::new(Lockdown::default());
216/// let app = App::builder().checkpoint(Arc::clone(&lockdown)).route(get("/", |cx| cx.text(200, "hi"))).build()?;
217/// let req = b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
218/// assert_eq!(respond(&app, req).status, 200);
219/// lockdown.0.store(true, Ordering::Relaxed);
220/// assert_eq!(respond(&app, req).status, 503);
221/// # Ok::<(), web_net::BuildError>(())
222/// ```
223pub trait Checkpoint: Send + Sync + 'static {
224    /// Decides at one stage.
225    fn check(&self, env: &Envelope, at: Stage) -> Verdict;
226}
227
228/// A checkpoint shared with the code that revokes.
229impl<T: Checkpoint + ?Sized> Checkpoint for std::sync::Arc<T> {
230    fn check(&self, env: &Envelope, at: Stage) -> Verdict {
231        (**self).check(env, at)
232    }
233}