Skip to main content

vane_core/
handler.rs

1//! Handler trait — the L4/L7 plug-in point between the transport engine and
2//! protocol logic (HTTP parsing/routing lives above this; L4 splice below).
3
4use std::io;
5use std::net::SocketAddr;
6use std::path::PathBuf;
7use std::time::Instant;
8
9/// Why a session deadline fired — handlers branch on this to decide
10/// between closing idle conns and failing in-flight requests.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u8)]
13pub enum DeadlineReason {
14    /// No traffic within the idle window.
15    Idle = 0,
16    /// Upstream connect did not complete in time.
17    Connect = 1,
18    /// Upstream has not produced the response head in time.
19    FirstByte = 2,
20    /// Handler-defined (aux payload rides the timer).
21    Custom(u8) = 3,
22}
23
24impl DeadlineReason {
25    /// Packs a reason into the timer's aux payload.
26    #[must_use]
27    pub fn aux(self) -> u16 {
28        match self {
29            Self::Idle => 0,
30            Self::Connect => 1,
31            Self::FirstByte => 2,
32            Self::Custom(b) => u16::from(b) | (1 << 8),
33        }
34    }
35
36    /// Unpacks from the aux payload.
37    #[must_use]
38    pub fn from_aux(aux: u16) -> Self {
39        match aux {
40            0 => Self::Idle,
41            1 => Self::Connect,
42            2 => Self::FirstByte,
43            other => Self::Custom((other & 0xff) as u8),
44        }
45    }
46}
47
48use crate::worker::WorkerCtx;
49
50/// Proxy mode a handler serves (chosen per listener).
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Mode {
53    /// L7: HTTP/1.1 parsing + filter pipeline + upstream relay.
54    Http,
55    /// L4: kernel splice passthrough (`IO-04`), no parsing.
56    L4,
57}
58
59/// Outcome of an upstream dial.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum UpstreamDial {
62    /// Connect queued; completion arrives asynchronously.
63    Initiated,
64    /// Connected synchronously — the handler may proceed immediately.
65    Established,
66}
67
68/// Per-connection handler. One instance is shared by all sessions of a
69/// worker (single-threaded dispatch; per-connection state lives in the
70/// session, reached through [`SessionIo`]).
71pub trait Handler: Send + 'static {
72    /// A new downstream connection was accepted.
73    fn on_connected(&mut self, io: &mut SessionIo<'_>) {
74        let _ = io;
75    }
76
77    /// Bytes arrived from the downstream (client) socket.
78    fn on_downstream_data(&mut self, io: &mut SessionIo<'_>, data: &[u8]);
79
80    /// The queued downstream write batch fully flushed.
81    fn on_downstream_flushed(&mut self, io: &mut SessionIo<'_>) {
82        let _ = io;
83    }
84
85    /// Upstream connect established.
86    fn on_upstream_connected(&mut self, io: &mut SessionIo<'_>);
87
88    /// Bytes arrived from the upstream (backend) socket.
89    fn on_upstream_data(&mut self, io: &mut SessionIo<'_>, data: &[u8]);
90
91    /// The queued upstream write batch fully flushed.
92    fn on_upstream_flushed(&mut self, io: &mut SessionIo<'_>) {
93        let _ = io;
94    }
95
96    /// Downstream EOF (client half-closed).
97    fn on_downstream_eof(&mut self, io: &mut SessionIo<'_>);
98
99    /// Upstream EOF.
100    fn on_upstream_eof(&mut self, io: &mut SessionIo<'_>);
101
102    /// Downstream I/O error (session should close).
103    fn on_downstream_error(&mut self, io: &mut SessionIo<'_>, err: io::Error) {
104        let _ = err;
105        io.close();
106    }
107
108    /// Upstream I/O error.
109    fn on_upstream_error(&mut self, io: &mut SessionIo<'_>, err: io::Error);
110
111    /// Worker is draining (shutdown): finish in-flight work promptly.
112    fn on_shutdown_hint(&mut self, io: &mut SessionIo<'_>) {
113        let _ = io;
114    }
115
116    /// Session deadline expired; `reason` says which deadline.
117    fn on_deadline(&mut self, io: &mut SessionIo<'_>, reason: DeadlineReason) {
118        let _ = reason;
119        io.close();
120    }
121}
122
123/// Builds a [`Handler`] for each worker.
124pub trait HandlerFactory: Send + Sync + 'static {
125    /// Handler mode (affects listener wiring).
126    fn mode(&self) -> Mode;
127
128    /// Constructs the per-worker handler.
129    fn build(&self, ctx: &WorkerCtx) -> Box<dyn Handler>;
130}
131
132/// Handler-facing control surface for one session.
133///
134/// The handler uses this to respond, relay, dial upstreams, and manage the
135/// session lifecycle. Buffer management is automatic: writes copy into
136/// pre-allocated pool slots (one user-space copy, zero `malloc`).
137pub struct SessionIo<'a> {
138    pub(crate) worker: &'a mut super::worker::WorkerState,
139    pub(crate) slot: u32,
140    pub(crate) generation: u16,
141}
142
143impl<'a> SessionIo<'a> {
144    /// Session slot index (metrics/log correlation).
145    #[must_use]
146    pub fn slot_index(&self) -> u32 {
147        self.slot
148    }
149
150    /// Queues bytes to the downstream socket.
151    ///
152    /// # Panics
153    /// Never panics; on pool exhaustion the write is queued as pending
154    /// bytes (bounded by `max_buffered` — beyond that the session is
155    /// killed as runaway).
156    pub fn respond(&mut self, bytes: &[u8]) {
157        self.worker
158            .downstream_write(self.slot, self.generation, bytes);
159    }
160
161    /// Queues bytes to the upstream socket (must be connected).
162    pub fn write_upstream(&mut self, bytes: &[u8]) {
163        self.worker
164            .upstream_write(self.slot, self.generation, bytes);
165    }
166
167    /// Dials a TCP upstream for this session.
168    ///
169    /// Returns `false` when the connect could not even start; the
170    /// completion arrives via [`Handler::on_upstream_connected`] or
171    /// [`Handler::on_upstream_error`].
172    pub fn connect_upstream(&mut self, addr: SocketAddr) -> bool {
173        self.worker
174            .connect_upstream(self.slot, self.generation, addr)
175    }
176
177    /// Dials a Unix-domain-socket upstream.
178    pub fn connect_upstream_unix(&mut self, path: PathBuf) -> bool {
179        self.worker
180            .connect_upstream_unix(self.slot, self.generation, path)
181    }
182
183    /// Starts the zero-copy L4 splice pump (requires a connected upstream).
184    pub fn start_splice(&mut self) -> bool {
185        self.worker.start_splice(self.slot, self.generation)
186    }
187
188    /// Half-closes the downstream write side (streaming completion).
189    pub fn downstream_eof_write(&mut self) {
190        self.worker
191            .shutdown_downstream_write(self.slot, self.generation);
192    }
193
194    /// Half-closes the upstream write side (end of request body).
195    pub fn upstream_eof_write(&mut self) {
196        self.worker
197            .shutdown_upstream_write(self.slot, self.generation);
198    }
199
200    /// Closes the whole session.
201    pub fn close(&mut self) {
202        self.worker
203            .close_session(self.slot, self.generation, "handler");
204    }
205
206    /// Arms (or clears) the session's single deadline slot with a reason.
207    pub fn set_deadline(&mut self, at: Option<Instant>, reason: DeadlineReason) {
208        self.worker
209            .set_deadline(self.slot, self.generation, at, reason);
210    }
211
212    /// Detaches the upstream fd without closing it (connection pooling).
213    /// Returns the raw descriptor; the caller owns it from here.
214    #[must_use]
215    pub fn detach_upstream(&mut self) -> Option<std::os::fd::RawFd> {
216        self.worker.detach_upstream(self.slot, self.generation)
217    }
218
219    /// Discards a dead upstream (close + epoch bump) so stale completions
220    /// for its descriptor can never touch the session again.
221    pub fn discard_upstream(&mut self) {
222        self.worker.discard_upstream(self.slot, self.generation);
223    }
224
225    /// Attaches a previously detached fd as this session's upstream
226    /// (connection pooling checkout) and arms its read.
227    pub fn attach_upstream(&mut self, fd: std::os::fd::RawFd) -> bool {
228        self.worker.attach_upstream(self.slot, self.generation, fd)
229    }
230
231    /// Raw upstream descriptor (diagnostics).
232    #[must_use]
233    pub fn upstream_fd(&self) -> Option<std::os::fd::RawFd> {
234        self.worker.upstream_fd(self.slot, self.generation)
235    }
236
237    /// `true` once the request head has been written to the upstream
238    /// (failover decisions must not re-send after this point).
239    #[must_use]
240    pub fn request_sent_upstream(&self) -> bool {
241        self.worker.request_sent(self.slot, self.generation)
242    }
243
244    /// Marks the request head as written upstream.
245    pub fn mark_request_sent(&mut self) {
246        self.worker.mark_request_sent(self.slot, self.generation);
247    }
248
249    /// Peer address of the downstream connection.
250    #[must_use]
251    pub fn peer(&self) -> Option<SocketAddr> {
252        self.worker.peer_of(self.slot, self.generation)
253    }
254
255    /// `true` when the session has a connected upstream.
256    #[must_use]
257    pub fn has_upstream(&self) -> bool {
258        self.worker.has_upstream(self.slot, self.generation)
259    }
260}