Skip to main content

sozu_lib/
http.rs

1use std::{
2    cell::RefCell,
3    collections::{BTreeMap, HashMap, hash_map::Entry},
4    io::ErrorKind,
5    net::{Shutdown, SocketAddr},
6    os::unix::io::AsRawFd,
7    rc::{Rc, Weak},
8    str::from_utf8_unchecked,
9    time::{Duration, Instant},
10};
11
12use mio::{
13    Interest, Registry, Token,
14    net::{TcpListener as MioTcpListener, TcpStream},
15    unix::SourceFd,
16};
17use rusty_ulid::Ulid;
18use sozu_command::{
19    logging::CachedTags,
20    proto::command::{
21        Cluster, HttpListenerConfig, ListenerType, RemoveListener, RequestHttpFrontend,
22        UpdateHttpListenerConfig, WorkerRequest, WorkerResponse, request::RequestType,
23    },
24    ready::Ready,
25    response::HttpFrontend,
26    state::{ClusterId, validate_h2_flood_knobs_http, validate_sozu_id_header},
27};
28
29use crate::metrics::names;
30use crate::{
31    AcceptError, FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerError,
32    ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, SessionIsToBeClosed,
33    SessionMetrics, SessionResult, StateMachineBuilder, StateResult,
34    backends::BackendMap,
35    pool::Pool,
36    protocol::{
37        Pipe, SessionState,
38        http::{
39            answers::HttpAnswers,
40            parser::{Method, hostname_and_port},
41        },
42        mux::{self, Mux, MuxClear},
43        proxy_protocol::expect::ExpectProxyProtocol,
44    },
45    router::{RouteResult, Router},
46    server::{ListenToken, SessionManager},
47    socket::server_bind,
48    timer::TimeoutContainer,
49};
50
51#[derive(PartialEq, Eq)]
52pub enum SessionStatus {
53    Normal,
54    DefaultAnswer,
55}
56
57StateMachineBuilder! {
58    /// The various Stages of an HTTP connection:
59    ///
60    /// 1. optional (ExpectProxyProtocol)
61    /// 2. HTTP (via Mux in H1 mode)
62    /// 3. WebSocket (passthrough)
63    enum HttpStateMachine impl SessionState {
64        Expect(ExpectProxyProtocol<TcpStream>),
65        Mux(MuxClear),
66        WebSocket(Pipe<crate::socket::SessionTcpStream, HttpListener>),
67    }
68}
69
70/// Module-level prefix for log lines emitted from this file when no session
71/// is in scope. Produces a bold bright-white `HTTP` label in colored mode.
72/// Used by [`HttpProxy`] / [`HttpListener`] callbacks (notify, add_cluster,
73/// add_*_frontend, accept, soft_stop, hard_stop, etc.) which own a token map
74/// keyed by listener and have no `frontend_token` of their own.
75macro_rules! log_module_context {
76    () => {{
77        let (open, reset, _, _, _) = sozu_command::logging::ansi_palette();
78        format!("{open}HTTP{reset}\t >>>", open = open, reset = reset)
79    }};
80}
81
82/// Per-session prefix for log lines emitted with an [`HttpSession`] in
83/// scope. Renders the canonical `\tHTTP\tSession(...)\t >>>` envelope from
84/// the session's `frontend_token` (mirrors the bracket convention used by
85/// `MUX-*`, `RUSTLS`, `PIPE`). Operators can grep-correlate against the
86/// token id across log lines for the same H1 connection.
87macro_rules! log_context {
88    ($self:expr) => {{
89        let (open, reset, grey, gray, white) = sozu_command::logging::ansi_palette();
90        format!(
91            "{open}HTTP{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset})\t >>>",
92            open = open,
93            reset = reset,
94            grey = grey,
95            gray = gray,
96            white = white,
97            frontend = $self.frontend_token.0,
98        )
99    }};
100}
101
102/// HTTP Session to insert in the SessionManager
103///
104/// 1 session <=> 1 HTTP connection (client to sozu)
105pub struct HttpSession {
106    configured_backend_timeout: Duration,
107    configured_connect_timeout: Duration,
108    configured_frontend_timeout: Duration,
109    frontend_token: Token,
110    last_event: Instant,
111    listener: Rc<RefCell<HttpListener>>,
112    metrics: SessionMetrics,
113    pool: Weak<RefCell<Pool>>,
114    proxy: Rc<RefCell<HttpProxy>>,
115    state: HttpStateMachine,
116    has_been_closed: bool,
117}
118
119impl HttpSession {
120    #[allow(clippy::too_many_arguments)]
121    pub fn new(
122        configured_backend_timeout: Duration,
123        configured_connect_timeout: Duration,
124        configured_frontend_timeout: Duration,
125        configured_request_timeout: Duration,
126        expect_proxy: bool,
127        listener: Rc<RefCell<HttpListener>>,
128        pool: Weak<RefCell<Pool>>,
129        proxy: Rc<RefCell<HttpProxy>>,
130        public_address: SocketAddr,
131        sock: TcpStream,
132        token: Token,
133        wait_time: Duration,
134    ) -> Result<Self, AcceptError> {
135        let request_id = Ulid::generate();
136        let container_frontend_timeout = TimeoutContainer::new(configured_request_timeout, token);
137
138        let state = if expect_proxy {
139            trace!("{} starting in expect proxy state", log_module_context!());
140            gauge_add!(names::protocol::PROXY_EXPECT, 1);
141
142            HttpStateMachine::Expect(ExpectProxyProtocol::new(
143                container_frontend_timeout,
144                sock,
145                token,
146                request_id,
147            ))
148        } else {
149            gauge_add!(names::protocol::HTTP, 1);
150            let session_address = sock.peer_addr().ok();
151            let session_ulid = rusty_ulid::Ulid::generate();
152            let sock = crate::socket::SessionTcpStream::new(sock, session_ulid, session_address);
153
154            let frontend =
155                mux::Connection::new_h1_server(session_ulid, sock, container_frontend_timeout);
156            let router = mux::Router::new(configured_backend_timeout, configured_connect_timeout);
157            let mut context = mux::Context::new(
158                session_ulid,
159                pool.clone(),
160                listener.clone(),
161                session_address,
162                public_address,
163            );
164            context
165                .create_stream(request_id, 1 << 16)
166                .ok_or(AcceptError::BufferCapacityReached)?;
167            HttpStateMachine::Mux(Mux {
168                configured_frontend_timeout,
169                frontend_token: token,
170                frontend,
171                router,
172                context,
173                session_ulid,
174            })
175        };
176
177        // Invariant: `create_session` allocates `token` from the session slab
178        // and registers the frontend socket under it before constructing us.
179        // A session whose frontend token aliased the listen-socket token (0)
180        // would route listener readiness into a session — a state-machine bug.
181        // (`Token(0)` is reserved in real workers; the test harness in this
182        // file builds listeners directly without going through `new`.)
183        debug_assert_eq!(
184            state.marker() as u8,
185            if expect_proxy {
186                StateMarker::Expect as u8
187            } else {
188                StateMarker::Mux as u8
189            },
190            "constructed state must match the expect_proxy branch"
191        );
192        // The freshly-built state can never be a FailedUpgrade: that marker
193        // only appears after a real upgrade attempt drains the state via
194        // `take()`. Catch a future refactor that constructs one by accident.
195        debug_assert!(
196            !state.failed(),
197            "a newly created session must not start in FailedUpgrade"
198        );
199
200        let metrics = SessionMetrics::new(Some(wait_time));
201        let session = HttpSession {
202            configured_backend_timeout,
203            configured_connect_timeout,
204            configured_frontend_timeout,
205            frontend_token: token,
206            has_been_closed: false,
207            last_event: Instant::now(),
208            listener,
209            metrics,
210            pool,
211            proxy,
212            state,
213        };
214        debug_assert_eq!(
215            session.frontend_token, token,
216            "frontend token must be the slab token used for registration"
217        );
218        #[cfg(debug_assertions)]
219        session.check_invariants();
220        Ok(session)
221    }
222
223    /// Full cross-field invariant sweep for the session state machine, used as
224    /// a run-to-completion postcondition. Compiled out in release.
225    ///
226    /// Strong invariants asserted here:
227    /// - the frontend token is always present (slab key is never sentinel);
228    /// - once `close()` has run the state has been drained / cancelled, so the
229    ///   session is terminal and must not be re-driven (callers gate on the
230    ///   returned `SessionIsToBeClosed`);
231    /// - the live state marker is one of the three legal H1 stages — the
232    ///   `FailedUpgrade` marker is only transient (between `take()` and the
233    ///   `close()` that immediately follows a failed upgrade), so it is the
234    ///   single tolerated exception on a still-open session.
235    #[cfg(debug_assertions)]
236    fn check_invariants(&self) {
237        let marker = self.state.marker();
238        debug_assert!(
239            matches!(
240                marker,
241                StateMarker::Expect | StateMarker::Mux | StateMarker::WebSocket
242            ),
243            "session marker must be a legal H1 stage (Expect/Mux/WebSocket), got {marker:?}"
244        );
245        // A failed state is only ever observed transiently between a failed
246        // upgrade and the close() that reaps it; if it survives to a postcondition
247        // sweep on a not-yet-closed session, a close path was skipped.
248        debug_assert!(
249            !self.state.failed() || self.has_been_closed,
250            "FailedUpgrade state must be reaped by close(), never left live"
251        );
252    }
253
254    pub fn upgrade(&mut self) -> SessionIsToBeClosed {
255        debug!("{} upgrade", log_context!(self));
256        // Record the stage we are leaving so we can assert the transition is
257        // legal (no stage-skipping) after the handoff resolves. `take()`
258        // installs a `FailedUpgrade(marker)` placeholder carrying this same
259        // marker, so the session is never left in a half-moved state if an
260        // upgrade_* helper bails out.
261        let from_marker = self.state.marker();
262        let new_state = match self.state.take() {
263            HttpStateMachine::Mux(mux) => self.upgrade_mux(mux),
264            HttpStateMachine::Expect(expect) => self.upgrade_expect(expect),
265            HttpStateMachine::WebSocket(ws) => self.upgrade_websocket(ws),
266            HttpStateMachine::FailedUpgrade(_) => {
267                // Reaching this arm means a prior upgrade already returned
268                // `None` and the session should have been closed. Fall back
269                // to closing cleanly instead of panicking the worker.
270                error!(
271                    "{} upgrade called on FailedUpgrade state; closing session",
272                    log_context!(self)
273                );
274                None
275            }
276        };
277
278        match new_state {
279            Some(state) => {
280                // Legal transitions only: Expect→Mux, Mux→WebSocket, or the
281                // WebSocket self-loop (upgrade_websocket is a no-op guard). Any
282                // other pair means a stage was skipped or reversed.
283                debug_assert!(
284                    matches!(
285                        (from_marker, state.marker()),
286                        (StateMarker::Expect, StateMarker::Mux)
287                            | (StateMarker::Mux, StateMarker::WebSocket)
288                            | (StateMarker::WebSocket, StateMarker::WebSocket)
289                    ),
290                    "illegal protocol-upgrade transition {from_marker:?} -> {:?}",
291                    state.marker()
292                );
293                debug_assert!(
294                    !state.failed(),
295                    "a successful upgrade must not install a FailedUpgrade state"
296                );
297                self.state = state;
298                #[cfg(debug_assertions)]
299                self.check_invariants();
300                false
301            }
302            // The state stays FailedUpgrade, but the Session should be closed right after
303            None => {
304                // On failure `take()` left a FailedUpgrade placeholder behind;
305                // the caller (`ready`) must close the session on the returned
306                // `true`, so we do not run the still-open invariant sweep here.
307                debug_assert!(
308                    self.state.failed(),
309                    "a failed upgrade must leave the session in FailedUpgrade"
310                );
311                true
312            }
313        }
314    }
315
316    fn upgrade_expect(
317        &mut self,
318        expect: ExpectProxyProtocol<TcpStream>,
319    ) -> Option<HttpStateMachine> {
320        debug!("{} switching to HTTP", log_context!(self));
321        match expect
322            .addresses
323            .as_ref()
324            .map(|add| (add.destination(), add.source()))
325        {
326            Some((Some(public_address), Some(session_address))) => {
327                let session_ulid = rusty_ulid::Ulid::generate();
328                let frontend = mux::Connection::new_h1_server(
329                    session_ulid,
330                    crate::socket::SessionTcpStream::new(
331                        expect.frontend,
332                        session_ulid,
333                        Some(session_address),
334                    ),
335                    expect.container_frontend_timeout,
336                );
337                let router = mux::Router::new(
338                    self.configured_backend_timeout,
339                    self.configured_connect_timeout,
340                );
341                let mut context = mux::Context::new(
342                    session_ulid,
343                    self.pool.clone(),
344                    self.listener.clone(),
345                    Some(session_address),
346                    public_address,
347                );
348                if context.create_stream(expect.request_id, 1 << 16).is_none() {
349                    error!(
350                        "{} expect upgrade failed: could not create stream",
351                        log_context!(self)
352                    );
353                    return None;
354                }
355                let mut mux = Mux {
356                    configured_frontend_timeout: self.configured_frontend_timeout,
357                    frontend_token: self.frontend_token,
358                    frontend,
359                    router,
360                    context,
361                    session_ulid,
362                };
363                mux.frontend.readiness_mut().event = expect.frontend_readiness.event;
364
365                // The Expect→Mux handoff must carry the session's frontend
366                // token across unchanged: the slab slot and epoll registration
367                // are keyed by it, so a mismatch would misroute readiness.
368                debug_assert_eq!(
369                    mux.frontend_token, self.frontend_token,
370                    "expect upgrade must preserve the frontend token"
371                );
372                // Fresh Mux: exactly one stream was just created above, and no
373                // backend is connected yet (back token is set iff linked).
374                debug_assert_eq!(
375                    mux.context.streams.len(),
376                    1,
377                    "a freshly upgraded Mux owns exactly the request stream"
378                );
379
380                // Gauge pairing: this connection leaves PROXY_EXPECT and enters
381                // HTTP. The two adjustments must stay together so the live-state
382                // gauges sum to the session count (no double-count, no leak).
383                gauge_add!(names::protocol::PROXY_EXPECT, -1);
384                gauge_add!(names::protocol::HTTP, 1);
385                Some(HttpStateMachine::Mux(mux))
386            }
387            _ => {
388                debug!(
389                    "{} expect upgrade failed: bad header {:?}",
390                    log_context!(self),
391                    expect.addresses
392                );
393                None
394            }
395        }
396    }
397
398    fn upgrade_mux(&mut self, mut mux: MuxClear) -> Option<HttpStateMachine> {
399        debug!("{} mux switching to ws", log_context!(self));
400        let Some(stream) = mux.context.streams.pop() else {
401            error!(
402                "{} upgrade_mux: no stream attached to the mux session, closing",
403                log_context!(self)
404            );
405            return None;
406        };
407        // http.active_requests was already decremented by generate_access_log()
408        // in h1.rs before MuxResult::Upgrade was returned to us.
409
410        let (frontend_readiness, frontend_socket, mut container_frontend_timeout) =
411            match mux.frontend {
412                mux::Connection::H1(mux::ConnectionH1 {
413                    readiness,
414                    socket,
415                    timeout_container,
416                    ..
417                }) => (readiness, socket, timeout_container),
418                mux::Connection::H2(_) => {
419                    error!(
420                        "{} only h1<->h1 connections can upgrade to websocket",
421                        log_context!(self)
422                    );
423                    return None;
424                }
425            };
426
427        let mux::StreamState::Linked(back_token) = stream.state else {
428            error!(
429                "{} upgrading stream should be linked to a backend",
430                log_context!(self)
431            );
432            return None;
433        };
434        // Invariant (back token set iff backend connected): a Linked stream
435        // names a backend token that must exist in the router's backend map.
436        // We assert the map is non-empty here, then prove membership by the
437        // `remove` below — a Linked token absent from the map would be a
438        // book-keeping desync between stream state and the backend map.
439        debug_assert!(
440            mux.router.backends.contains_key(&back_token),
441            "a Linked stream's back token must index a connected backend"
442        );
443        let backends_before = mux.router.backends.len();
444        let Some(backend) = mux.router.backends.remove(&back_token) else {
445            error!(
446                "{} upgrade_mux: backend for token {:?} is missing (already disconnected?), closing",
447                log_context!(self),
448                back_token
449            );
450            return None;
451        };
452        let (cluster_id, backend, backend_readiness, backend_socket, mut container_backend_timeout) =
453            match backend {
454                mux::Connection::H1(mux::ConnectionH1 {
455                    position:
456                        mux::Position::Client(cluster_id, backend, mux::BackendStatus::Connected),
457                    readiness,
458                    socket,
459                    timeout_container,
460                    ..
461                }) => (cluster_id, backend, readiness, socket, timeout_container),
462                mux::Connection::H1(_) => {
463                    error!(
464                        "{} the backend disconnected just after upgrade, abort",
465                        log_context!(self)
466                    );
467                    return None;
468                }
469                mux::Connection::H2(_) => {
470                    error!(
471                        "{} only h1<->h1 connections can upgrade to websocket",
472                        log_context!(self)
473                    );
474                    return None;
475                }
476            };
477
478        // Post-removal book-keeping: the backend is gone from the map and the
479        // count dropped by exactly one (the `remove` matched a present key).
480        debug_assert!(
481            !mux.router.backends.contains_key(&back_token),
482            "the upgraded backend must be evicted from the router map"
483        );
484        debug_assert_eq!(
485            mux.router.backends.len(),
486            backends_before - 1,
487            "removing the backend must drop the backend count by exactly one"
488        );
489
490        let ws_context = stream.context.websocket_context();
491
492        container_frontend_timeout.reset();
493        container_backend_timeout.reset();
494
495        let backend_id = backend.borrow().backend_id.clone();
496        // `Pipe::backend_socket` is typed `Option<TcpStream>` (raw, pre-mux).
497        // The mux wraps every backend TCP socket in `SessionTcpStream` so
498        // SOCKET-layer errors carry the session ULID; unwrap back to the
499        // plain `TcpStream` here to feed Pipe's legacy shape.
500        let backend_socket = backend_socket.stream;
501        let mut pipe = Pipe::new(
502            stream.back.storage.buffer,
503            Some(backend_id),
504            Some(backend_socket),
505            Some(backend),
506            Some(container_backend_timeout),
507            Some(container_frontend_timeout),
508            Some(cluster_id),
509            stream.front.storage.buffer,
510            self.frontend_token,
511            frontend_socket,
512            self.listener.clone(),
513            Protocol::HTTP,
514            stream.context.session_id,
515            stream.context.id,
516            stream.context.session_address,
517            ws_context,
518        );
519
520        pipe.restore_readiness_events(frontend_readiness.event, backend_readiness.event);
521        // The WebSocket pipe inherits the live backend connection, so its back
522        // token must be set (back token present iff a backend is connected).
523        pipe.set_back_token(back_token);
524        debug_assert_eq!(
525            pipe.back_token(),
526            vec![back_token],
527            "websocket pipe must carry exactly the upgraded backend token"
528        );
529
530        // http.active_requests was already decremented by generate_access_log()
531        // in h1.rs when the 101 response was written (before MuxResult::Upgrade).
532        //
533        // Gauge pairing (Mux→WebSocket): leave HTTP, enter WS, and start
534        // accounting one active websocket request. These three must move
535        // together so the protocol gauges stay consistent with the session set.
536        gauge_add!(names::protocol::HTTP, -1);
537        gauge_add!(names::protocol::WS, 1);
538        gauge_add!(names::websocket::ACTIVE_REQUESTS, 1);
539        Some(HttpStateMachine::WebSocket(pipe))
540    }
541
542    fn upgrade_websocket(
543        &self,
544        ws: Pipe<crate::socket::SessionTcpStream, HttpListener>,
545    ) -> Option<HttpStateMachine> {
546        // what do we do here?
547        error!(
548            "{} upgrade called on WS, this should not happen",
549            log_context!(self)
550        );
551        Some(HttpStateMachine::WebSocket(ws))
552    }
553}
554
555impl ProxySession for HttpSession {
556    fn close(&mut self) {
557        if self.has_been_closed {
558            return;
559        }
560        // Reaching here means we are about to do the one-and-only teardown.
561        debug_assert!(
562            !self.has_been_closed,
563            "close past the guard must run on a not-yet-closed session"
564        );
565
566        trace!("{} closing HTTP session", log_context!(self));
567        self.metrics.service_stop();
568
569        // Restore gauges
570        match self.state.marker() {
571            StateMarker::Expect => gauge_add!(names::protocol::PROXY_EXPECT, -1),
572            StateMarker::Mux => gauge_add!(names::protocol::HTTP, -1),
573            StateMarker::WebSocket => {
574                gauge_add!(names::protocol::WS, -1);
575                gauge_add!(names::websocket::ACTIVE_REQUESTS, -1);
576            }
577        }
578
579        if self.state.failed() {
580            match self.state.marker() {
581                StateMarker::Expect => incr!(names::http::UPGRADE_EXPECT_FAILED),
582                StateMarker::Mux => incr!(names::http::UPGRADE_MUX_FAILED),
583                StateMarker::WebSocket => incr!(names::http::UPGRADE_WS_FAILED),
584            }
585            // FailedUpgrade means the socket was consumed by a failed upgrade
586            // attempt, so we can only close the state (no-op) and remove the
587            // session — cancel_timeouts / front_socket are unreachable.
588            self.state.close(self.proxy.clone(), &mut self.metrics);
589            self.proxy.borrow().remove_session(self.frontend_token);
590            self.has_been_closed = true;
591            debug_assert!(
592                self.has_been_closed,
593                "failed-upgrade close path must mark the session closed"
594            );
595            return;
596        }
597
598        self.state.cancel_timeouts();
599        // defer backend closing to the state
600        self.state.close(self.proxy.clone(), &mut self.metrics);
601
602        let front_socket = self.state.front_socket();
603        // invariant: write-only shutdown — Shutdown::Both on a TLS frontend
604        // discards the receive buffer and elicits TCP RST, truncating the
605        // already-queued response. Canonical write-up: `lib/src/https.rs:650-655`.
606        // Backend sockets follow the same discipline for symmetry.
607        if let Err(e) = front_socket.shutdown(Shutdown::Write) {
608            // error 107 NotConnected can happen when was never fully connected, or was already disconnected due to error
609            if e.kind() != ErrorKind::NotConnected {
610                error!(
611                    "{} error shutting down front socket({:?}): {:?}",
612                    log_context!(self),
613                    front_socket,
614                    e
615                )
616            }
617        }
618
619        // deregister the frontend and remove it
620        let proxy = self.proxy.borrow();
621        let fd = front_socket.as_raw_fd();
622        if let Err(e) = proxy.registry.deregister(&mut SourceFd(&fd)) {
623            error!(
624                "{} error deregistering front socket({:?}) while closing HTTP session: {:?}",
625                log_context!(self),
626                fd,
627                e
628            );
629        }
630        proxy.remove_session(self.frontend_token);
631
632        self.has_been_closed = true;
633        debug_assert!(
634            self.has_been_closed,
635            "close must leave the session marked closed (idempotency latch)"
636        );
637    }
638
639    fn timeout(&mut self, token: Token) -> SessionIsToBeClosed {
640        let state_result = self.state.timeout(token, &mut self.metrics);
641        state_result == StateResult::CloseSession
642    }
643
644    fn protocol(&self) -> Protocol {
645        Protocol::HTTP
646    }
647
648    fn update_readiness(&mut self, token: Token, events: Ready) {
649        trace!(
650            "{} token {:?} got event {}",
651            log_context!(self),
652            token,
653            super::ready_to_string(events)
654        );
655        self.last_event = Instant::now();
656        self.metrics.wait_start();
657        self.state.update_readiness(token, events);
658    }
659
660    fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
661        self.metrics.service_start();
662
663        let session_result =
664            self.state
665                .ready(session.clone(), self.proxy.clone(), &mut self.metrics);
666
667        let to_be_closed = match session_result {
668            SessionResult::Close => true,
669            SessionResult::Continue => false,
670            SessionResult::Upgrade => match self.upgrade() {
671                false => self.ready(session),
672                true => true,
673            },
674        };
675
676        self.metrics.service_stop();
677        // Run-to-completion postcondition: a session that is being kept alive
678        // must still satisfy its cross-field invariants. When `to_be_closed`
679        // is true the state may be a transient FailedUpgrade or already torn
680        // down, so the sweep only applies to the still-open path.
681        #[cfg(debug_assertions)]
682        if !to_be_closed {
683            self.check_invariants();
684        }
685        to_be_closed
686    }
687
688    fn shutting_down(&mut self) -> SessionIsToBeClosed {
689        self.state.shutting_down()
690    }
691
692    fn last_event(&self) -> Instant {
693        self.last_event
694    }
695
696    fn print_session(&self) {
697        self.state.print_state("HTTP");
698        error!("{} Metrics: {:?}", log_context!(self), self.metrics);
699    }
700
701    fn frontend_token(&self) -> Token {
702        self.frontend_token
703    }
704}
705
706pub type Hostname = String;
707
708/// Cleartext HTTP/1.x listener.
709///
710/// # HTTP/2 over cleartext (h2c) is NOT supported
711///
712/// RFC 7540 §3.2 specified an `Upgrade: h2c` mechanism to negotiate HTTP/2
713/// over a cleartext TCP connection, with a companion prior-knowledge
714/// variant in §3.4. Both paths are intentionally absent from this listener:
715///
716/// - No `Upgrade: h2c` handler: the HTTP/1.1 state machine forwards
717///   `Upgrade` headers to the backend but never responds `101 Switching
718///   Protocols` with an HTTP/2 connection preface.
719/// - No prior-knowledge detection: the listener does not sniff the
720///   24-byte `PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n` magic string; a client
721///   that opens a TCP connection and immediately sends the preface will
722///   be interpreted as a malformed HTTP/1 request and rejected with 400.
723///
724/// RFC 9113 (the current HTTP/2 RFC, obsoleting 7540) formally deprecates
725/// the `Upgrade: h2c` mechanism. Clients that want HTTP/2 MUST use the
726/// TLS ALPN path (`HttpsListener`, selector `h2`) instead. This is
727/// consistent with the industry consensus (nginx, envoy, cloudflare) and
728/// removes an entire class of cleartext-preface smuggling primitives.
729pub struct HttpListener {
730    active: bool,
731    address: SocketAddr,
732    answers: Rc<RefCell<HttpAnswers>>,
733    config: HttpListenerConfig,
734    fronts: Router,
735    listener: Option<MioTcpListener>,
736    tags: BTreeMap<String, CachedTags>,
737    token: Token,
738}
739
740impl ListenerHandler for HttpListener {
741    fn get_addr(&self) -> &SocketAddr {
742        &self.address
743    }
744
745    fn get_tags(&self, key: &str) -> Option<&CachedTags> {
746        self.tags.get(key)
747    }
748
749    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
750        match tags {
751            Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
752            None => self.tags.remove(&key),
753        };
754    }
755
756    fn protocol(&self) -> Protocol {
757        Protocol::HTTP
758    }
759
760    fn public_address(&self) -> SocketAddr {
761        self.config
762            .public_address
763            .map(|addr| addr.into())
764            .unwrap_or(self.address)
765    }
766}
767
768impl L7ListenerHandler for HttpListener {
769    fn get_sticky_name(&self) -> &str {
770        &self.config.sticky_name
771    }
772
773    fn get_sozu_id_header(&self) -> &str {
774        self.config
775            .sozu_id_header
776            .as_deref()
777            .filter(|s| !s.is_empty())
778            .unwrap_or("Sozu-Id")
779    }
780
781    fn get_connect_timeout(&self) -> u32 {
782        self.config.connect_timeout
783    }
784
785    // redundant, already called once in extract_route
786    fn frontend_from_request(
787        &self,
788        host: &str,
789        uri: &str,
790        method: &Method,
791    ) -> Result<RouteResult, FrontendFromRequestError> {
792        let start = Instant::now();
793        let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) {
794            Ok(tuple) => tuple,
795            Err(parse_error) => {
796                // parse_error contains a slice of given_host, which should NOT escape this scope
797                return Err(FrontendFromRequestError::HostParse {
798                    host: host.to_owned(),
799                    error: parse_error.to_string(),
800                });
801            }
802        };
803        if remaining_input != &b""[..] {
804            return Err(FrontendFromRequestError::InvalidCharsAfterHost(
805                host.to_owned(),
806            ));
807        }
808
809        /*if port == Some(&b"80"[..]) {
810        // it is alright to call from_utf8_unchecked,
811        // we already verified that there are only ascii
812        // chars in there
813          unsafe { from_utf8_unchecked(hostname) }
814        } else {
815          host
816        }
817        */
818        // SAFETY: `hostname` was just produced by `hostname_and_port` (see
819        // `lib/src/protocol/kawa_h1/parser.rs:133`), which only accepts
820        // bytes matching `is_hostname_char` (alphanumeric, `-`, `.`, plus
821        // `_` under the tolerant-http1-parser feature). All accepted
822        // bytes are ASCII (≤ 0x7F), so the slice is valid single-byte UTF-8.
823        let host = unsafe { from_utf8_unchecked(hostname) };
824
825        let route = self.fronts.lookup(host, uri, method).map_err(|e| {
826            incr!(names::http::FAILED_BACKEND_MATCHING);
827            FrontendFromRequestError::NoClusterFound(e)
828        })?;
829
830        let now = Instant::now();
831
832        if let Some(cluster) = route.cluster_id.as_deref() {
833            time!(
834                names::event_loop::FRONTEND_MATCHING_TIME,
835                cluster,
836                (now - start).as_millis()
837            );
838        }
839
840        Ok(route)
841    }
842
843    fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>> {
844        &self.answers
845    }
846
847    fn get_h2_flood_config(&self) -> crate::protocol::mux::H2FloodConfig {
848        let defaults = crate::protocol::mux::H2FloodConfig::default();
849        crate::protocol::mux::H2FloodConfig {
850            max_rst_stream_per_window: self
851                .config
852                .h2_max_rst_stream_per_window
853                .unwrap_or(defaults.max_rst_stream_per_window),
854            max_ping_per_window: self
855                .config
856                .h2_max_ping_per_window
857                .unwrap_or(defaults.max_ping_per_window),
858            max_settings_per_window: self
859                .config
860                .h2_max_settings_per_window
861                .unwrap_or(defaults.max_settings_per_window),
862            max_empty_data_per_window: self
863                .config
864                .h2_max_empty_data_per_window
865                .unwrap_or(defaults.max_empty_data_per_window),
866            max_window_update_stream0_per_window: self
867                .config
868                .h2_max_window_update_stream0_per_window
869                .unwrap_or(defaults.max_window_update_stream0_per_window),
870            max_continuation_frames: self
871                .config
872                .h2_max_continuation_frames
873                .unwrap_or(defaults.max_continuation_frames),
874            max_glitch_count: self
875                .config
876                .h2_max_glitch_count
877                .unwrap_or(defaults.max_glitch_count),
878            max_rst_stream_lifetime: self
879                .config
880                .h2_max_rst_stream_lifetime
881                .unwrap_or(defaults.max_rst_stream_lifetime),
882            max_rst_stream_abusive_lifetime: self
883                .config
884                .h2_max_rst_stream_abusive_lifetime
885                .unwrap_or(defaults.max_rst_stream_abusive_lifetime),
886            max_rst_stream_emitted_lifetime: self
887                .config
888                .h2_max_rst_stream_emitted_lifetime
889                .unwrap_or(defaults.max_rst_stream_emitted_lifetime),
890            max_header_list_size: self
891                .config
892                .h2_max_header_list_size
893                .unwrap_or(defaults.max_header_list_size),
894            max_header_table_size: self
895                .config
896                .h2_max_header_table_size
897                .unwrap_or(defaults.max_header_table_size),
898            max_header_fields: self
899                .config
900                .h2_max_header_fields
901                .unwrap_or(defaults.max_header_fields),
902        }
903    }
904
905    fn get_h2_connection_config(&self) -> crate::protocol::mux::H2ConnectionConfig {
906        crate::protocol::mux::H2ConnectionConfig::from_optional(
907            self.config.h2_initial_connection_window,
908            self.config.h2_max_concurrent_streams,
909            self.config.h2_stream_shrink_ratio,
910        )
911    }
912
913    fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
914        // Inherit `back_timeout` when the knob is unset so listeners tuned for
915        // long-running backends do not cancel streams at the 30 s security
916        // floor. The `max(30, …)` keeps the baseline slow-multiplex mitigation
917        // when `back_timeout` is shorter than 30 s. Explicit values (including
918        // ones below 30 s) win — operators under a slow-multiplex attack can
919        // lower the per-stream deadline to cap buffer pinning.
920        let seconds = self
921            .config
922            .h2_stream_idle_timeout_seconds
923            .map(|s| u64::from(s.max(1)))
924            .unwrap_or_else(|| u64::from(self.config.back_timeout).max(30));
925        std::time::Duration::from_secs(seconds)
926    }
927
928    fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
929        match self.config.h2_graceful_shutdown_deadline_seconds {
930            None => Some(std::time::Duration::from_secs(5)),
931            Some(0) => None,
932            Some(s) => Some(std::time::Duration::from_secs(u64::from(s))),
933        }
934    }
935
936    fn get_elide_x_real_ip(&self) -> bool {
937        self.config.elide_x_real_ip.unwrap_or(false)
938    }
939
940    fn get_send_x_real_ip(&self) -> bool {
941        self.config.send_x_real_ip.unwrap_or(false)
942    }
943}
944
945pub struct HttpProxy {
946    backends: Rc<RefCell<BackendMap>>,
947    clusters: HashMap<ClusterId, Cluster>,
948    listeners: HashMap<Token, Rc<RefCell<HttpListener>>>,
949    pool: Rc<RefCell<Pool>>,
950    registry: Registry,
951    sessions: Rc<RefCell<SessionManager>>,
952}
953
954impl HttpProxy {
955    pub fn new(
956        registry: Registry,
957        sessions: Rc<RefCell<SessionManager>>,
958        pool: Rc<RefCell<Pool>>,
959        backends: Rc<RefCell<BackendMap>>,
960    ) -> HttpProxy {
961        HttpProxy {
962            backends,
963            clusters: HashMap::new(),
964            listeners: HashMap::new(),
965            pool,
966            registry,
967            sessions,
968        }
969    }
970
971    pub fn add_listener(
972        &mut self,
973        config: HttpListenerConfig,
974        token: Token,
975    ) -> Result<Token, ProxyError> {
976        match self.listeners.entry(token) {
977            Entry::Vacant(entry) => {
978                let http_listener =
979                    HttpListener::new(config, token).map_err(ProxyError::AddListener)?;
980                entry.insert(Rc::new(RefCell::new(http_listener)));
981                Ok(token)
982            }
983            _ => Err(ProxyError::ListenerAlreadyPresent),
984        }
985    }
986
987    pub fn get_listener(&self, token: &Token) -> Option<Rc<RefCell<HttpListener>>> {
988        self.listeners.get(token).cloned()
989    }
990
991    pub fn remove_listener(&mut self, remove: RemoveListener) -> Result<(), ProxyError> {
992        let len = self.listeners.len();
993        let remove_address = remove.address.into();
994        self.listeners
995            .retain(|_, l| l.borrow().address != remove_address);
996
997        if !self.listeners.len() < len {
998            info!(
999                "{} no HTTP listener to remove at address {:?}",
1000                log_module_context!(),
1001                remove_address
1002            );
1003        }
1004        Ok(())
1005    }
1006
1007    pub fn activate_listener(
1008        &self,
1009        addr: &SocketAddr,
1010        tcp_listener: Option<MioTcpListener>,
1011    ) -> Result<Token, ProxyError> {
1012        let listener = self
1013            .listeners
1014            .values()
1015            .find(|listener| listener.borrow().address == *addr)
1016            .ok_or(ProxyError::NoListenerFound(addr.to_owned()))?;
1017
1018        listener
1019            .borrow_mut()
1020            .activate(&self.registry, tcp_listener)
1021            .map_err(|listener_error| ProxyError::ListenerActivation {
1022                address: *addr,
1023                listener_error,
1024            })
1025    }
1026
1027    pub fn give_back_listeners(&mut self) -> Vec<(SocketAddr, MioTcpListener)> {
1028        self.listeners
1029            .values()
1030            .filter_map(|listener| {
1031                let mut owned = listener.borrow_mut();
1032                if let Some(listener) = owned.listener.take() {
1033                    // Reset `active` so a subsequent `activate()` re-binds
1034                    // instead of short-circuiting on the stale flag.
1035                    owned.active = false;
1036                    return Some((owned.address, listener));
1037                }
1038
1039                None
1040            })
1041            .collect()
1042    }
1043
1044    pub fn give_back_listener(
1045        &mut self,
1046        address: SocketAddr,
1047    ) -> Result<(Token, MioTcpListener), ProxyError> {
1048        let listener = self
1049            .listeners
1050            .values()
1051            .find(|listener| listener.borrow().address == address)
1052            .ok_or(ProxyError::NoListenerFound(address))?;
1053
1054        let mut owned = listener.borrow_mut();
1055
1056        let taken_listener = owned
1057            .listener
1058            .take()
1059            .ok_or(ProxyError::UnactivatedListener)?;
1060
1061        // Reset `active` so a subsequent `activate()` re-binds instead of
1062        // short-circuiting on the stale flag.
1063        owned.active = false;
1064
1065        Ok((owned.token, taken_listener))
1066    }
1067
1068    /// Apply a partial-update patch to the identified HTTP listener.
1069    pub fn update_listener(&mut self, patch: UpdateHttpListenerConfig) -> Result<(), ProxyError> {
1070        let address: std::net::SocketAddr = patch.address.into();
1071        let listener = self
1072            .listeners
1073            .values()
1074            .find(|l| l.borrow().address == address)
1075            .ok_or(ProxyError::NoListenerFound(address))?;
1076        listener
1077            .borrow_mut()
1078            .update_config(&patch)
1079            .map_err(|listener_error| ProxyError::ListenerActivation {
1080                address,
1081                listener_error,
1082            })
1083    }
1084
1085    pub fn add_cluster(&mut self, mut cluster: Cluster) -> Result<(), ProxyError> {
1086        // Reconcile the legacy single-status `answer_503` field with the
1087        // new map. The new map wins on collision.
1088        let mut overrides = cluster.answers.clone();
1089        if let Some(answer_503) = cluster.answer_503.take() {
1090            overrides.entry("503".to_owned()).or_insert(answer_503);
1091        }
1092        if !overrides.is_empty() {
1093            for listener in self.listeners.values() {
1094                listener
1095                    .borrow()
1096                    .answers
1097                    .borrow_mut()
1098                    .add_cluster_answers(&cluster.cluster_id, &overrides)
1099                    .map_err(|(name, error)| {
1100                        ProxyError::AddCluster(ListenerError::TemplateParse(name, error))
1101                    })?;
1102            }
1103        }
1104        self.clusters.insert(cluster.cluster_id.clone(), cluster);
1105        Ok(())
1106    }
1107
1108    pub fn remove_cluster(&mut self, cluster_id: &str) -> Result<(), ProxyError> {
1109        self.clusters.remove(cluster_id);
1110
1111        for listener in self.listeners.values() {
1112            listener
1113                .borrow()
1114                .answers
1115                .borrow_mut()
1116                .remove_cluster_answers(cluster_id);
1117        }
1118        Ok(())
1119    }
1120
1121    pub fn add_http_frontend(&mut self, front: RequestHttpFrontend) -> Result<(), ProxyError> {
1122        // RFC 6797 §7.2: `Strict-Transport-Security` MUST NOT be sent over
1123        // plaintext HTTP. Reject any AddHttpFrontend that ships an enabled
1124        // HSTS policy before it ever touches the routing trie. This is
1125        // defense in depth on top of the TOML config-load reject in
1126        // `command/src/config.rs`; sites that build a `RequestHttpFrontend`
1127        // outside the TOML path (`sozu frontend http add`, programmatic
1128        // IPC senders) are caught here.
1129        // Reject ANY hsts field on a plain-HTTP frontend, not just
1130        // `enabled = true`. There is no listener-default HSTS to inherit
1131        // on an HTTP listener (the field doesn't exist on
1132        // `HttpListenerConfig`), so the explicit-disable signal
1133        // (`enabled = false`) has nothing to suppress on this surface.
1134        // Carrying any `hsts` field here is a misconfiguration rather
1135        // than a deliberate choice.
1136        if front.hsts.is_some() {
1137            incr!(names::http::HSTS_SUPPRESSED_PLAINTEXT);
1138            return Err(ProxyError::HstsOnPlainHttp(front.address.into()));
1139        }
1140
1141        let front = front.clone().to_frontend().map_err(|request_error| {
1142            ProxyError::WrongInputFrontend {
1143                front: Box::new(front),
1144                error: request_error.to_string(),
1145            }
1146        })?;
1147
1148        let mut listener = self
1149            .listeners
1150            .values()
1151            .find(|l| l.borrow().address == front.address)
1152            .ok_or(ProxyError::NoListenerFound(front.address))?
1153            .borrow_mut();
1154
1155        let hostname = front.hostname.to_owned();
1156        let tags = front.tags.to_owned();
1157
1158        listener
1159            .add_http_front(front)
1160            .map_err(ProxyError::AddFrontend)?;
1161        listener.set_tags(hostname, tags);
1162        Ok(())
1163    }
1164
1165    pub fn remove_http_frontend(&mut self, front: RequestHttpFrontend) -> Result<(), ProxyError> {
1166        let front = front.clone().to_frontend().map_err(|request_error| {
1167            ProxyError::WrongInputFrontend {
1168                front: Box::new(front),
1169                error: request_error.to_string(),
1170            }
1171        })?;
1172
1173        let mut listener = self
1174            .listeners
1175            .values()
1176            .find(|l| l.borrow().address == front.address)
1177            .ok_or(ProxyError::NoListenerFound(front.address))?
1178            .borrow_mut();
1179
1180        let hostname = front.hostname.to_owned();
1181
1182        listener
1183            .remove_http_front(front)
1184            .map_err(ProxyError::RemoveFrontend)?;
1185
1186        if !listener.fronts.has_hostname(&hostname) {
1187            listener.set_tags(hostname, None);
1188        }
1189        Ok(())
1190    }
1191
1192    pub fn soft_stop(&mut self) -> Result<(), ProxyError> {
1193        let listeners: HashMap<_, _> = self.listeners.drain().collect();
1194        let mut socket_errors = vec![];
1195        for l in listeners.values() {
1196            if let Some(mut sock) = l.borrow_mut().listener.take() {
1197                debug!("{} deregistering socket {:?}", log_module_context!(), sock);
1198                if let Err(e) = self.registry.deregister(&mut sock) {
1199                    let error = format!("socket {sock:?}: {e:?}");
1200                    socket_errors.push(error);
1201                }
1202            }
1203        }
1204
1205        if !socket_errors.is_empty() {
1206            return Err(ProxyError::SoftStop {
1207                proxy_protocol: "HTTP".to_string(),
1208                error: format!("Error deregistering listen sockets: {socket_errors:?}"),
1209            });
1210        }
1211
1212        Ok(())
1213    }
1214
1215    pub fn hard_stop(&mut self) -> Result<(), ProxyError> {
1216        let mut listeners: HashMap<_, _> = self.listeners.drain().collect();
1217        let mut socket_errors = vec![];
1218        for (_, l) in listeners.drain() {
1219            if let Some(mut sock) = l.borrow_mut().listener.take() {
1220                debug!("{} deregistering socket {:?}", log_module_context!(), sock);
1221                if let Err(e) = self.registry.deregister(&mut sock) {
1222                    let error = format!("socket {sock:?}: {e:?}");
1223                    socket_errors.push(error);
1224                }
1225            }
1226        }
1227
1228        if !socket_errors.is_empty() {
1229            return Err(ProxyError::HardStop {
1230                proxy_protocol: "HTTP".to_string(),
1231                error: format!("Error deregistering listen sockets: {socket_errors:?}"),
1232            });
1233        }
1234
1235        Ok(())
1236    }
1237}
1238
1239impl HttpListener {
1240    pub fn new(config: HttpListenerConfig, token: Token) -> Result<HttpListener, ListenerError> {
1241        let answers = Self::build_answers(&config)?;
1242        Ok(HttpListener {
1243            active: false,
1244            address: config.address.into(),
1245            answers: Rc::new(RefCell::new(answers)),
1246            config,
1247            fronts: Router::new(),
1248            listener: None,
1249            tags: BTreeMap::new(),
1250            token,
1251        })
1252    }
1253
1254    /// Build the listener's HTTP answer templates from its config, reconciling
1255    /// the legacy `http_answers` per-status fields with the new `answers`
1256    /// template map (the new map wins on collision; legacy fields fill any
1257    /// status not yet migrated). Shared by [`Self::new`] and
1258    /// [`Self::validate_config`] so the master-side pre-commit check cannot
1259    /// drift from worker construction.
1260    fn build_answers(config: &HttpListenerConfig) -> Result<HttpAnswers, ListenerError> {
1261        let mut answers_map = config.answers.clone();
1262        if let Some(ref legacy) = config.http_answers {
1263            crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1264        }
1265        HttpAnswers::new(&answers_map)
1266            .map_err(|(name, error)| ListenerError::TemplateParse(name, error))
1267    }
1268
1269    /// Validate that a worker can build this HTTP listener configuration (its
1270    /// answer templates) WITHOUT constructing the full listener or binding a
1271    /// socket. The main process calls this before committing an
1272    /// `AddHttpListener` to its `ConfigState` and fanning it out, so an invalid
1273    /// listener never reserves its address and blocks a corrected reload
1274    /// (sozu#1301).
1275    pub fn validate_config(config: &HttpListenerConfig) -> Result<(), ListenerError> {
1276        Self::build_answers(config)?;
1277        Ok(())
1278    }
1279
1280    pub fn activate(
1281        &mut self,
1282        registry: &Registry,
1283        tcp_listener: Option<MioTcpListener>,
1284    ) -> Result<Token, ListenerError> {
1285        if self.active {
1286            return Ok(self.token);
1287        }
1288        let address: SocketAddr = self.config.address.into();
1289
1290        let mut listener = match tcp_listener {
1291            Some(tcp_listener) => tcp_listener,
1292            None => {
1293                server_bind(address).map_err(|server_bind_error| ListenerError::Activation {
1294                    address,
1295                    error: server_bind_error.to_string(),
1296                })?
1297            }
1298        };
1299
1300        registry
1301            .register(&mut listener, self.token, Interest::READABLE)
1302            .map_err(ListenerError::SocketRegistration)?;
1303
1304        self.listener = Some(listener);
1305        self.active = true;
1306        Ok(self.token)
1307    }
1308
1309    /// Apply a partial-update patch to this listener's live configuration.
1310    ///
1311    /// Fields absent in the patch (i.e. `None`) are preserved unchanged.
1312    /// If `http_answers` is present only the listener-default templates are
1313    /// replaced; per-cluster overrides in `cluster_custom_answers` are kept.
1314    pub fn update_config(&mut self, patch: &UpdateHttpListenerConfig) -> Result<(), ListenerError> {
1315        // Defense-in-depth validation: main-process ConfigState::dispatch
1316        // validates before scatter, but a raw protobuf client or state replay
1317        // may reach the worker without that check. `StateError` lifts into
1318        // `ListenerError` via `From` so `?` suffices.
1319        validate_h2_flood_knobs_http(patch)?;
1320        if let Some(ref hdr) = patch.sozu_id_header {
1321            validate_sozu_id_header(hdr)?;
1322        }
1323
1324        if let Some(v) = patch.public_address {
1325            self.config.public_address = Some(v);
1326        }
1327        if let Some(v) = patch.expect_proxy {
1328            self.config.expect_proxy = v;
1329        }
1330        if let Some(ref v) = patch.sticky_name {
1331            self.config.sticky_name = v.to_owned();
1332        }
1333        if let Some(v) = patch.front_timeout {
1334            self.config.front_timeout = v;
1335        }
1336        if let Some(v) = patch.back_timeout {
1337            self.config.back_timeout = v;
1338        }
1339        if let Some(v) = patch.connect_timeout {
1340            self.config.connect_timeout = v;
1341        }
1342        if let Some(v) = patch.request_timeout {
1343            self.config.request_timeout = v;
1344        }
1345        if let Some(ref v) = patch.sozu_id_header {
1346            self.config.sozu_id_header = Some(v.to_owned());
1347        }
1348        if let Some(v) = patch.elide_x_real_ip {
1349            self.config.elide_x_real_ip = Some(v);
1350        }
1351        if let Some(v) = patch.send_x_real_ip {
1352            self.config.send_x_real_ip = Some(v);
1353        }
1354
1355        // H2 flood knobs
1356        if let Some(v) = patch.h2_max_rst_stream_per_window {
1357            self.config.h2_max_rst_stream_per_window = Some(v);
1358        }
1359        if let Some(v) = patch.h2_max_ping_per_window {
1360            self.config.h2_max_ping_per_window = Some(v);
1361        }
1362        if let Some(v) = patch.h2_max_settings_per_window {
1363            self.config.h2_max_settings_per_window = Some(v);
1364        }
1365        if let Some(v) = patch.h2_max_empty_data_per_window {
1366            self.config.h2_max_empty_data_per_window = Some(v);
1367        }
1368        if let Some(v) = patch.h2_max_continuation_frames {
1369            self.config.h2_max_continuation_frames = Some(v);
1370        }
1371        if let Some(v) = patch.h2_max_glitch_count {
1372            self.config.h2_max_glitch_count = Some(v);
1373        }
1374        if let Some(v) = patch.h2_initial_connection_window {
1375            self.config.h2_initial_connection_window = Some(v);
1376        }
1377        if let Some(v) = patch.h2_max_concurrent_streams {
1378            self.config.h2_max_concurrent_streams = Some(v);
1379        }
1380        if let Some(v) = patch.h2_stream_shrink_ratio {
1381            self.config.h2_stream_shrink_ratio = Some(v);
1382        }
1383        if let Some(v) = patch.h2_max_rst_stream_lifetime {
1384            self.config.h2_max_rst_stream_lifetime = Some(v);
1385        }
1386        if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
1387            self.config.h2_max_rst_stream_abusive_lifetime = Some(v);
1388        }
1389        if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
1390            self.config.h2_max_rst_stream_emitted_lifetime = Some(v);
1391        }
1392        if let Some(v) = patch.h2_max_header_list_size {
1393            self.config.h2_max_header_list_size = Some(v);
1394        }
1395        if let Some(v) = patch.h2_max_header_table_size {
1396            self.config.h2_max_header_table_size = Some(v);
1397        }
1398        if let Some(v) = patch.h2_max_header_fields {
1399            self.config.h2_max_header_fields = Some(v);
1400        }
1401        if let Some(v) = patch.h2_stream_idle_timeout_seconds {
1402            self.config.h2_stream_idle_timeout_seconds = Some(v);
1403        }
1404        if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
1405            self.config.h2_graceful_shutdown_deadline_seconds = Some(v);
1406        }
1407        if let Some(v) = patch.h2_max_window_update_stream0_per_window {
1408            self.config.h2_max_window_update_stream0_per_window = Some(v);
1409        }
1410
1411        // HTTP answers: merge legacy `http_answers` and the new `answers`
1412        // map on top of the existing config, then rebuild the listener-level
1413        // template registry. Per-cluster overrides in
1414        // `HttpAnswers::cluster_answers` are preserved across the rebuild.
1415        let answers_changed = patch.http_answers.is_some() || !patch.answers.is_empty();
1416        if answers_changed {
1417            if let Some(ref new_answers) = patch.http_answers {
1418                crate::sozu_command::state::merge_custom_http_answers(
1419                    &mut self.config.http_answers,
1420                    new_answers,
1421                );
1422            }
1423            for (code, body) in &patch.answers {
1424                if !body.is_empty() {
1425                    self.config.answers.insert(code.clone(), body.clone());
1426                }
1427            }
1428
1429            let mut answers_map = self.config.answers.clone();
1430            if let Some(ref legacy) = self.config.http_answers {
1431                crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1432            }
1433            // Rebuild the listener-level templates and migrate the existing
1434            // per-cluster overrides over to the new `HttpAnswers`.
1435            let mut new_answers = HttpAnswers::new(&answers_map)
1436                .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?;
1437            let preserved = std::mem::take(&mut self.answers.borrow_mut().cluster_answers);
1438            new_answers.cluster_answers = preserved;
1439            *self.answers.borrow_mut() = new_answers;
1440        }
1441
1442        Ok(())
1443    }
1444
1445    pub fn add_http_front(&mut self, http_front: HttpFrontend) -> Result<(), ListenerError> {
1446        self.fronts
1447            .add_http_front(&http_front)
1448            .map_err(ListenerError::AddFrontend)
1449    }
1450
1451    pub fn remove_http_front(&mut self, http_front: HttpFrontend) -> Result<(), ListenerError> {
1452        debug!(
1453            "{} removing http_front {:?}",
1454            log_module_context!(),
1455            http_front
1456        );
1457        self.fronts
1458            .remove_http_front(&http_front)
1459            .map_err(ListenerError::RemoveFrontend)
1460    }
1461
1462    fn accept(&mut self) -> Result<TcpStream, AcceptError> {
1463        if let Some(ref sock) = self.listener {
1464            sock.accept()
1465                .map_err(|e| match e.kind() {
1466                    ErrorKind::WouldBlock => AcceptError::WouldBlock,
1467                    _ => {
1468                        error!("{} accept() IO error: {:?}", log_module_context!(), e);
1469                        AcceptError::IoError
1470                    }
1471                })
1472                .map(|(sock, _)| sock)
1473        } else {
1474            error!(
1475                "{} cannot accept connections, no listening socket available",
1476                log_module_context!()
1477            );
1478            Err(AcceptError::IoError)
1479        }
1480    }
1481}
1482
1483impl ProxyConfiguration for HttpProxy {
1484    fn notify(&mut self, request: WorkerRequest) -> WorkerResponse {
1485        let request_id = request.id.clone();
1486
1487        let result = match request.content.request_type {
1488            Some(RequestType::AddCluster(cluster)) => {
1489                debug!(
1490                    "{} {} add cluster {:?}",
1491                    log_module_context!(),
1492                    request.id,
1493                    cluster
1494                );
1495                self.add_cluster(cluster)
1496            }
1497            Some(RequestType::RemoveCluster(cluster_id)) => {
1498                debug!(
1499                    "{} {} remove cluster {:?}",
1500                    log_module_context!(),
1501                    request_id,
1502                    cluster_id
1503                );
1504                self.remove_cluster(&cluster_id)
1505            }
1506            Some(RequestType::AddHttpFrontend(front)) => {
1507                debug!(
1508                    "{} {} add front {:?}",
1509                    log_module_context!(),
1510                    request_id,
1511                    front
1512                );
1513                self.add_http_frontend(front)
1514            }
1515            Some(RequestType::RemoveHttpFrontend(front)) => {
1516                debug!(
1517                    "{} {} remove front {:?}",
1518                    log_module_context!(),
1519                    request_id,
1520                    front
1521                );
1522                self.remove_http_frontend(front)
1523            }
1524            Some(RequestType::RemoveListener(remove)) => {
1525                debug!(
1526                    "{} removing HTTP listener at address {:?}",
1527                    log_module_context!(),
1528                    remove.address
1529                );
1530                self.remove_listener(remove)
1531            }
1532            Some(RequestType::SoftStop(_)) => {
1533                debug!(
1534                    "{} {} processing soft shutdown",
1535                    log_module_context!(),
1536                    request_id
1537                );
1538                match self.soft_stop() {
1539                    Ok(()) => {
1540                        info!(
1541                            "{} {} soft stop successful",
1542                            log_module_context!(),
1543                            request_id
1544                        );
1545                        return WorkerResponse::processing(request.id);
1546                    }
1547                    Err(e) => Err(e),
1548                }
1549            }
1550            Some(RequestType::HardStop(_)) => {
1551                debug!(
1552                    "{} {} processing hard shutdown",
1553                    log_module_context!(),
1554                    request_id
1555                );
1556                match self.hard_stop() {
1557                    Ok(()) => {
1558                        info!(
1559                            "{} {} hard stop successful",
1560                            log_module_context!(),
1561                            request_id
1562                        );
1563                        return WorkerResponse::processing(request.id);
1564                    }
1565                    Err(e) => Err(e),
1566                }
1567            }
1568            Some(RequestType::Status(_)) => {
1569                debug!("{} {} status", log_module_context!(), request_id);
1570                Ok(())
1571            }
1572            other_command => {
1573                debug!(
1574                    "{} {} unsupported message for HTTP proxy, ignoring: {:?}",
1575                    log_module_context!(),
1576                    request.id,
1577                    other_command
1578                );
1579                Err(ProxyError::UnsupportedMessage)
1580            }
1581        };
1582
1583        match result {
1584            Ok(()) => {
1585                debug!("{} {} successful", log_module_context!(), request_id);
1586                WorkerResponse::ok(request_id)
1587            }
1588            Err(proxy_error) => {
1589                debug!(
1590                    "{} {} unsuccessful: {}",
1591                    log_module_context!(),
1592                    request_id,
1593                    proxy_error
1594                );
1595                WorkerResponse::error(request_id, proxy_error)
1596            }
1597        }
1598    }
1599
1600    fn accept(&mut self, token: ListenToken) -> Result<TcpStream, AcceptError> {
1601        if let Some(listener) = self.listeners.get(&Token(token.0)) {
1602            listener.borrow_mut().accept()
1603        } else {
1604            Err(AcceptError::IoError)
1605        }
1606    }
1607
1608    fn create_session(
1609        &mut self,
1610        mut frontend_sock: TcpStream,
1611        listener_token: ListenToken,
1612        wait_time: Duration,
1613        proxy: Rc<RefCell<Self>>,
1614    ) -> Result<(), AcceptError> {
1615        let listener = self
1616            .listeners
1617            .get(&Token(listener_token.0))
1618            .cloned()
1619            .ok_or(AcceptError::IoError)?;
1620
1621        if let Err(e) = frontend_sock.set_nodelay(true) {
1622            error!(
1623                "{} error setting nodelay on front socket({:?}): {:?}",
1624                log_module_context!(),
1625                frontend_sock,
1626                e
1627            );
1628        }
1629        let mut session_manager = self.sessions.borrow_mut();
1630        let slab_len_before = session_manager.slab.len();
1631        let session_entry = session_manager.slab.vacant_entry();
1632        let session_token = Token(session_entry.key());
1633        // The token handed to the new session, used for epoll registration and
1634        // every `remove_session(frontend_token)` call, MUST be the very slab
1635        // key this entry will occupy. A drift here would deregister/free the
1636        // wrong slot on close.
1637        debug_assert_eq!(
1638            session_token.0,
1639            session_entry.key(),
1640            "session token must equal the slab vacant-entry key"
1641        );
1642        let owned = listener.borrow();
1643
1644        if let Err(register_error) = self.registry.register(
1645            &mut frontend_sock,
1646            session_token,
1647            Interest::READABLE | Interest::WRITABLE,
1648        ) {
1649            error!(
1650                "{} error registering listen socket({:?}): {:?}",
1651                log_module_context!(),
1652                frontend_sock,
1653                register_error
1654            );
1655            return Err(AcceptError::RegisterError);
1656        }
1657
1658        let public_address: SocketAddr = match owned.config.public_address {
1659            Some(pub_addr) => pub_addr.into(),
1660            None => owned.config.address.into(),
1661        };
1662
1663        let session = HttpSession::new(
1664            Duration::from_secs(owned.config.back_timeout as u64),
1665            Duration::from_secs(owned.config.connect_timeout as u64),
1666            Duration::from_secs(owned.config.front_timeout as u64),
1667            Duration::from_secs(owned.config.request_timeout as u64),
1668            owned.config.expect_proxy,
1669            listener.clone(),
1670            Rc::downgrade(&self.pool),
1671            proxy,
1672            public_address,
1673            frontend_sock,
1674            session_token,
1675            wait_time,
1676        )?;
1677
1678        // The session's frontend token must be exactly the slab key we are
1679        // about to fill — the registration above and all later token lookups
1680        // depend on it.
1681        debug_assert_eq!(
1682            session.frontend_token, session_token,
1683            "session must own the frontend token it was created with"
1684        );
1685
1686        let session = Rc::new(RefCell::new(session));
1687        session_entry.insert(session);
1688        // Inserting into the previously-vacant entry grows the live session
1689        // count by exactly one (gauge-like ±1 pairing against close()'s
1690        // try_remove). `len()` is the count of occupied slots.
1691        debug_assert_eq!(
1692            session_manager.slab.len(),
1693            slab_len_before + 1,
1694            "creating a session must occupy exactly one new slab slot"
1695        );
1696
1697        Ok(())
1698    }
1699}
1700
1701impl L7Proxy for HttpProxy {
1702    fn kind(&self) -> ListenerType {
1703        ListenerType::Http
1704    }
1705
1706    fn register_socket(
1707        &self,
1708        source: &mut TcpStream,
1709        token: Token,
1710        interest: Interest,
1711    ) -> Result<(), std::io::Error> {
1712        self.registry.register(source, token, interest)
1713    }
1714
1715    fn deregister_socket(&self, tcp_stream: &mut TcpStream) -> Result<(), std::io::Error> {
1716        self.registry.deregister(tcp_stream)
1717    }
1718
1719    fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token {
1720        let mut session_manager = self.sessions.borrow_mut();
1721        let len_before = session_manager.slab.len();
1722        let entry = session_manager.slab.vacant_entry();
1723        let token = Token(entry.key());
1724        let _entry = entry.insert(session);
1725        // The returned token is the slab key callers use to later remove this
1726        // session, and the insert occupied exactly one new slot.
1727        debug_assert_eq!(
1728            session_manager.slab.len(),
1729            len_before + 1,
1730            "add_session must occupy exactly one new slab slot"
1731        );
1732        debug_assert!(
1733            session_manager.slab.contains(token.0),
1734            "the returned token must index the freshly inserted session"
1735        );
1736        token
1737    }
1738
1739    fn remove_session(&self, token: Token) -> bool {
1740        let mut sessions = self.sessions.borrow_mut();
1741        let was_present = sessions.slab.contains(token.0);
1742        let len_before = sessions.slab.len();
1743        // Drain the session's `(cluster, ip)` accounting before the slab
1744        // slot is freed — once the slot is reused for a new session the
1745        // token would otherwise alias an unrelated set of entries. No-op
1746        // when the session never tracked anything (feature disabled, or
1747        // no request reached `Router::connect`).
1748        sessions.untrack_all_cluster_ip(token);
1749        let removed = sessions.slab.try_remove(token.0).is_some();
1750        // Removal must report exactly whether the slot was occupied, and the
1751        // live count drops by one iff something was actually removed (±1
1752        // pairing against add_session / create_session). The slot is gone.
1753        debug_assert_eq!(
1754            removed, was_present,
1755            "try_remove reports presence iff the slot was occupied"
1756        );
1757        debug_assert_eq!(
1758            sessions.slab.len(),
1759            len_before - removed as usize,
1760            "slab len drops by exactly one iff a session was removed"
1761        );
1762        debug_assert!(
1763            !sessions.slab.contains(token.0),
1764            "the slot must be free after remove_session"
1765        );
1766        removed
1767    }
1768
1769    fn backends(&self) -> Rc<RefCell<BackendMap>> {
1770        self.backends.clone()
1771    }
1772
1773    fn clusters(&self) -> &HashMap<ClusterId, Cluster> {
1774        &self.clusters
1775    }
1776
1777    fn sessions(&self) -> Rc<RefCell<SessionManager>> {
1778        self.sessions.clone()
1779    }
1780}
1781
1782pub mod testing {
1783    use crate::testing::*;
1784
1785    /// this function is not used, but is available for example and testing purposes
1786    pub fn start_http_worker(
1787        config: HttpListenerConfig,
1788        channel: ProxyChannel,
1789        max_buffers: usize,
1790        buffer_size: usize,
1791    ) -> anyhow::Result<()> {
1792        let address = config.address.into();
1793
1794        let ServerParts {
1795            event_loop,
1796            registry,
1797            sessions,
1798            pool,
1799            backends,
1800            client_scm_socket: _,
1801            server_scm_socket,
1802            server_config,
1803        } = prebuild_server(max_buffers, buffer_size, true)?;
1804
1805        let token = {
1806            let mut sessions = sessions.borrow_mut();
1807            let entry = sessions.slab.vacant_entry();
1808            let key = entry.key();
1809            let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
1810                protocol: Protocol::HTTPListen,
1811            })));
1812            Token(key)
1813        };
1814
1815        let mut proxy = HttpProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
1816        proxy
1817            .add_listener(config, token)
1818            .with_context(|| "Failed at creating adding the listener")?;
1819        proxy
1820            .activate_listener(&address, None)
1821            .with_context(|| "Failed at creating activating the listener")?;
1822
1823        let mut server = Server::new(
1824            event_loop,
1825            channel,
1826            server_scm_socket,
1827            sessions,
1828            pool,
1829            backends,
1830            Some(proxy),
1831            None,
1832            None,
1833            server_config,
1834            None,
1835            false,
1836        )
1837        .with_context(|| "Failed at creating server")?;
1838
1839        debug!("{} starting event loop", log_module_context!());
1840        server.run();
1841        debug!("{} ending event loop", log_module_context!());
1842        Ok(())
1843    }
1844}
1845
1846#[cfg(test)]
1847mod tests {
1848    extern crate tiny_http;
1849
1850    use std::{
1851        io::{Read, Write},
1852        net::TcpStream,
1853        str,
1854        sync::{Arc, Barrier},
1855        thread,
1856        time::Duration,
1857    };
1858
1859    use sozu_command::proto::command::SocketAddress;
1860
1861    use super::{testing::start_http_worker, *};
1862    use crate::sozu_command::{
1863        channel::Channel,
1864        config::ListenerBuilder,
1865        proto::command::{
1866            LoadBalancingParams, PathRule, RulePosition, SoftStop, WorkerRequest,
1867            request::RequestType,
1868        },
1869        response::{Backend, HttpFrontend},
1870    };
1871
1872    /*
1873    #[test]
1874    #[cfg(target_pointer_width = "64")]
1875    fn size_test() {
1876      assert_size!(ExpectProxyProtocol<mio::net::TcpStream>, 520);
1877      assert_size!(Http<mio::net::TcpStream>, 1232);
1878      assert_size!(Pipe<mio::net::TcpStream>, 272);
1879      assert_size!(State, 1240);
1880      // fails depending on the platform?
1881      assert_size!(Session, 1592);
1882    }
1883    */
1884
1885    #[test]
1886    fn round_trip() {
1887        setup_test_logger!();
1888        let front_port = crate::testing::provide_port();
1889        let backend_server = Arc::new(
1890            tiny_http::Server::http("127.0.0.1:0").expect("could not create tiny_http server"),
1891        );
1892        let backend_port = backend_server
1893            .server_addr()
1894            .to_ip()
1895            .expect("tiny_http server should bind to IP address")
1896            .port();
1897
1898        let barrier = Arc::new(Barrier::new(2));
1899
1900        let config = ListenerBuilder::new_http(SocketAddress::new_v4(127, 0, 0, 1, front_port))
1901            .to_http(None)
1902            .expect("could not create listener config");
1903
1904        let (mut command, channel) =
1905            Channel::generate(1000, 10000).expect("should create a channel");
1906
1907        thread::scope(|s| {
1908            let backend_handle = backend_server.clone();
1909            let barrier_clone = barrier.to_owned();
1910            s.spawn(move || {
1911                setup_test_logger!();
1912                start_server(&backend_handle, barrier_clone);
1913            });
1914            barrier.wait();
1915
1916            s.spawn(move || {
1917                setup_test_logger!();
1918                start_http_worker(config, channel, 10, 16384)
1919                    .expect("could not start the http server");
1920            });
1921
1922            let front = RequestHttpFrontend {
1923                cluster_id: Some("cluster_1".to_owned()),
1924                address: SocketAddress::new_v4(127, 0, 0, 1, front_port),
1925                hostname: "localhost".to_owned(),
1926                path: PathRule::prefix("/".to_owned()),
1927                ..Default::default()
1928            };
1929            command
1930                .write_message(&WorkerRequest {
1931                    id: "ID_ABCD".to_owned(),
1932                    content: RequestType::AddHttpFrontend(front).into(),
1933                })
1934                .expect("could not send AddHttpFrontend");
1935            let backend = Backend {
1936                cluster_id: "cluster_1".to_owned(),
1937                backend_id: "cluster_1-0".to_owned(),
1938                address: SocketAddress::new_v4(127, 0, 0, 1, backend_port).into(),
1939                load_balancing_parameters: Some(LoadBalancingParams::default()),
1940                sticky_id: None,
1941                backup: None,
1942            };
1943            command
1944                .write_message(&WorkerRequest {
1945                    id: "ID_EFGH".to_owned(),
1946                    content: RequestType::AddBackend(backend.to_add_backend()).into(),
1947                })
1948                .expect("could not send AddBackend");
1949
1950            println!("test received: {:?}", command.read_message());
1951            println!("test received: {:?}", command.read_message());
1952
1953            let mut client =
1954                TcpStream::connect(("127.0.0.1", front_port)).expect("could not connect to sozu");
1955
1956            client
1957                .set_read_timeout(Some(Duration::new(1, 0)))
1958                .expect("could not set read timeout");
1959            let request = format!(
1960                "GET / HTTP/1.1\r\nHost: localhost:{front_port}\r\nConnection: Close\r\n\r\n"
1961            );
1962            let w = client.write(request.as_bytes());
1963            println!("http client write: {w:?}");
1964
1965            barrier.wait();
1966            let mut buffer = [0; 4096];
1967            let mut index = 0;
1968
1969            // tiny_http responds with exactly 191 bytes for a "hello world" body
1970            // (headers + body). This is deterministic for a given tiny_http version.
1971            let expected_len = 191;
1972
1973            loop {
1974                assert!(index <= expected_len);
1975                if index == expected_len {
1976                    break;
1977                }
1978
1979                let r = client.read(&mut buffer[index..]);
1980                println!("http client read: {r:?}");
1981                match r {
1982                    Err(e) => panic!("client request should not fail. Error: {e:?}"),
1983                    Ok(sz) => {
1984                        index += sz;
1985                    }
1986                }
1987            }
1988            println!(
1989                "Response: {}",
1990                str::from_utf8(&buffer[..index]).expect("could not make string from buffer")
1991            );
1992
1993            // Gracefully stop the sozu worker so the scoped thread can join
1994            command
1995                .write_message(&WorkerRequest {
1996                    id: "ID_STOP".to_owned(),
1997                    content: RequestType::SoftStop(SoftStop {}).into(),
1998                })
1999                .expect("could not send SoftStop");
2000            // Unblock the backend server so its thread can exit
2001            backend_server.unblock();
2002        });
2003    }
2004
2005    #[test]
2006    fn keep_alive() {
2007        setup_test_logger!();
2008        let front_port = crate::testing::provide_port();
2009        let backend_server = Arc::new(
2010            tiny_http::Server::http("127.0.0.1:0").expect("could not create tiny_http server"),
2011        );
2012        let backend_port = backend_server
2013            .server_addr()
2014            .to_ip()
2015            .expect("tiny_http server should bind to IP address")
2016            .port();
2017
2018        let barrier = Arc::new(Barrier::new(2));
2019
2020        let config = ListenerBuilder::new_http(SocketAddress::new_v4(127, 0, 0, 1, front_port))
2021            .to_http(None)
2022            .expect("could not create listener config");
2023
2024        let (mut command, channel) =
2025            Channel::generate(1000, 10000).expect("should create a channel");
2026
2027        thread::scope(|s| {
2028            let backend_handle = backend_server.clone();
2029            let barrier_clone = barrier.to_owned();
2030            s.spawn(move || {
2031                setup_test_logger!();
2032                start_server(&backend_handle, barrier_clone);
2033            });
2034            barrier.wait();
2035
2036            s.spawn(move || {
2037                setup_test_logger!();
2038                start_http_worker(config, channel, 10, 16384)
2039                    .expect("could not start the http server");
2040            });
2041
2042            let front = RequestHttpFrontend {
2043                address: SocketAddress::new_v4(127, 0, 0, 1, front_port),
2044                hostname: "localhost".to_owned(),
2045                path: PathRule::prefix("/".to_owned()),
2046                cluster_id: Some("cluster_1".to_owned()),
2047                ..Default::default()
2048            };
2049            command
2050                .write_message(&WorkerRequest {
2051                    id: "ID_ABCD".to_owned(),
2052                    content: RequestType::AddHttpFrontend(front).into(),
2053                })
2054                .expect("could not send AddHttpFrontend");
2055            let backend = Backend {
2056                address: SocketAddress::new_v4(127, 0, 0, 1, backend_port).into(),
2057                backend_id: "cluster_1-0".to_owned(),
2058                backup: None,
2059                cluster_id: "cluster_1".to_owned(),
2060                load_balancing_parameters: Some(LoadBalancingParams::default()),
2061                sticky_id: None,
2062            };
2063            command
2064                .write_message(&WorkerRequest {
2065                    id: "ID_EFGH".to_owned(),
2066                    content: RequestType::AddBackend(backend.to_add_backend()).into(),
2067                })
2068                .expect("could not send AddBackend");
2069
2070            println!("test received: {:?}", command.read_message());
2071            println!("test received: {:?}", command.read_message());
2072
2073            let mut client =
2074                TcpStream::connect(("127.0.0.1", front_port)).expect("could not connect to sozu");
2075            client
2076                .set_read_timeout(Some(Duration::new(5, 0)))
2077                .expect("could not set read timeout");
2078
2079            // tiny_http responds with exactly 191 bytes for a "hello world" body
2080            // (headers + body). This is deterministic for a given tiny_http version.
2081            let expected_len = 191;
2082
2083            let request = format!("GET / HTTP/1.1\r\nHost: localhost:{front_port}\r\n\r\n");
2084            let w = client
2085                .write(request.as_bytes())
2086                .expect("could not write first request");
2087            println!("http client write: {w:?}");
2088            barrier.wait();
2089
2090            let mut buffer = [0; 4096];
2091            let mut index = 0;
2092
2093            loop {
2094                assert!(index <= expected_len);
2095                if index == expected_len {
2096                    break;
2097                }
2098
2099                let r = client.read(&mut buffer[index..]);
2100                println!("http client read: {r:?}");
2101                match r {
2102                    Err(e) => panic!("client request should not fail. Error: {e:?}"),
2103                    Ok(sz) => {
2104                        index += sz;
2105                    }
2106                }
2107            }
2108
2109            println!(
2110                "Response: {}",
2111                str::from_utf8(&buffer[..index]).expect("could not make string from buffer")
2112            );
2113
2114            println!("first request ended, will send second one");
2115            let request2 = format!("GET / HTTP/1.1\r\nHost: localhost:{front_port}\r\n\r\n");
2116            let w2 = client.write(request2.as_bytes());
2117            println!("http client write: {w2:?}");
2118            barrier.wait();
2119
2120            let mut buffer2 = [0; 4096];
2121            let mut index = 0;
2122
2123            loop {
2124                assert!(index <= expected_len);
2125                if index == expected_len {
2126                    break;
2127                }
2128
2129                let r2 = client.read(&mut buffer2[index..]);
2130                println!("http client read: {r2:?}");
2131                match r2 {
2132                    Err(e) => panic!("client request should not fail. Error: {e:?}"),
2133                    Ok(sz) => {
2134                        index += sz;
2135                    }
2136                }
2137            }
2138            println!(
2139                "Response: {}",
2140                str::from_utf8(&buffer2[..index]).expect("could not make string from buffer")
2141            );
2142
2143            // Gracefully stop the sozu worker so the scoped thread can join
2144            command
2145                .write_message(&WorkerRequest {
2146                    id: "ID_STOP".to_owned(),
2147                    content: RequestType::SoftStop(SoftStop {}).into(),
2148                })
2149                .expect("could not send SoftStop");
2150            // Unblock the backend server so its thread can exit
2151            backend_server.unblock();
2152        });
2153    }
2154
2155    use self::tiny_http::Response;
2156
2157    fn start_server(server: &tiny_http::Server, barrier: Arc<Barrier>) {
2158        let addr = server.server_addr();
2159        info!("starting web server on {:?}", addr);
2160        barrier.wait();
2161
2162        for request in server.incoming_requests() {
2163            info!(
2164                "backend web server got request -> method: {:?}, url: {:?}, headers: {:?}",
2165                request.method(),
2166                request.url(),
2167                request.headers()
2168            );
2169
2170            let response = Response::from_string("hello world");
2171            request
2172                .respond(response)
2173                .expect("could not respond to request");
2174            info!("backend web server sent response");
2175            barrier.wait();
2176            info!("server session stopped");
2177        }
2178
2179        println!("server on {addr:?} closed");
2180    }
2181
2182    #[test]
2183    fn frontend_from_request_test() {
2184        let cluster_id1 = "cluster_1".to_owned();
2185        let cluster_id2 = "cluster_2".to_owned();
2186        let cluster_id3 = "cluster_3".to_owned();
2187        let uri1 = "/".to_owned();
2188        let uri2 = "/yolo".to_owned();
2189        let uri3 = "/yolo/swag".to_owned();
2190
2191        let mut fronts = Router::new();
2192        fronts
2193            .add_http_front(&HttpFrontend {
2194                address: "0.0.0.0:80".parse().unwrap(),
2195                hostname: "lolcatho.st".to_owned(),
2196                method: None,
2197                path: PathRule::prefix(uri1),
2198                position: RulePosition::Tree,
2199                cluster_id: Some(cluster_id1),
2200                tags: None,
2201                redirect: None,
2202                redirect_scheme: None,
2203                redirect_template: None,
2204                rewrite_host: None,
2205                rewrite_path: None,
2206                rewrite_port: None,
2207                required_auth: None,
2208                headers: Vec::new(),
2209                hsts: None,
2210            })
2211            .expect("Could not add http frontend");
2212        fronts
2213            .add_http_front(&HttpFrontend {
2214                address: "0.0.0.0:80".parse().unwrap(),
2215                hostname: "lolcatho.st".to_owned(),
2216                method: None,
2217                path: PathRule::prefix(uri2),
2218                position: RulePosition::Tree,
2219                cluster_id: Some(cluster_id2),
2220                tags: None,
2221                redirect: None,
2222                redirect_scheme: None,
2223                redirect_template: None,
2224                rewrite_host: None,
2225                rewrite_path: None,
2226                rewrite_port: None,
2227                required_auth: None,
2228                headers: Vec::new(),
2229                hsts: None,
2230            })
2231            .expect("Could not add http frontend");
2232        fronts
2233            .add_http_front(&HttpFrontend {
2234                address: "0.0.0.0:80".parse().unwrap(),
2235                hostname: "lolcatho.st".to_owned(),
2236                method: None,
2237                path: PathRule::prefix(uri3),
2238                position: RulePosition::Tree,
2239                cluster_id: Some(cluster_id3),
2240                tags: None,
2241                redirect: None,
2242                redirect_scheme: None,
2243                redirect_template: None,
2244                rewrite_host: None,
2245                rewrite_path: None,
2246                rewrite_port: None,
2247                required_auth: None,
2248                headers: Vec::new(),
2249                hsts: None,
2250            })
2251            .expect("Could not add http frontend");
2252        fronts
2253            .add_http_front(&HttpFrontend {
2254                address: "0.0.0.0:80".parse().unwrap(),
2255                hostname: "other.domain".to_owned(),
2256                method: None,
2257                path: PathRule::prefix("/test".to_owned()),
2258                position: RulePosition::Tree,
2259                cluster_id: Some("cluster_1".to_owned()),
2260                tags: None,
2261                redirect: None,
2262                redirect_scheme: None,
2263                redirect_template: None,
2264                rewrite_host: None,
2265                rewrite_path: None,
2266                rewrite_port: None,
2267                required_auth: None,
2268                headers: Vec::new(),
2269                hsts: None,
2270            })
2271            .expect("Could not add http frontend");
2272
2273        let address = SocketAddress::new_v4(127, 0, 0, 1, 1030);
2274
2275        let default_config = ListenerBuilder::new_http(address)
2276            .to_http(None)
2277            .expect("Could not create default HTTP listener config");
2278
2279        let listener = HttpListener {
2280            listener: None,
2281            address: address.into(),
2282            fronts,
2283            answers: Rc::new(RefCell::new(HttpAnswers::new(&BTreeMap::new()).unwrap())),
2284            config: default_config,
2285            token: Token(0),
2286            active: true,
2287            tags: BTreeMap::new(),
2288        };
2289
2290        let frontend1 = listener.frontend_from_request("lolcatho.st", "/", &Method::Get);
2291        let frontend2 = listener.frontend_from_request("lolcatho.st", "/test", &Method::Get);
2292        let frontend3 = listener.frontend_from_request("lolcatho.st", "/yolo/test", &Method::Get);
2293        let frontend4 = listener.frontend_from_request("lolcatho.st", "/yolo/swag", &Method::Get);
2294        let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get);
2295        assert_eq!(
2296            frontend1
2297                .expect("should find frontend")
2298                .cluster_id
2299                .as_deref(),
2300            Some("cluster_1")
2301        );
2302        assert_eq!(
2303            frontend2
2304                .expect("should find frontend")
2305                .cluster_id
2306                .as_deref(),
2307            Some("cluster_1")
2308        );
2309        assert_eq!(
2310            frontend3
2311                .expect("should find frontend")
2312                .cluster_id
2313                .as_deref(),
2314            Some("cluster_2")
2315        );
2316        assert_eq!(
2317            frontend4
2318                .expect("should find frontend")
2319                .cluster_id
2320                .as_deref(),
2321            Some("cluster_3")
2322        );
2323        assert!(frontend5.is_err());
2324    }
2325
2326    #[test]
2327    fn h2_stream_idle_timeout_inherits_back_timeout() {
2328        let address = SocketAddress::new_v4(127, 0, 0, 1, 1040);
2329        let build = |back_timeout: u32, explicit: Option<u32>| -> HttpListener {
2330            let mut cfg = ListenerBuilder::new_http(address)
2331                .to_http(None)
2332                .expect("default HTTP listener config");
2333            cfg.back_timeout = back_timeout;
2334            cfg.h2_stream_idle_timeout_seconds = explicit;
2335            HttpListener::new(cfg, Token(0)).expect("build listener")
2336        };
2337
2338        // Knob unset: inherit back_timeout when it exceeds the 30s floor.
2339        assert_eq!(
2340            build(180, None).get_h2_stream_idle_timeout(),
2341            Duration::from_secs(180)
2342        );
2343
2344        // Knob unset, back_timeout below floor: stay at 30s to preserve the
2345        // slow-multiplex Slowloris mitigation.
2346        assert_eq!(
2347            build(5, None).get_h2_stream_idle_timeout(),
2348            Duration::from_secs(30)
2349        );
2350
2351        // Explicit values win in both directions — including below the floor,
2352        // so operators under attack can tighten the deadline.
2353        assert_eq!(
2354            build(180, Some(10)).get_h2_stream_idle_timeout(),
2355            Duration::from_secs(10)
2356        );
2357        assert_eq!(
2358            build(5, Some(600)).get_h2_stream_idle_timeout(),
2359            Duration::from_secs(600)
2360        );
2361
2362        // `Some(0)` is clamped to 1s to keep the deadline non-degenerate.
2363        assert_eq!(
2364            build(180, Some(0)).get_h2_stream_idle_timeout(),
2365            Duration::from_secs(1)
2366        );
2367    }
2368}