1use std::{
9 cell::RefCell,
10 fmt::Debug,
11 rc::{Rc, Weak},
12 time::Duration,
13};
14
15use mio::Token;
16use sozu_command::logging::ansi_palette;
17
18use super::{GenericHttpStream, Position};
19use crate::metrics::names;
20use crate::{
21 L7ListenerHandler, ListenerHandler, Protocol, SessionMetrics, pool::Pool,
22 protocol::http::editor::HttpContext,
23};
24
25macro_rules! log_module_context {
30 () => {{
31 let (open, reset, _, _, _) = ansi_palette();
32 format!("{open}MUX-STREAM{reset}\t >>>", open = open, reset = reset)
33 }};
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum StreamState {
38 Idle,
39 Link,
41 Linked(Token),
43 Unlinked,
46 Recycle,
48}
49
50impl StreamState {
51 pub fn is_open(&self) -> bool {
52 !matches!(self, StreamState::Idle | StreamState::Recycle)
53 }
54}
55
56pub struct Stream {
57 pub window: i32,
58 pub attempts: u8,
59 pub state: StreamState,
60 pub front_received_end_of_stream: bool,
62 pub back_received_end_of_stream: bool,
64 pub front_data_received: usize,
66 pub back_data_received: usize,
68 pub request_counted: bool,
72 pub front: GenericHttpStream,
73 pub back: GenericHttpStream,
74 pub context: HttpContext,
75 pub metrics: SessionMetrics,
76}
77
78struct KawaSummary<'a>(&'a GenericHttpStream);
79impl Debug for KawaSummary<'_> {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("Kawa")
82 .field("kind", &self.0.kind)
83 .field("parsing_phase", &self.0.parsing_phase)
84 .field("body_size", &self.0.body_size)
85 .field("consumed", &self.0.consumed)
86 .field("expects", &self.0.expects)
87 .field("blocks", &self.0.blocks.len())
88 .field("out", &self.0.out.len())
89 .field("storage_start", &self.0.storage.start)
90 .field("storage_head", &self.0.storage.head)
91 .field("storage_end", &self.0.storage.end)
92 .finish()
93 }
94}
95impl Debug for Stream {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 f.debug_struct("Stream")
98 .field("window", &self.window)
99 .field("attempts", &self.attempts)
100 .field("state", &self.state)
101 .field(
102 "front_received_end_of_stream",
103 &self.front_received_end_of_stream,
104 )
105 .field(
106 "back_received_end_of_stream",
107 &self.back_received_end_of_stream,
108 )
109 .field("front_data_received", &self.front_data_received)
110 .field("back_data_received", &self.back_data_received)
111 .field("request_counted", &self.request_counted)
112 .field("front", &KawaSummary(&self.front))
113 .field("back", &KawaSummary(&self.back))
114 .field("context", &self.context)
115 .field("metrics", &self.metrics)
116 .finish()
117 }
118}
119
120pub struct StreamParts<'a> {
123 pub window: &'a mut i32,
124 pub rbuffer: &'a mut GenericHttpStream,
125 pub wbuffer: &'a mut GenericHttpStream,
126 pub received_end_of_stream: &'a mut bool,
128 pub data_received: &'a mut usize,
130 pub context: &'a mut HttpContext,
131 pub metrics: &'a mut SessionMetrics,
132}
133
134impl Stream {
135 pub fn new(pool: Weak<RefCell<Pool>>, context: HttpContext, window: u32) -> Option<Self> {
136 let (front_buffer, back_buffer) = {
137 let pool = pool.upgrade()?;
138 let mut pool = pool.borrow_mut();
139 match (pool.checkout(), pool.checkout()) {
140 (Some(front_buffer), Some(back_buffer)) => (front_buffer, back_buffer),
141 _ => return None,
142 }
143 };
144 let stream = Self {
145 state: StreamState::Idle,
146 attempts: 0,
147 window: i32::try_from(window).unwrap_or(i32::MAX),
148 front_received_end_of_stream: false,
149 back_received_end_of_stream: false,
150 front_data_received: 0,
151 back_data_received: 0,
152 request_counted: false,
153 front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)),
154 back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)),
155 context,
156 metrics: SessionMetrics::new(None),
157 };
158 debug_assert_eq!(stream.state, StreamState::Idle, "new stream must be Idle");
163 debug_assert!(
164 !stream.state.is_open(),
165 "an Idle stream slot must not report as open"
166 );
167 debug_assert!(
168 !stream.request_counted,
169 "new stream must not have a counted request (gauge-underflow guard)"
170 );
171 debug_assert_eq!(
172 (stream.front_data_received, stream.back_data_received),
173 (0, 0),
174 "new stream DATA counters must start at 0"
175 );
176 #[cfg(debug_assertions)]
177 stream.check_invariants();
178 Some(stream)
179 }
180
181 #[cfg(debug_assertions)]
199 pub(super) fn check_invariants(&self) {
200 debug_assert_eq!(
201 self.state.is_open(),
202 !matches!(self.state, StreamState::Idle | StreamState::Recycle),
203 "is_open() must agree with the Idle/Recycle discriminants"
204 );
205 if self.state == StreamState::Recycle {
206 debug_assert!(
207 !self.request_counted,
208 "a Recycle slot must not carry a counted request (active-requests leak)"
209 );
210 }
211 debug_assert_eq!(
215 self.linked_token().is_some(),
216 matches!(self.state, StreamState::Linked(_)),
217 "linked_token() must be Some iff the stream is Linked"
218 );
219 }
220 pub fn linked_token(&self) -> Option<Token> {
224 match self.state {
225 StreamState::Linked(token) => Some(token),
226 _ => None,
227 }
228 }
229
230 pub fn is_quiesced(&self) -> bool {
234 let front_done =
235 (self.front.is_initial() || self.front.is_completed() || self.front.is_terminated())
236 && self.front.storage.is_empty();
237 let back_done =
238 (self.back.is_initial() || self.back.is_completed() || self.back.is_terminated())
239 && self.back.storage.is_empty();
240 front_done && back_done
241 }
242
243 pub fn split(&mut self, position: &Position) -> StreamParts<'_> {
244 debug_assert_eq!(
248 self.front.kind,
249 kawa::Kind::Request,
250 "front buffer must hold a Request kawa"
251 );
252 debug_assert_eq!(
253 self.back.kind,
254 kawa::Kind::Response,
255 "back buffer must hold a Response kawa"
256 );
257 match position {
258 Position::Client(..) => StreamParts {
259 window: &mut self.window,
260 rbuffer: &mut self.back,
261 wbuffer: &mut self.front,
262 received_end_of_stream: &mut self.back_received_end_of_stream,
263 data_received: &mut self.back_data_received,
264 context: &mut self.context,
265 metrics: &mut self.metrics,
266 },
267 Position::Server => StreamParts {
268 window: &mut self.window,
269 rbuffer: &mut self.front,
270 wbuffer: &mut self.back,
271 received_end_of_stream: &mut self.front_received_end_of_stream,
272 data_received: &mut self.front_data_received,
273 context: &mut self.context,
274 metrics: &mut self.metrics,
275 },
276 }
277 }
278 pub fn generate_access_log<L>(
288 &mut self,
289 error: bool,
290 message: Option<&str>,
291 listener: Rc<RefCell<L>>,
292 client_rtt: Option<Duration>,
293 server_rtt: Option<Duration>,
294 ) where
295 L: ListenerHandler + L7ListenerHandler,
296 {
297 let context = &self.context;
298 let message = message.or(context.access_log_message);
306 let was_counted = self.request_counted;
312 if self.request_counted {
313 gauge_add!(names::http::ACTIVE_REQUESTS, -1);
314 self.request_counted = false;
315 }
316 debug_assert!(
317 !self.request_counted,
318 "generate_access_log must leave request_counted false (gauge-underflow guard)"
319 );
320 debug_assert!(
323 was_counted >= self.request_counted,
324 "request_counted must only clear here, never spontaneously set"
325 );
326 if error {
327 incr!(
331 "http.errors",
332 context.cluster_id.as_deref(),
333 context.backend_id.as_deref()
334 );
335 }
336 let protocol = match context.protocol {
337 Protocol::HTTP => "http",
338 Protocol::HTTPS => "https",
339 other => {
340 error!(
341 "{} mux streams only handle HTTP or HTTPS protocols, got {:?}",
342 log_module_context!(),
343 other
344 );
345 "unknown"
346 }
347 };
348
349 let bucket_key = if let Some(status) = context.status {
354 match status {
355 100..=199 => names::http::STATUS_1XX,
356 200..=299 => names::http::STATUS_2XX,
357 300..=399 => names::http::STATUS_3XX,
358 400..=499 => names::http::STATUS_4XX,
359 500..=599 => names::http::STATUS_5XX,
360 _ => names::http::STATUS_OTHER,
361 }
362 } else {
363 "http.status.none"
364 };
365 incr!(
366 bucket_key,
367 context.cluster_id.as_deref(),
368 context.backend_id.as_deref()
369 );
370
371 if let Some(status) = context.status
372 && let Some(per_code) = crate::metrics::http_status_code_metric_name(status)
373 {
374 incr!(
375 per_code,
376 context.cluster_id.as_deref(),
377 context.backend_id.as_deref()
378 );
379 }
380
381 let endpoint = sozu_command::logging::EndpointRecord::Http {
382 method: context.method.as_deref(),
383 authority: context.authority.as_deref(),
384 path: context.path.as_deref(),
385 reason: context.reason.as_deref(),
386 status: context.status,
387 };
388
389 let listener = listener.borrow();
390 let tags = context.authority.as_deref().and_then(|host| {
391 let hostname = match host.split_once(':') {
392 None => host,
393 Some((hostname, _)) => hostname,
394 };
395 listener.get_tags(hostname)
396 });
397
398 log_access! {
399 error,
400 on_failure: { incr!(names::access_logs::UNSENT) },
401 message,
402 context: context.log_context(),
403 session_address: context.session_address,
404 backend_address: context.backend_address,
405 protocol,
406 endpoint,
407 tags,
408 client_rtt,
409 server_rtt,
410 service_time: self.metrics.service_time(),
411 response_time: self.metrics.backend_response_time(),
412 request_time: self.metrics.request_time(),
413 start_time_ns: self.metrics.start_wall_ns(),
414 bytes_in: self.metrics.bin,
415 bytes_out: self.metrics.bout,
416 user_agent: context.user_agent.as_deref(),
417 x_request_id: context.x_request_id.as_deref(),
418 tls_version: context.tls_version,
419 tls_cipher: context.tls_cipher,
420 tls_sni: context.tls_server_name.as_deref(),
421 tls_alpn: context.tls_alpn,
422 xff_chain: context.xff_chain.as_deref(),
423 #[cfg(feature = "opentelemetry")]
424 otel: context.otel.as_ref(),
425 #[cfg(not(feature = "opentelemetry"))]
426 otel: None,
427 };
428 self.metrics.register_end_of_session(&context.log_context());
429 }
430}