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}