sentinel_proxy/proxy/
context.rs

1//! Request context for the proxy request lifecycle.
2//!
3//! The `RequestContext` struct maintains state throughout a single request,
4//! including timing, routing decisions, and metadata for logging.
5
6use std::sync::Arc;
7use std::time::Instant;
8
9use sentinel_config::{BodyStreamingMode, Config, RouteConfig, ServiceType};
10
11use crate::websocket::WebSocketHandler;
12
13/// Request context maintained throughout the request lifecycle.
14///
15/// This struct uses a hybrid approach:
16/// - Immutable fields (start_time) are private with getters
17/// - Mutable fields are public(crate) for efficient access within the proxy module
18pub struct RequestContext {
19    /// Request start time (immutable after creation)
20    start_time: Instant,
21
22    // === Tracing ===
23    /// Unique trace ID for request tracing (also used as correlation_id)
24    pub(crate) trace_id: String,
25
26    // === Global config (cached once per request) ===
27    /// Cached global configuration snapshot for this request
28    pub(crate) config: Option<Arc<Config>>,
29
30    // === Routing ===
31    /// Selected route ID
32    pub(crate) route_id: Option<String>,
33    /// Cached route configuration (avoids duplicate route matching)
34    pub(crate) route_config: Option<Arc<RouteConfig>>,
35    /// Selected upstream
36    pub(crate) upstream: Option<String>,
37    /// Number of upstream attempts
38    pub(crate) upstream_attempts: u32,
39
40    // === Request metadata (cached for logging) ===
41    /// HTTP method
42    pub(crate) method: String,
43    /// Request path
44    pub(crate) path: String,
45    /// Query string
46    pub(crate) query: Option<String>,
47
48    // === Client info ===
49    /// Client IP address
50    pub(crate) client_ip: String,
51    /// User-Agent header
52    pub(crate) user_agent: Option<String>,
53    /// Referer header
54    pub(crate) referer: Option<String>,
55    /// Host header
56    pub(crate) host: Option<String>,
57
58    // === Body tracking ===
59    /// Request body bytes received
60    pub(crate) request_body_bytes: u64,
61    /// Response body bytes (set during response)
62    pub(crate) response_bytes: u64,
63
64    // === Connection tracking ===
65    /// Whether the upstream connection was reused
66    pub(crate) connection_reused: bool,
67    /// Whether this request is a WebSocket upgrade
68    pub(crate) is_websocket_upgrade: bool,
69
70    // === WebSocket Inspection ===
71    /// Whether WebSocket frame inspection is enabled for this connection
72    pub(crate) websocket_inspection_enabled: bool,
73    /// Whether to skip inspection (e.g., due to compression negotiation)
74    pub(crate) websocket_skip_inspection: bool,
75    /// Agent IDs for WebSocket frame inspection
76    pub(crate) websocket_inspection_agents: Vec<String>,
77    /// WebSocket frame handler (created after 101 upgrade)
78    pub(crate) websocket_handler: Option<Arc<WebSocketHandler>>,
79
80    // === Caching ===
81    /// Whether this request is eligible for caching
82    pub(crate) cache_eligible: bool,
83
84    // === Body Inspection ===
85    /// Whether body inspection is enabled for this request
86    pub(crate) body_inspection_enabled: bool,
87    /// Bytes already sent to agent for inspection
88    pub(crate) body_bytes_inspected: u64,
89    /// Accumulated body buffer for agent inspection
90    pub(crate) body_buffer: Vec<u8>,
91    /// Agent IDs to use for body inspection
92    pub(crate) body_inspection_agents: Vec<String>,
93
94    // === Body Streaming ===
95    /// Body streaming mode for request body inspection
96    pub(crate) request_body_streaming_mode: BodyStreamingMode,
97    /// Current chunk index for request body streaming
98    pub(crate) request_body_chunk_index: u32,
99    /// Whether agent needs more data (streaming mode)
100    pub(crate) agent_needs_more: bool,
101    /// Body streaming mode for response body inspection
102    pub(crate) response_body_streaming_mode: BodyStreamingMode,
103    /// Current chunk index for response body streaming
104    pub(crate) response_body_chunk_index: u32,
105    /// Response body bytes inspected
106    pub(crate) response_body_bytes_inspected: u64,
107    /// Response body inspection enabled
108    pub(crate) response_body_inspection_enabled: bool,
109    /// Agent IDs for response body inspection
110    pub(crate) response_body_inspection_agents: Vec<String>,
111}
112
113impl RequestContext {
114    /// Create a new empty request context with the current timestamp.
115    pub fn new() -> Self {
116        Self {
117            start_time: Instant::now(),
118            trace_id: String::new(),
119            config: None,
120            route_id: None,
121            route_config: None,
122            upstream: None,
123            upstream_attempts: 0,
124            method: String::new(),
125            path: String::new(),
126            query: None,
127            client_ip: String::new(),
128            user_agent: None,
129            referer: None,
130            host: None,
131            request_body_bytes: 0,
132            response_bytes: 0,
133            connection_reused: false,
134            is_websocket_upgrade: false,
135            websocket_inspection_enabled: false,
136            websocket_skip_inspection: false,
137            websocket_inspection_agents: Vec::new(),
138            websocket_handler: None,
139            cache_eligible: false,
140            body_inspection_enabled: false,
141            body_bytes_inspected: 0,
142            body_buffer: Vec::new(),
143            body_inspection_agents: Vec::new(),
144            request_body_streaming_mode: BodyStreamingMode::Buffer,
145            request_body_chunk_index: 0,
146            agent_needs_more: false,
147            response_body_streaming_mode: BodyStreamingMode::Buffer,
148            response_body_chunk_index: 0,
149            response_body_bytes_inspected: 0,
150            response_body_inspection_enabled: false,
151            response_body_inspection_agents: Vec::new(),
152        }
153    }
154
155    // === Immutable field accessors ===
156
157    /// Get the request start time.
158    #[inline]
159    pub fn start_time(&self) -> Instant {
160        self.start_time
161    }
162
163    /// Get elapsed duration since request start.
164    #[inline]
165    pub fn elapsed(&self) -> std::time::Duration {
166        self.start_time.elapsed()
167    }
168
169    // === Read-only accessors ===
170
171    /// Get trace_id (alias for backwards compatibility with correlation_id usage).
172    #[inline]
173    pub fn correlation_id(&self) -> &str {
174        &self.trace_id
175    }
176
177    /// Get the trace ID.
178    #[inline]
179    pub fn trace_id(&self) -> &str {
180        &self.trace_id
181    }
182
183    /// Get the route ID, if set.
184    #[inline]
185    pub fn route_id(&self) -> Option<&str> {
186        self.route_id.as_deref()
187    }
188
189    /// Get the upstream ID, if set.
190    #[inline]
191    pub fn upstream(&self) -> Option<&str> {
192        self.upstream.as_deref()
193    }
194
195    /// Get the cached route configuration, if set.
196    #[inline]
197    pub fn route_config(&self) -> Option<&Arc<RouteConfig>> {
198        self.route_config.as_ref()
199    }
200
201    /// Get the cached global configuration, if set.
202    #[inline]
203    pub fn global_config(&self) -> Option<&Arc<Config>> {
204        self.config.as_ref()
205    }
206
207    /// Get the service type from cached route config.
208    #[inline]
209    pub fn service_type(&self) -> Option<ServiceType> {
210        self.route_config.as_ref().map(|c| c.service_type.clone())
211    }
212
213    /// Get the number of upstream attempts.
214    #[inline]
215    pub fn upstream_attempts(&self) -> u32 {
216        self.upstream_attempts
217    }
218
219    /// Get the HTTP method.
220    #[inline]
221    pub fn method(&self) -> &str {
222        &self.method
223    }
224
225    /// Get the request path.
226    #[inline]
227    pub fn path(&self) -> &str {
228        &self.path
229    }
230
231    /// Get the query string, if present.
232    #[inline]
233    pub fn query(&self) -> Option<&str> {
234        self.query.as_deref()
235    }
236
237    /// Get the client IP address.
238    #[inline]
239    pub fn client_ip(&self) -> &str {
240        &self.client_ip
241    }
242
243    /// Get the User-Agent header, if present.
244    #[inline]
245    pub fn user_agent(&self) -> Option<&str> {
246        self.user_agent.as_deref()
247    }
248
249    /// Get the Referer header, if present.
250    #[inline]
251    pub fn referer(&self) -> Option<&str> {
252        self.referer.as_deref()
253    }
254
255    /// Get the Host header, if present.
256    #[inline]
257    pub fn host(&self) -> Option<&str> {
258        self.host.as_deref()
259    }
260
261    /// Get the response body size in bytes.
262    #[inline]
263    pub fn response_bytes(&self) -> u64 {
264        self.response_bytes
265    }
266
267    // === Mutation helpers ===
268
269    /// Set the trace ID.
270    #[inline]
271    pub fn set_trace_id(&mut self, trace_id: impl Into<String>) {
272        self.trace_id = trace_id.into();
273    }
274
275    /// Set the route ID.
276    #[inline]
277    pub fn set_route_id(&mut self, route_id: impl Into<String>) {
278        self.route_id = Some(route_id.into());
279    }
280
281    /// Set the upstream ID.
282    #[inline]
283    pub fn set_upstream(&mut self, upstream: impl Into<String>) {
284        self.upstream = Some(upstream.into());
285    }
286
287    /// Increment upstream attempt counter.
288    #[inline]
289    pub fn inc_upstream_attempts(&mut self) {
290        self.upstream_attempts += 1;
291    }
292
293    /// Set response bytes.
294    #[inline]
295    pub fn set_response_bytes(&mut self, bytes: u64) {
296        self.response_bytes = bytes;
297    }
298}
299
300impl Default for RequestContext {
301    fn default() -> Self {
302        Self::new()
303    }
304}