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            .iter()
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.iter() {
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        Ok(HttpListener {
1242            active: false,
1243            address: config.address.into(),
1244            answers: Rc::new(RefCell::new({
1245                // Reconcile the legacy `http_answers` per-status fields
1246                // with the new template map: the new map wins on
1247                // collision, the legacy fields fill in any status the
1248                // operator hasn't yet migrated.
1249                let mut answers_map = config.answers.clone();
1250                if let Some(ref legacy) = config.http_answers {
1251                    crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1252                }
1253                HttpAnswers::new(&answers_map)
1254                    .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?
1255            })),
1256            config,
1257            fronts: Router::new(),
1258            listener: None,
1259            tags: BTreeMap::new(),
1260            token,
1261        })
1262    }
1263
1264    pub fn activate(
1265        &mut self,
1266        registry: &Registry,
1267        tcp_listener: Option<MioTcpListener>,
1268    ) -> Result<Token, ListenerError> {
1269        if self.active {
1270            return Ok(self.token);
1271        }
1272        let address: SocketAddr = self.config.address.into();
1273
1274        let mut listener = match tcp_listener {
1275            Some(tcp_listener) => tcp_listener,
1276            None => {
1277                server_bind(address).map_err(|server_bind_error| ListenerError::Activation {
1278                    address,
1279                    error: server_bind_error.to_string(),
1280                })?
1281            }
1282        };
1283
1284        registry
1285            .register(&mut listener, self.token, Interest::READABLE)
1286            .map_err(ListenerError::SocketRegistration)?;
1287
1288        self.listener = Some(listener);
1289        self.active = true;
1290        Ok(self.token)
1291    }
1292
1293    /// Apply a partial-update patch to this listener's live configuration.
1294    ///
1295    /// Fields absent in the patch (i.e. `None`) are preserved unchanged.
1296    /// If `http_answers` is present only the listener-default templates are
1297    /// replaced; per-cluster overrides in `cluster_custom_answers` are kept.
1298    pub fn update_config(&mut self, patch: &UpdateHttpListenerConfig) -> Result<(), ListenerError> {
1299        // Defense-in-depth validation: main-process ConfigState::dispatch
1300        // validates before scatter, but a raw protobuf client or state replay
1301        // may reach the worker without that check. `StateError` lifts into
1302        // `ListenerError` via `From` so `?` suffices.
1303        validate_h2_flood_knobs_http(patch)?;
1304        if let Some(ref hdr) = patch.sozu_id_header {
1305            validate_sozu_id_header(hdr)?;
1306        }
1307
1308        if let Some(v) = patch.public_address {
1309            self.config.public_address = Some(v);
1310        }
1311        if let Some(v) = patch.expect_proxy {
1312            self.config.expect_proxy = v;
1313        }
1314        if let Some(ref v) = patch.sticky_name {
1315            self.config.sticky_name = v.to_owned();
1316        }
1317        if let Some(v) = patch.front_timeout {
1318            self.config.front_timeout = v;
1319        }
1320        if let Some(v) = patch.back_timeout {
1321            self.config.back_timeout = v;
1322        }
1323        if let Some(v) = patch.connect_timeout {
1324            self.config.connect_timeout = v;
1325        }
1326        if let Some(v) = patch.request_timeout {
1327            self.config.request_timeout = v;
1328        }
1329        if let Some(ref v) = patch.sozu_id_header {
1330            self.config.sozu_id_header = Some(v.to_owned());
1331        }
1332        if let Some(v) = patch.elide_x_real_ip {
1333            self.config.elide_x_real_ip = Some(v);
1334        }
1335        if let Some(v) = patch.send_x_real_ip {
1336            self.config.send_x_real_ip = Some(v);
1337        }
1338
1339        // H2 flood knobs
1340        if let Some(v) = patch.h2_max_rst_stream_per_window {
1341            self.config.h2_max_rst_stream_per_window = Some(v);
1342        }
1343        if let Some(v) = patch.h2_max_ping_per_window {
1344            self.config.h2_max_ping_per_window = Some(v);
1345        }
1346        if let Some(v) = patch.h2_max_settings_per_window {
1347            self.config.h2_max_settings_per_window = Some(v);
1348        }
1349        if let Some(v) = patch.h2_max_empty_data_per_window {
1350            self.config.h2_max_empty_data_per_window = Some(v);
1351        }
1352        if let Some(v) = patch.h2_max_continuation_frames {
1353            self.config.h2_max_continuation_frames = Some(v);
1354        }
1355        if let Some(v) = patch.h2_max_glitch_count {
1356            self.config.h2_max_glitch_count = Some(v);
1357        }
1358        if let Some(v) = patch.h2_initial_connection_window {
1359            self.config.h2_initial_connection_window = Some(v);
1360        }
1361        if let Some(v) = patch.h2_max_concurrent_streams {
1362            self.config.h2_max_concurrent_streams = Some(v);
1363        }
1364        if let Some(v) = patch.h2_stream_shrink_ratio {
1365            self.config.h2_stream_shrink_ratio = Some(v);
1366        }
1367        if let Some(v) = patch.h2_max_rst_stream_lifetime {
1368            self.config.h2_max_rst_stream_lifetime = Some(v);
1369        }
1370        if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
1371            self.config.h2_max_rst_stream_abusive_lifetime = Some(v);
1372        }
1373        if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
1374            self.config.h2_max_rst_stream_emitted_lifetime = Some(v);
1375        }
1376        if let Some(v) = patch.h2_max_header_list_size {
1377            self.config.h2_max_header_list_size = Some(v);
1378        }
1379        if let Some(v) = patch.h2_max_header_table_size {
1380            self.config.h2_max_header_table_size = Some(v);
1381        }
1382        if let Some(v) = patch.h2_max_header_fields {
1383            self.config.h2_max_header_fields = Some(v);
1384        }
1385        if let Some(v) = patch.h2_stream_idle_timeout_seconds {
1386            self.config.h2_stream_idle_timeout_seconds = Some(v);
1387        }
1388        if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
1389            self.config.h2_graceful_shutdown_deadline_seconds = Some(v);
1390        }
1391        if let Some(v) = patch.h2_max_window_update_stream0_per_window {
1392            self.config.h2_max_window_update_stream0_per_window = Some(v);
1393        }
1394
1395        // HTTP answers: merge legacy `http_answers` and the new `answers`
1396        // map on top of the existing config, then rebuild the listener-level
1397        // template registry. Per-cluster overrides in
1398        // `HttpAnswers::cluster_answers` are preserved across the rebuild.
1399        let answers_changed = patch.http_answers.is_some() || !patch.answers.is_empty();
1400        if answers_changed {
1401            if let Some(ref new_answers) = patch.http_answers {
1402                crate::sozu_command::state::merge_custom_http_answers(
1403                    &mut self.config.http_answers,
1404                    new_answers,
1405                );
1406            }
1407            for (code, body) in &patch.answers {
1408                if !body.is_empty() {
1409                    self.config.answers.insert(code.clone(), body.clone());
1410                }
1411            }
1412
1413            let mut answers_map = self.config.answers.clone();
1414            if let Some(ref legacy) = self.config.http_answers {
1415                crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1416            }
1417            // Rebuild the listener-level templates and migrate the existing
1418            // per-cluster overrides over to the new `HttpAnswers`.
1419            let mut new_answers = HttpAnswers::new(&answers_map)
1420                .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?;
1421            let preserved = std::mem::take(&mut self.answers.borrow_mut().cluster_answers);
1422            new_answers.cluster_answers = preserved;
1423            *self.answers.borrow_mut() = new_answers;
1424        }
1425
1426        Ok(())
1427    }
1428
1429    pub fn add_http_front(&mut self, http_front: HttpFrontend) -> Result<(), ListenerError> {
1430        self.fronts
1431            .add_http_front(&http_front)
1432            .map_err(ListenerError::AddFrontend)
1433    }
1434
1435    pub fn remove_http_front(&mut self, http_front: HttpFrontend) -> Result<(), ListenerError> {
1436        debug!(
1437            "{} removing http_front {:?}",
1438            log_module_context!(),
1439            http_front
1440        );
1441        self.fronts
1442            .remove_http_front(&http_front)
1443            .map_err(ListenerError::RemoveFrontend)
1444    }
1445
1446    fn accept(&mut self) -> Result<TcpStream, AcceptError> {
1447        if let Some(ref sock) = self.listener {
1448            sock.accept()
1449                .map_err(|e| match e.kind() {
1450                    ErrorKind::WouldBlock => AcceptError::WouldBlock,
1451                    _ => {
1452                        error!("{} accept() IO error: {:?}", log_module_context!(), e);
1453                        AcceptError::IoError
1454                    }
1455                })
1456                .map(|(sock, _)| sock)
1457        } else {
1458            error!(
1459                "{} cannot accept connections, no listening socket available",
1460                log_module_context!()
1461            );
1462            Err(AcceptError::IoError)
1463        }
1464    }
1465}
1466
1467impl ProxyConfiguration for HttpProxy {
1468    fn notify(&mut self, request: WorkerRequest) -> WorkerResponse {
1469        let request_id = request.id.clone();
1470
1471        let result = match request.content.request_type {
1472            Some(RequestType::AddCluster(cluster)) => {
1473                debug!(
1474                    "{} {} add cluster {:?}",
1475                    log_module_context!(),
1476                    request.id,
1477                    cluster
1478                );
1479                self.add_cluster(cluster)
1480            }
1481            Some(RequestType::RemoveCluster(cluster_id)) => {
1482                debug!(
1483                    "{} {} remove cluster {:?}",
1484                    log_module_context!(),
1485                    request_id,
1486                    cluster_id
1487                );
1488                self.remove_cluster(&cluster_id)
1489            }
1490            Some(RequestType::AddHttpFrontend(front)) => {
1491                debug!(
1492                    "{} {} add front {:?}",
1493                    log_module_context!(),
1494                    request_id,
1495                    front
1496                );
1497                self.add_http_frontend(front)
1498            }
1499            Some(RequestType::RemoveHttpFrontend(front)) => {
1500                debug!(
1501                    "{} {} remove front {:?}",
1502                    log_module_context!(),
1503                    request_id,
1504                    front
1505                );
1506                self.remove_http_frontend(front)
1507            }
1508            Some(RequestType::RemoveListener(remove)) => {
1509                debug!(
1510                    "{} removing HTTP listener at address {:?}",
1511                    log_module_context!(),
1512                    remove.address
1513                );
1514                self.remove_listener(remove)
1515            }
1516            Some(RequestType::SoftStop(_)) => {
1517                debug!(
1518                    "{} {} processing soft shutdown",
1519                    log_module_context!(),
1520                    request_id
1521                );
1522                match self.soft_stop() {
1523                    Ok(()) => {
1524                        info!(
1525                            "{} {} soft stop successful",
1526                            log_module_context!(),
1527                            request_id
1528                        );
1529                        return WorkerResponse::processing(request.id);
1530                    }
1531                    Err(e) => Err(e),
1532                }
1533            }
1534            Some(RequestType::HardStop(_)) => {
1535                debug!(
1536                    "{} {} processing hard shutdown",
1537                    log_module_context!(),
1538                    request_id
1539                );
1540                match self.hard_stop() {
1541                    Ok(()) => {
1542                        info!(
1543                            "{} {} hard stop successful",
1544                            log_module_context!(),
1545                            request_id
1546                        );
1547                        return WorkerResponse::processing(request.id);
1548                    }
1549                    Err(e) => Err(e),
1550                }
1551            }
1552            Some(RequestType::Status(_)) => {
1553                debug!("{} {} status", log_module_context!(), request_id);
1554                Ok(())
1555            }
1556            other_command => {
1557                debug!(
1558                    "{} {} unsupported message for HTTP proxy, ignoring: {:?}",
1559                    log_module_context!(),
1560                    request.id,
1561                    other_command
1562                );
1563                Err(ProxyError::UnsupportedMessage)
1564            }
1565        };
1566
1567        match result {
1568            Ok(()) => {
1569                debug!("{} {} successful", log_module_context!(), request_id);
1570                WorkerResponse::ok(request_id)
1571            }
1572            Err(proxy_error) => {
1573                debug!(
1574                    "{} {} unsuccessful: {}",
1575                    log_module_context!(),
1576                    request_id,
1577                    proxy_error
1578                );
1579                WorkerResponse::error(request_id, proxy_error)
1580            }
1581        }
1582    }
1583
1584    fn accept(&mut self, token: ListenToken) -> Result<TcpStream, AcceptError> {
1585        if let Some(listener) = self.listeners.get(&Token(token.0)) {
1586            listener.borrow_mut().accept()
1587        } else {
1588            Err(AcceptError::IoError)
1589        }
1590    }
1591
1592    fn create_session(
1593        &mut self,
1594        mut frontend_sock: TcpStream,
1595        listener_token: ListenToken,
1596        wait_time: Duration,
1597        proxy: Rc<RefCell<Self>>,
1598    ) -> Result<(), AcceptError> {
1599        let listener = self
1600            .listeners
1601            .get(&Token(listener_token.0))
1602            .cloned()
1603            .ok_or(AcceptError::IoError)?;
1604
1605        if let Err(e) = frontend_sock.set_nodelay(true) {
1606            error!(
1607                "{} error setting nodelay on front socket({:?}): {:?}",
1608                log_module_context!(),
1609                frontend_sock,
1610                e
1611            );
1612        }
1613        let mut session_manager = self.sessions.borrow_mut();
1614        let slab_len_before = session_manager.slab.len();
1615        let session_entry = session_manager.slab.vacant_entry();
1616        let session_token = Token(session_entry.key());
1617        // The token handed to the new session, used for epoll registration and
1618        // every `remove_session(frontend_token)` call, MUST be the very slab
1619        // key this entry will occupy. A drift here would deregister/free the
1620        // wrong slot on close.
1621        debug_assert_eq!(
1622            session_token.0,
1623            session_entry.key(),
1624            "session token must equal the slab vacant-entry key"
1625        );
1626        let owned = listener.borrow();
1627
1628        if let Err(register_error) = self.registry.register(
1629            &mut frontend_sock,
1630            session_token,
1631            Interest::READABLE | Interest::WRITABLE,
1632        ) {
1633            error!(
1634                "{} error registering listen socket({:?}): {:?}",
1635                log_module_context!(),
1636                frontend_sock,
1637                register_error
1638            );
1639            return Err(AcceptError::RegisterError);
1640        }
1641
1642        let public_address: SocketAddr = match owned.config.public_address {
1643            Some(pub_addr) => pub_addr.into(),
1644            None => owned.config.address.into(),
1645        };
1646
1647        let session = HttpSession::new(
1648            Duration::from_secs(owned.config.back_timeout as u64),
1649            Duration::from_secs(owned.config.connect_timeout as u64),
1650            Duration::from_secs(owned.config.front_timeout as u64),
1651            Duration::from_secs(owned.config.request_timeout as u64),
1652            owned.config.expect_proxy,
1653            listener.clone(),
1654            Rc::downgrade(&self.pool),
1655            proxy,
1656            public_address,
1657            frontend_sock,
1658            session_token,
1659            wait_time,
1660        )?;
1661
1662        // The session's frontend token must be exactly the slab key we are
1663        // about to fill — the registration above and all later token lookups
1664        // depend on it.
1665        debug_assert_eq!(
1666            session.frontend_token, session_token,
1667            "session must own the frontend token it was created with"
1668        );
1669
1670        let session = Rc::new(RefCell::new(session));
1671        session_entry.insert(session);
1672        // Inserting into the previously-vacant entry grows the live session
1673        // count by exactly one (gauge-like ±1 pairing against close()'s
1674        // try_remove). `len()` is the count of occupied slots.
1675        debug_assert_eq!(
1676            session_manager.slab.len(),
1677            slab_len_before + 1,
1678            "creating a session must occupy exactly one new slab slot"
1679        );
1680
1681        Ok(())
1682    }
1683}
1684
1685impl L7Proxy for HttpProxy {
1686    fn kind(&self) -> ListenerType {
1687        ListenerType::Http
1688    }
1689
1690    fn register_socket(
1691        &self,
1692        source: &mut TcpStream,
1693        token: Token,
1694        interest: Interest,
1695    ) -> Result<(), std::io::Error> {
1696        self.registry.register(source, token, interest)
1697    }
1698
1699    fn deregister_socket(&self, tcp_stream: &mut TcpStream) -> Result<(), std::io::Error> {
1700        self.registry.deregister(tcp_stream)
1701    }
1702
1703    fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token {
1704        let mut session_manager = self.sessions.borrow_mut();
1705        let len_before = session_manager.slab.len();
1706        let entry = session_manager.slab.vacant_entry();
1707        let token = Token(entry.key());
1708        let _entry = entry.insert(session);
1709        // The returned token is the slab key callers use to later remove this
1710        // session, and the insert occupied exactly one new slot.
1711        debug_assert_eq!(
1712            session_manager.slab.len(),
1713            len_before + 1,
1714            "add_session must occupy exactly one new slab slot"
1715        );
1716        debug_assert!(
1717            session_manager.slab.contains(token.0),
1718            "the returned token must index the freshly inserted session"
1719        );
1720        token
1721    }
1722
1723    fn remove_session(&self, token: Token) -> bool {
1724        let mut sessions = self.sessions.borrow_mut();
1725        let was_present = sessions.slab.contains(token.0);
1726        let len_before = sessions.slab.len();
1727        // Drain the session's `(cluster, ip)` accounting before the slab
1728        // slot is freed — once the slot is reused for a new session the
1729        // token would otherwise alias an unrelated set of entries. No-op
1730        // when the session never tracked anything (feature disabled, or
1731        // no request reached `Router::connect`).
1732        sessions.untrack_all_cluster_ip(token);
1733        let removed = sessions.slab.try_remove(token.0).is_some();
1734        // Removal must report exactly whether the slot was occupied, and the
1735        // live count drops by one iff something was actually removed (±1
1736        // pairing against add_session / create_session). The slot is gone.
1737        debug_assert_eq!(
1738            removed, was_present,
1739            "try_remove reports presence iff the slot was occupied"
1740        );
1741        debug_assert_eq!(
1742            sessions.slab.len(),
1743            len_before - removed as usize,
1744            "slab len drops by exactly one iff a session was removed"
1745        );
1746        debug_assert!(
1747            !sessions.slab.contains(token.0),
1748            "the slot must be free after remove_session"
1749        );
1750        removed
1751    }
1752
1753    fn backends(&self) -> Rc<RefCell<BackendMap>> {
1754        self.backends.clone()
1755    }
1756
1757    fn clusters(&self) -> &HashMap<ClusterId, Cluster> {
1758        &self.clusters
1759    }
1760
1761    fn sessions(&self) -> Rc<RefCell<SessionManager>> {
1762        self.sessions.clone()
1763    }
1764}
1765
1766pub mod testing {
1767    use crate::testing::*;
1768
1769    /// this function is not used, but is available for example and testing purposes
1770    pub fn start_http_worker(
1771        config: HttpListenerConfig,
1772        channel: ProxyChannel,
1773        max_buffers: usize,
1774        buffer_size: usize,
1775    ) -> anyhow::Result<()> {
1776        let address = config.address.into();
1777
1778        let ServerParts {
1779            event_loop,
1780            registry,
1781            sessions,
1782            pool,
1783            backends,
1784            client_scm_socket: _,
1785            server_scm_socket,
1786            server_config,
1787        } = prebuild_server(max_buffers, buffer_size, true)?;
1788
1789        let token = {
1790            let mut sessions = sessions.borrow_mut();
1791            let entry = sessions.slab.vacant_entry();
1792            let key = entry.key();
1793            let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
1794                protocol: Protocol::HTTPListen,
1795            })));
1796            Token(key)
1797        };
1798
1799        let mut proxy = HttpProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
1800        proxy
1801            .add_listener(config, token)
1802            .with_context(|| "Failed at creating adding the listener")?;
1803        proxy
1804            .activate_listener(&address, None)
1805            .with_context(|| "Failed at creating activating the listener")?;
1806
1807        let mut server = Server::new(
1808            event_loop,
1809            channel,
1810            server_scm_socket,
1811            sessions,
1812            pool,
1813            backends,
1814            Some(proxy),
1815            None,
1816            None,
1817            server_config,
1818            None,
1819            false,
1820        )
1821        .with_context(|| "Failed at creating server")?;
1822
1823        debug!("{} starting event loop", log_module_context!());
1824        server.run();
1825        debug!("{} ending event loop", log_module_context!());
1826        Ok(())
1827    }
1828}
1829
1830#[cfg(test)]
1831mod tests {
1832    extern crate tiny_http;
1833
1834    use std::{
1835        io::{Read, Write},
1836        net::TcpStream,
1837        str,
1838        sync::{Arc, Barrier},
1839        thread,
1840        time::Duration,
1841    };
1842
1843    use sozu_command::proto::command::SocketAddress;
1844
1845    use super::{testing::start_http_worker, *};
1846    use crate::sozu_command::{
1847        channel::Channel,
1848        config::ListenerBuilder,
1849        proto::command::{
1850            LoadBalancingParams, PathRule, RulePosition, SoftStop, WorkerRequest,
1851            request::RequestType,
1852        },
1853        response::{Backend, HttpFrontend},
1854    };
1855
1856    /*
1857    #[test]
1858    #[cfg(target_pointer_width = "64")]
1859    fn size_test() {
1860      assert_size!(ExpectProxyProtocol<mio::net::TcpStream>, 520);
1861      assert_size!(Http<mio::net::TcpStream>, 1232);
1862      assert_size!(Pipe<mio::net::TcpStream>, 272);
1863      assert_size!(State, 1240);
1864      // fails depending on the platform?
1865      assert_size!(Session, 1592);
1866    }
1867    */
1868
1869    #[test]
1870    fn round_trip() {
1871        setup_test_logger!();
1872        let front_port = crate::testing::provide_port();
1873        let backend_server = Arc::new(
1874            tiny_http::Server::http("127.0.0.1:0").expect("could not create tiny_http server"),
1875        );
1876        let backend_port = backend_server
1877            .server_addr()
1878            .to_ip()
1879            .expect("tiny_http server should bind to IP address")
1880            .port();
1881
1882        let barrier = Arc::new(Barrier::new(2));
1883
1884        let config = ListenerBuilder::new_http(SocketAddress::new_v4(127, 0, 0, 1, front_port))
1885            .to_http(None)
1886            .expect("could not create listener config");
1887
1888        let (mut command, channel) =
1889            Channel::generate(1000, 10000).expect("should create a channel");
1890
1891        thread::scope(|s| {
1892            let backend_handle = backend_server.clone();
1893            let barrier_clone = barrier.to_owned();
1894            s.spawn(move || {
1895                setup_test_logger!();
1896                start_server(&backend_handle, barrier_clone);
1897            });
1898            barrier.wait();
1899
1900            s.spawn(move || {
1901                setup_test_logger!();
1902                start_http_worker(config, channel, 10, 16384)
1903                    .expect("could not start the http server");
1904            });
1905
1906            let front = RequestHttpFrontend {
1907                cluster_id: Some("cluster_1".to_owned()),
1908                address: SocketAddress::new_v4(127, 0, 0, 1, front_port),
1909                hostname: "localhost".to_owned(),
1910                path: PathRule::prefix("/".to_owned()),
1911                ..Default::default()
1912            };
1913            command
1914                .write_message(&WorkerRequest {
1915                    id: "ID_ABCD".to_owned(),
1916                    content: RequestType::AddHttpFrontend(front).into(),
1917                })
1918                .expect("could not send AddHttpFrontend");
1919            let backend = Backend {
1920                cluster_id: "cluster_1".to_owned(),
1921                backend_id: "cluster_1-0".to_owned(),
1922                address: SocketAddress::new_v4(127, 0, 0, 1, backend_port).into(),
1923                load_balancing_parameters: Some(LoadBalancingParams::default()),
1924                sticky_id: None,
1925                backup: None,
1926            };
1927            command
1928                .write_message(&WorkerRequest {
1929                    id: "ID_EFGH".to_owned(),
1930                    content: RequestType::AddBackend(backend.to_add_backend()).into(),
1931                })
1932                .expect("could not send AddBackend");
1933
1934            println!("test received: {:?}", command.read_message());
1935            println!("test received: {:?}", command.read_message());
1936
1937            let mut client =
1938                TcpStream::connect(("127.0.0.1", front_port)).expect("could not connect to sozu");
1939
1940            client
1941                .set_read_timeout(Some(Duration::new(1, 0)))
1942                .expect("could not set read timeout");
1943            let request = format!(
1944                "GET / HTTP/1.1\r\nHost: localhost:{front_port}\r\nConnection: Close\r\n\r\n"
1945            );
1946            let w = client.write(request.as_bytes());
1947            println!("http client write: {w:?}");
1948
1949            barrier.wait();
1950            let mut buffer = [0; 4096];
1951            let mut index = 0;
1952
1953            // tiny_http responds with exactly 191 bytes for a "hello world" body
1954            // (headers + body). This is deterministic for a given tiny_http version.
1955            let expected_len = 191;
1956
1957            loop {
1958                assert!(index <= expected_len);
1959                if index == expected_len {
1960                    break;
1961                }
1962
1963                let r = client.read(&mut buffer[index..]);
1964                println!("http client read: {r:?}");
1965                match r {
1966                    Err(e) => panic!("client request should not fail. Error: {e:?}"),
1967                    Ok(sz) => {
1968                        index += sz;
1969                    }
1970                }
1971            }
1972            println!(
1973                "Response: {}",
1974                str::from_utf8(&buffer[..index]).expect("could not make string from buffer")
1975            );
1976
1977            // Gracefully stop the sozu worker so the scoped thread can join
1978            command
1979                .write_message(&WorkerRequest {
1980                    id: "ID_STOP".to_owned(),
1981                    content: RequestType::SoftStop(SoftStop {}).into(),
1982                })
1983                .expect("could not send SoftStop");
1984            // Unblock the backend server so its thread can exit
1985            backend_server.unblock();
1986        });
1987    }
1988
1989    #[test]
1990    fn keep_alive() {
1991        setup_test_logger!();
1992        let front_port = crate::testing::provide_port();
1993        let backend_server = Arc::new(
1994            tiny_http::Server::http("127.0.0.1:0").expect("could not create tiny_http server"),
1995        );
1996        let backend_port = backend_server
1997            .server_addr()
1998            .to_ip()
1999            .expect("tiny_http server should bind to IP address")
2000            .port();
2001
2002        let barrier = Arc::new(Barrier::new(2));
2003
2004        let config = ListenerBuilder::new_http(SocketAddress::new_v4(127, 0, 0, 1, front_port))
2005            .to_http(None)
2006            .expect("could not create listener config");
2007
2008        let (mut command, channel) =
2009            Channel::generate(1000, 10000).expect("should create a channel");
2010
2011        thread::scope(|s| {
2012            let backend_handle = backend_server.clone();
2013            let barrier_clone = barrier.to_owned();
2014            s.spawn(move || {
2015                setup_test_logger!();
2016                start_server(&backend_handle, barrier_clone);
2017            });
2018            barrier.wait();
2019
2020            s.spawn(move || {
2021                setup_test_logger!();
2022                start_http_worker(config, channel, 10, 16384)
2023                    .expect("could not start the http server");
2024            });
2025
2026            let front = RequestHttpFrontend {
2027                address: SocketAddress::new_v4(127, 0, 0, 1, front_port),
2028                hostname: "localhost".to_owned(),
2029                path: PathRule::prefix("/".to_owned()),
2030                cluster_id: Some("cluster_1".to_owned()),
2031                ..Default::default()
2032            };
2033            command
2034                .write_message(&WorkerRequest {
2035                    id: "ID_ABCD".to_owned(),
2036                    content: RequestType::AddHttpFrontend(front).into(),
2037                })
2038                .expect("could not send AddHttpFrontend");
2039            let backend = Backend {
2040                address: SocketAddress::new_v4(127, 0, 0, 1, backend_port).into(),
2041                backend_id: "cluster_1-0".to_owned(),
2042                backup: None,
2043                cluster_id: "cluster_1".to_owned(),
2044                load_balancing_parameters: Some(LoadBalancingParams::default()),
2045                sticky_id: None,
2046            };
2047            command
2048                .write_message(&WorkerRequest {
2049                    id: "ID_EFGH".to_owned(),
2050                    content: RequestType::AddBackend(backend.to_add_backend()).into(),
2051                })
2052                .expect("could not send AddBackend");
2053
2054            println!("test received: {:?}", command.read_message());
2055            println!("test received: {:?}", command.read_message());
2056
2057            let mut client =
2058                TcpStream::connect(("127.0.0.1", front_port)).expect("could not connect to sozu");
2059            client
2060                .set_read_timeout(Some(Duration::new(5, 0)))
2061                .expect("could not set read timeout");
2062
2063            // tiny_http responds with exactly 191 bytes for a "hello world" body
2064            // (headers + body). This is deterministic for a given tiny_http version.
2065            let expected_len = 191;
2066
2067            let request = format!("GET / HTTP/1.1\r\nHost: localhost:{front_port}\r\n\r\n");
2068            let w = client
2069                .write(request.as_bytes())
2070                .expect("could not write first request");
2071            println!("http client write: {w:?}");
2072            barrier.wait();
2073
2074            let mut buffer = [0; 4096];
2075            let mut index = 0;
2076
2077            loop {
2078                assert!(index <= expected_len);
2079                if index == expected_len {
2080                    break;
2081                }
2082
2083                let r = client.read(&mut buffer[index..]);
2084                println!("http client read: {r:?}");
2085                match r {
2086                    Err(e) => panic!("client request should not fail. Error: {e:?}"),
2087                    Ok(sz) => {
2088                        index += sz;
2089                    }
2090                }
2091            }
2092
2093            println!(
2094                "Response: {}",
2095                str::from_utf8(&buffer[..index]).expect("could not make string from buffer")
2096            );
2097
2098            println!("first request ended, will send second one");
2099            let request2 = format!("GET / HTTP/1.1\r\nHost: localhost:{front_port}\r\n\r\n");
2100            let w2 = client.write(request2.as_bytes());
2101            println!("http client write: {w2:?}");
2102            barrier.wait();
2103
2104            let mut buffer2 = [0; 4096];
2105            let mut index = 0;
2106
2107            loop {
2108                assert!(index <= expected_len);
2109                if index == expected_len {
2110                    break;
2111                }
2112
2113                let r2 = client.read(&mut buffer2[index..]);
2114                println!("http client read: {r2:?}");
2115                match r2 {
2116                    Err(e) => panic!("client request should not fail. Error: {e:?}"),
2117                    Ok(sz) => {
2118                        index += sz;
2119                    }
2120                }
2121            }
2122            println!(
2123                "Response: {}",
2124                str::from_utf8(&buffer2[..index]).expect("could not make string from buffer")
2125            );
2126
2127            // Gracefully stop the sozu worker so the scoped thread can join
2128            command
2129                .write_message(&WorkerRequest {
2130                    id: "ID_STOP".to_owned(),
2131                    content: RequestType::SoftStop(SoftStop {}).into(),
2132                })
2133                .expect("could not send SoftStop");
2134            // Unblock the backend server so its thread can exit
2135            backend_server.unblock();
2136        });
2137    }
2138
2139    use self::tiny_http::Response;
2140
2141    fn start_server(server: &tiny_http::Server, barrier: Arc<Barrier>) {
2142        let addr = server.server_addr();
2143        info!("starting web server on {:?}", addr);
2144        barrier.wait();
2145
2146        for request in server.incoming_requests() {
2147            info!(
2148                "backend web server got request -> method: {:?}, url: {:?}, headers: {:?}",
2149                request.method(),
2150                request.url(),
2151                request.headers()
2152            );
2153
2154            let response = Response::from_string("hello world");
2155            request
2156                .respond(response)
2157                .expect("could not respond to request");
2158            info!("backend web server sent response");
2159            barrier.wait();
2160            info!("server session stopped");
2161        }
2162
2163        println!("server on {addr:?} closed");
2164    }
2165
2166    #[test]
2167    fn frontend_from_request_test() {
2168        let cluster_id1 = "cluster_1".to_owned();
2169        let cluster_id2 = "cluster_2".to_owned();
2170        let cluster_id3 = "cluster_3".to_owned();
2171        let uri1 = "/".to_owned();
2172        let uri2 = "/yolo".to_owned();
2173        let uri3 = "/yolo/swag".to_owned();
2174
2175        let mut fronts = Router::new();
2176        fronts
2177            .add_http_front(&HttpFrontend {
2178                address: "0.0.0.0:80".parse().unwrap(),
2179                hostname: "lolcatho.st".to_owned(),
2180                method: None,
2181                path: PathRule::prefix(uri1),
2182                position: RulePosition::Tree,
2183                cluster_id: Some(cluster_id1),
2184                tags: None,
2185                redirect: None,
2186                redirect_scheme: None,
2187                redirect_template: None,
2188                rewrite_host: None,
2189                rewrite_path: None,
2190                rewrite_port: None,
2191                required_auth: None,
2192                headers: Vec::new(),
2193                hsts: None,
2194            })
2195            .expect("Could not add http frontend");
2196        fronts
2197            .add_http_front(&HttpFrontend {
2198                address: "0.0.0.0:80".parse().unwrap(),
2199                hostname: "lolcatho.st".to_owned(),
2200                method: None,
2201                path: PathRule::prefix(uri2),
2202                position: RulePosition::Tree,
2203                cluster_id: Some(cluster_id2),
2204                tags: None,
2205                redirect: None,
2206                redirect_scheme: None,
2207                redirect_template: None,
2208                rewrite_host: None,
2209                rewrite_path: None,
2210                rewrite_port: None,
2211                required_auth: None,
2212                headers: Vec::new(),
2213                hsts: None,
2214            })
2215            .expect("Could not add http frontend");
2216        fronts
2217            .add_http_front(&HttpFrontend {
2218                address: "0.0.0.0:80".parse().unwrap(),
2219                hostname: "lolcatho.st".to_owned(),
2220                method: None,
2221                path: PathRule::prefix(uri3),
2222                position: RulePosition::Tree,
2223                cluster_id: Some(cluster_id3),
2224                tags: None,
2225                redirect: None,
2226                redirect_scheme: None,
2227                redirect_template: None,
2228                rewrite_host: None,
2229                rewrite_path: None,
2230                rewrite_port: None,
2231                required_auth: None,
2232                headers: Vec::new(),
2233                hsts: None,
2234            })
2235            .expect("Could not add http frontend");
2236        fronts
2237            .add_http_front(&HttpFrontend {
2238                address: "0.0.0.0:80".parse().unwrap(),
2239                hostname: "other.domain".to_owned(),
2240                method: None,
2241                path: PathRule::prefix("/test".to_owned()),
2242                position: RulePosition::Tree,
2243                cluster_id: Some("cluster_1".to_owned()),
2244                tags: None,
2245                redirect: None,
2246                redirect_scheme: None,
2247                redirect_template: None,
2248                rewrite_host: None,
2249                rewrite_path: None,
2250                rewrite_port: None,
2251                required_auth: None,
2252                headers: Vec::new(),
2253                hsts: None,
2254            })
2255            .expect("Could not add http frontend");
2256
2257        let address = SocketAddress::new_v4(127, 0, 0, 1, 1030);
2258
2259        let default_config = ListenerBuilder::new_http(address)
2260            .to_http(None)
2261            .expect("Could not create default HTTP listener config");
2262
2263        let listener = HttpListener {
2264            listener: None,
2265            address: address.into(),
2266            fronts,
2267            answers: Rc::new(RefCell::new(HttpAnswers::new(&BTreeMap::new()).unwrap())),
2268            config: default_config,
2269            token: Token(0),
2270            active: true,
2271            tags: BTreeMap::new(),
2272        };
2273
2274        let frontend1 = listener.frontend_from_request("lolcatho.st", "/", &Method::Get);
2275        let frontend2 = listener.frontend_from_request("lolcatho.st", "/test", &Method::Get);
2276        let frontend3 = listener.frontend_from_request("lolcatho.st", "/yolo/test", &Method::Get);
2277        let frontend4 = listener.frontend_from_request("lolcatho.st", "/yolo/swag", &Method::Get);
2278        let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get);
2279        assert_eq!(
2280            frontend1
2281                .expect("should find frontend")
2282                .cluster_id
2283                .as_deref(),
2284            Some("cluster_1")
2285        );
2286        assert_eq!(
2287            frontend2
2288                .expect("should find frontend")
2289                .cluster_id
2290                .as_deref(),
2291            Some("cluster_1")
2292        );
2293        assert_eq!(
2294            frontend3
2295                .expect("should find frontend")
2296                .cluster_id
2297                .as_deref(),
2298            Some("cluster_2")
2299        );
2300        assert_eq!(
2301            frontend4
2302                .expect("should find frontend")
2303                .cluster_id
2304                .as_deref(),
2305            Some("cluster_3")
2306        );
2307        assert!(frontend5.is_err());
2308    }
2309
2310    #[test]
2311    fn h2_stream_idle_timeout_inherits_back_timeout() {
2312        let address = SocketAddress::new_v4(127, 0, 0, 1, 1040);
2313        let build = |back_timeout: u32, explicit: Option<u32>| -> HttpListener {
2314            let mut cfg = ListenerBuilder::new_http(address)
2315                .to_http(None)
2316                .expect("default HTTP listener config");
2317            cfg.back_timeout = back_timeout;
2318            cfg.h2_stream_idle_timeout_seconds = explicit;
2319            HttpListener::new(cfg, Token(0)).expect("build listener")
2320        };
2321
2322        // Knob unset: inherit back_timeout when it exceeds the 30s floor.
2323        assert_eq!(
2324            build(180, None).get_h2_stream_idle_timeout(),
2325            Duration::from_secs(180)
2326        );
2327
2328        // Knob unset, back_timeout below floor: stay at 30s to preserve the
2329        // slow-multiplex Slowloris mitigation.
2330        assert_eq!(
2331            build(5, None).get_h2_stream_idle_timeout(),
2332            Duration::from_secs(30)
2333        );
2334
2335        // Explicit values win in both directions — including below the floor,
2336        // so operators under attack can tighten the deadline.
2337        assert_eq!(
2338            build(180, Some(10)).get_h2_stream_idle_timeout(),
2339            Duration::from_secs(10)
2340        );
2341        assert_eq!(
2342            build(5, Some(600)).get_h2_stream_idle_timeout(),
2343            Duration::from_secs(600)
2344        );
2345
2346        // `Some(0)` is clamped to 1s to keep the deadline non-degenerate.
2347        assert_eq!(
2348            build(180, Some(0)).get_h2_stream_idle_timeout(),
2349            Duration::from_secs(1)
2350        );
2351    }
2352}