Skip to main content

rustlavel_http/
server.rs

1//! The HTTP/1.1 server: accept loop, request parsing, keep-alive, and a
2//! graceful shutdown that lets in-flight requests finish.
3
4use crate::error_page;
5use crate::headers::Headers;
6use crate::method::Method;
7use crate::panic;
8use crate::request::Request;
9use crate::response::Response;
10use crate::router::Router;
11use crate::status::Status;
12use crate::url;
13use rustlavel_core::{Context, Error, Result};
14use std::net::SocketAddr;
15use std::sync::Arc;
16use std::sync::atomic::{AtomicUsize, Ordering};
17use std::time::{Duration, Instant};
18use tokio::io::{AsyncReadExt, AsyncWriteExt, BufWriter};
19use tokio::net::{TcpListener, TcpStream};
20
21/// Guard rails applied to every connection.
22#[derive(Debug, Clone)]
23pub struct Limits {
24    pub max_header_bytes: usize,
25    pub max_body_bytes: usize,
26    /// How long an idle keep-alive connection is held open.
27    pub keep_alive_timeout: Duration,
28    /// How long the headers of a single request may take to arrive.
29    pub header_timeout: Duration,
30}
31
32impl Default for Limits {
33    fn default() -> Self {
34        Limits {
35            max_header_bytes: 64 * 1024,
36            max_body_bytes: 10 * 1024 * 1024,
37            keep_alive_timeout: Duration::from_secs(15),
38            header_timeout: Duration::from_secs(10),
39        }
40    }
41}
42
43/// How many ports to try before giving up.
44///
45/// Ten outside production, because a port held by a process that has not
46/// finished dying is a daily nuisance there and nothing depends on the number.
47/// **One inside it**, because in production something *does* depend on the
48/// number: a load balancer, a health check, a firewall rule. A server that
49/// moved itself to 8301 while the traffic still went to 8300 would look like an
50/// outage with no cause in the logs. Refusing to start says what happened.
51///
52/// `server.port_attempts` overrides both, in either direction.
53fn port_attempts(config: &rustlavel_core::Config) -> u16 {
54    let default = if config.is_production() { 1 } else { 10 };
55    config.int("server.port_attempts", default).clamp(1, 1000) as u16
56}
57
58/// Refuse a message whose end has more than one answer.
59///
60/// These checks are the difference between a proxy and this server agreeing
61/// about where one request stops and the next begins. When they disagree, the
62/// bytes left in the connection buffer are read as the start of the next
63/// request — so an attacker writes a prefix onto a pooled connection and
64/// captures or rewrites whatever victim's request arrives after it. The session
65/// and CSRF work above this is no defence: the request that reaches the router
66/// was never the one the visitor sent.
67///
68/// RFC 7230 §3.3.3 says refuse, and refusing is cheap.
69fn check_framing(headers: &Headers) -> Result<()> {
70        //
71    // These checks are the difference between a proxy and this server
72    // agreeing about where one request stops and the next begins. When they
73    // disagree, the bytes left in the connection buffer are read as the
74    // start of the next request — so an attacker writes a prefix onto a
75    // pooled connection and captures or rewrites whatever victim's request
76    // arrives after it. The session and CSRF work above this is no defence:
77    // the request that reaches the router was never the one the visitor
78    // sent.
79    //
80    // RFC 7230 §3.3.3 says refuse, and refusing is cheap.
81    let lengths = headers.get_all("content-length");
82    if lengths.len() > 1 && lengths.iter().any(|value| value != &lengths[0]) {
83        return Err(Error::Protocol(
84            "more than one Content-Length, and they disagree".into(),
85        ));
86    }
87    if !lengths.is_empty() && headers.get("transfer-encoding").is_some() {
88        return Err(Error::Protocol(
89            "both Transfer-Encoding and Content-Length: a message may say where it ends \
90             once, not twice"
91                .into(),
92        ));
93    }
94    // Only `chunked`, and only as the last encoding, tells us where the
95    // body ends. Anything else — a second `Transfer-Encoding` line, an
96    // encoding this server does not implement — leaves the length unknown,
97    // and guessing is what a smuggled request relies on.
98    let encodings = headers.get_all("transfer-encoding");
99    if !encodings.is_empty() {
100        let listed: Vec<&str> = encodings
101            .iter()
102            .flat_map(|value| value.split(','))
103            .map(str::trim)
104            .filter(|value| !value.is_empty())
105            .collect();
106        if listed.last() != Some(&"chunked")
107            || listed.iter().filter(|value| **value == "chunked").count() != 1
108        {
109            return Err(Error::Protocol(format!(
110                "unsupported Transfer-Encoding: {}",
111                listed.join(", ")
112            )));
113        }
114    }
115    Ok(())
116}
117
118/// Bind `addr`, walking up the port number while the one asked for is taken.
119///
120/// A port left occupied by a process that has not finished dying is the most
121/// common way a run fails, and the operating system's answer to it — `Os {
122/// code: 48, kind: AddrInUse }` — tells somebody nothing they can act on. So
123/// the next few ports are tried, and the one actually used is said out loud.
124///
125/// **Saying it out loud is the whole safety argument.** A server that quietly
126/// moves is worse than one that refuses to start: the load balancer still
127/// points at the old port, the health check fails, and nothing in the logs
128/// explains why. That is why production defaults to a single attempt — see
129/// [`Server::listen`] — and why moving is a warning rather than a debug line.
130///
131/// Only `AddrInUse` moves on. A refused bind for any other reason — a
132/// privileged port, an address that is not on this machine — is that reason,
133/// and walking away from it would only make it harder to see.
134async fn bind_walking(addr: &str, attempts: u16) -> Result<TcpListener> {
135    // Port zero means "any free port"; the operating system is already doing
136    // this job, and incrementing from zero would undo it.
137    let Some((host, first)) = addr
138        .rsplit_once(':')
139        .and_then(|(host, port)| port.parse::<u16>().ok().map(|port| (host, port)))
140        .filter(|(_, port)| *port != 0)
141    else {
142        return TcpListener::bind(addr).await.map_err(Error::Io);
143    };
144
145    let mut tried = first;
146    for offset in 0..attempts {
147        let Some(port) = first.checked_add(offset) else { break };
148        tried = port;
149        match TcpListener::bind(format!("{host}:{port}")).await {
150            Ok(listener) => {
151                if offset > 0 {
152                    rustlavel_core::warn!(
153                        "port {first} is in use, so this is serving on {port} instead"
154                    );
155                }
156                return Ok(listener);
157            }
158            Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => continue,
159            Err(e) => return Err(Error::Io(e)),
160        }
161    }
162
163    Err(Error::msg(if first == tried {
164        format!(
165            "port {first} is already in use. Something else is listening on it — `lsof -i :{first}` \
166             says what — so stop that, or set SERVER_PORT to a free port."
167        )
168    } else {
169        format!(
170            "every port from {first} to {tried} is already in use. Stop whatever is holding them \
171             — `lsof -i :{first}` names the first — or set SERVER_PORT to a free one."
172        )
173    }))
174}
175
176/// Resolves when the process has been asked to stop.
177///
178/// **Both signals, not just Ctrl-C.** This waited on `ctrl_c` alone, which is
179/// `SIGINT` — the one a person sends from a terminal. Every orchestrator sends
180/// `SIGTERM`: Kubernetes before it kills a pod, systemd on `stop`, Docker on
181/// `docker stop`. Under all three the default handler terminated the process
182/// immediately, so the drain below never ran and no shutdown work ever
183/// happened — a graceful shutdown that was only graceful when somebody was
184/// watching. Found by killing a service and seeing it stay registered.
185#[cfg(unix)]
186async fn stop_requested() {
187    use tokio::signal::unix::{SignalKind, signal};
188
189    let mut terminate = match signal(SignalKind::terminate()) {
190        Ok(stream) => stream,
191        // A process that cannot install the handler still stops on Ctrl-C
192        // rather than refusing to start.
193        Err(error) => {
194            rustlavel_core::warn!("cannot listen for SIGTERM: {error}");
195            let _ = tokio::signal::ctrl_c().await;
196            return;
197        }
198    };
199
200    tokio::select! {
201        _ = tokio::signal::ctrl_c() => {}
202        _ = terminate.recv() => {}
203    }
204}
205
206#[cfg(not(unix))]
207async fn stop_requested() {
208    let _ = tokio::signal::ctrl_c().await;
209}
210
211/// Something to run once the listener has stopped and in-flight requests have
212/// drained.
213// `Sync` as well as `Send`: the server is shared through an `Arc` while it is
214// accepting, and `Arc<T>` is only `Send` when `T` is `Sync`. A field that is
215// merely `Send` makes the whole server unshareable.
216pub type OnShutdown = Box<dyn FnOnce() -> crate::handler::BoxFuture<()> + Send + Sync>;
217
218pub struct Server {
219    router: Arc<Router>,
220    context: Context,
221    limits: Limits,
222    /// Run after the drain, in the order they were added.
223    ///
224    /// **This exists because advice nothing follows is not a feature.** A
225    /// service registered with a discovery server is meant to say goodbye on
226    /// the way down — the difference between a clean deploy and thirty seconds
227    /// of requests routed to a process that has exited — and there was nowhere
228    /// to put that call, so the documentation said to make it and nothing did.
229    on_shutdown: Vec<OnShutdown>,
230}
231
232impl Server {
233    pub fn new(mut router: Router, context: Context) -> Self {
234        router.finalize();
235        let limits = Limits {
236            max_body_bytes: context.config().int("server.max_body_bytes", 10 * 1024 * 1024) as usize,
237            ..Limits::default()
238        };
239        Server { router: Arc::new(router), context, limits, on_shutdown: Vec::new() }
240    }
241
242    /// Run something once the listener has stopped and requests have drained.
243    ///
244    /// After the drain rather than before it: a service that deregistered
245    /// first would still be serving the requests already in flight, and a
246    /// service that deregistered and then took ten seconds to finish them is
247    /// exactly the clean shutdown this is for.
248    pub fn on_shutdown(mut self, work: OnShutdown) -> Self {
249        self.on_shutdown.push(work);
250        self
251    }
252
253    pub fn limits(mut self, limits: Limits) -> Self {
254        self.limits = limits;
255        self
256    }
257
258    /// Bind and serve until Ctrl-C, then drain in-flight requests.
259    ///
260    /// When the port is taken, the next ones are tried — see [`bind_walking`].
261    /// How many is `server.port_attempts`, which defaults to ten outside
262    /// production and to one inside it.
263    pub async fn listen(mut self, addr: impl Into<String>) -> Result<()> {
264        let addr = addr.into();
265        let listener = bind_walking(&addr, port_attempts(self.context.config())).await?;
266        let local = listener.local_addr().map_err(Error::Io)?;
267
268        panic::install_hook();
269        error_page::set_debug(self.context.config().debug());
270
271        rustlavel_core::info!("Rustlavel serving on http://{local}");
272        rustlavel_core::info!("Press Ctrl-C to stop");
273
274        let in_flight = Arc::new(AtomicUsize::new(0));
275        // Taken out before the server is shared, because it runs after the
276        // loop has ended and the `Arc` is no longer the place to reach it.
277        let mut on_shutdown = std::mem::take(&mut self.on_shutdown);
278        let shared = Arc::new(self);
279
280        loop {
281            let accepted = tokio::select! {
282                result = listener.accept() => result,
283                _ = stop_requested() => break,
284            };
285
286            let (stream, peer) = match accepted {
287                Ok(pair) => pair,
288                // A single failed accept (fd exhaustion, a dropped SYN) should
289                // not bring down the listener.
290                Err(e) => {
291                    rustlavel_core::warn!("accept failed: {e}");
292                    continue;
293                }
294            };
295
296            let server = Arc::clone(&shared);
297            let counter = Arc::clone(&in_flight);
298            counter.fetch_add(1, Ordering::SeqCst);
299            tokio::spawn(async move {
300                if let Err(e) = server.serve_connection(stream, peer).await {
301                    rustlavel_core::debug!("connection closed: {e}");
302                }
303                counter.fetch_sub(1, Ordering::SeqCst);
304            });
305        }
306
307        rustlavel_core::info!("Shutting down, waiting for in-flight requests…");
308        let deadline = Instant::now() + Duration::from_secs(10);
309        while in_flight.load(Ordering::SeqCst) > 0 && Instant::now() < deadline {
310            tokio::time::sleep(Duration::from_millis(25)).await;
311        }
312        // After the drain: a service that deregistered first would still be
313        // serving the requests already in flight.
314        for work in on_shutdown.drain(..) {
315            work().await;
316        }
317
318        rustlavel_core::info!("Goodbye.");
319        Ok(())
320    }
321
322    pub(crate) async fn serve_connection(&self, stream: TcpStream, peer: SocketAddr) -> Result<()> {
323        // Small responses should leave for the client immediately.
324        let _ = stream.set_nodelay(true);
325        let (mut reader, writer) = stream.into_split();
326        let mut writer = BufWriter::new(writer);
327        let mut buffer: Vec<u8> = Vec::with_capacity(2048);
328
329        loop {
330            let head = match self.read_head(&mut reader, &mut buffer).await? {
331                Some(head) => head,
332                // Client hung up between requests: a clean end, not an error.
333                None => return Ok(()),
334            };
335
336            let (mut request, keep_alive) = match self.parse(&head, &mut reader, &mut buffer, peer).await {
337                Ok(parsed) => parsed,
338                Err(error) => {
339                    let response = Response::new(Status::BAD_REQUEST).with_text(error.to_string());
340                    writer.write_all(&response.to_bytes(true)).await.map_err(Error::Io)?;
341                    writer.flush().await.map_err(Error::Io)?;
342                    return Ok(());
343                }
344            };
345
346            request.context = self.context.clone();
347            let is_head = request.method() == Method::Head;
348            let mut response = self.dispatch(request).await;
349
350            // A handler that answered 101 wants the socket. Write the
351            // handshake, then stop speaking HTTP on this connection.
352            if response.upgrades() {
353                // The head is rendered *before* the upgrade is taken out of
354                // the response: `to_bytes` omits the content length for a
355                // response that hands the socket over, and it can only see
356                // that while the upgrade is still there. Taking it first
357                // wrote `content-length: 0` on every event stream, and the
358                // browser closed the stream before the first event.
359                let head = response.to_bytes(false);
360                let upgrade = response.take_upgrade().expect("checked just above");
361                writer.write_all(&head).await.map_err(Error::Io)?;
362                writer.flush().await.map_err(Error::Io)?;
363
364                let upgraded = crate::upgrade::Upgraded {
365                    reader: Box::new(reader),
366                    writer: Box::new(writer),
367                    // Anything already read past the request belongs to the new
368                    // protocol; dropping it would lose its first frame.
369                    buffered: std::mem::take(&mut buffer),
370                };
371                upgrade.run(upgraded).await;
372                return Ok(());
373            }
374
375            if !keep_alive {
376                response.headers.set("connection", "close");
377            }
378            writer.write_all(&response.to_bytes(!is_head)).await.map_err(Error::Io)?;
379            writer.flush().await.map_err(Error::Io)?;
380
381            if !keep_alive {
382                return Ok(());
383            }
384        }
385    }
386
387    /// Run the router, converting a panic into the error page instead of
388    /// letting it kill the connection task.
389    async fn dispatch(&self, request: Request) -> Response {
390        let started = Instant::now();
391        let method = request.method();
392        let path = request.path().to_string();
393
394        // Panics are caught, and the request event is dispatched, inside the
395        // router — so both behave identically under the test client.
396        let response = self.router.dispatch(request).await;
397        let elapsed = started.elapsed();
398
399        if rustlavel_core::log::enabled(rustlavel_core::log::Level::Debug) {
400            rustlavel_core::debug!(
401                "{method} {path} → {} ({:.1}ms)",
402                response.status.code(),
403                elapsed.as_secs_f64() * 1000.0
404            );
405        }
406
407        response
408    }
409
410    /// Read until the end of the header block, returning the raw head.
411    async fn read_head(
412        &self,
413        reader: &mut tokio::net::tcp::OwnedReadHalf,
414        buffer: &mut Vec<u8>,
415    ) -> Result<Option<Vec<u8>>> {
416        // A connection waiting for its first byte gets the longer keep-alive
417        // budget; once bytes arrive the head must complete promptly.
418        let mut timeout = self.limits.keep_alive_timeout;
419
420        loop {
421            if let Some(end) = find_head_end(buffer) {
422                let head = buffer[..end].to_vec();
423                buffer.drain(..end);
424                return Ok(Some(head));
425            }
426            if buffer.len() > self.limits.max_header_bytes {
427                return Err(Error::Protocol("request headers are too large".into()));
428            }
429
430            let mut chunk = [0u8; 4096];
431            let read = match tokio::time::timeout(timeout, reader.read(&mut chunk)).await {
432                Ok(Ok(0)) if buffer.is_empty() => return Ok(None),
433                Ok(Ok(0)) => return Err(Error::Protocol("connection closed mid-request".into())),
434                Ok(Ok(n)) => n,
435                Ok(Err(e)) => return Err(Error::Io(e)),
436                Err(_) if buffer.is_empty() => return Ok(None),
437                Err(_) => return Err(Error::Protocol("timed out reading request headers".into())),
438            };
439            buffer.extend_from_slice(&chunk[..read]);
440            timeout = self.limits.header_timeout;
441        }
442    }
443
444    async fn parse(
445        &self,
446        head: &[u8],
447        reader: &mut tokio::net::tcp::OwnedReadHalf,
448        buffer: &mut Vec<u8>,
449        peer: SocketAddr,
450    ) -> Result<(Request, bool)> {
451        let text = std::str::from_utf8(head).map_err(|_| Error::Protocol("headers are not UTF-8".into()))?;
452        let mut lines = text.split("\r\n");
453
454        let request_line = lines.next().ok_or_else(|| Error::Protocol("empty request".into()))?;
455        let mut parts = request_line.split(' ');
456        let method = parts
457            .next()
458            .and_then(Method::parse)
459            .ok_or_else(|| Error::Protocol("unsupported method".into()))?;
460        let target = parts.next().ok_or_else(|| Error::Protocol("missing request target".into()))?;
461        let version = parts.next().unwrap_or("HTTP/1.1");
462
463        let mut headers = Headers::new();
464        for line in lines {
465            if line.is_empty() {
466                continue;
467            }
468            let (name, value) = line
469                .split_once(':')
470                .ok_or_else(|| Error::Protocol(format!("malformed header line: {line}")))?;
471
472            // `Content-Length : 5` is not a header with the name
473            // `Content-Length`. RFC 7230 §3.2.4 requires it be rejected, and
474            // the reason is this file's problem specifically: a front-end that
475            // trims where we trim, or does not, disagrees with us about the
476            // body's length — which is the whole of request smuggling.
477            if name.ends_with(' ') || name.ends_with('\t') {
478                return Err(Error::Protocol(
479                    "a header name may not be followed by whitespace before the colon".into(),
480                ));
481            }
482            headers.append(name.trim(), value.trim());
483        }
484
485        check_framing(&headers)?;
486
487        // An absolute-form target (`GET http://host/path`) is legal for proxies.
488        let target = match target.find("://") {
489            Some(scheme_end) => match target[scheme_end + 3..].find('/') {
490                Some(path_start) => &target[scheme_end + 3 + path_start..],
491                None => "/",
492            },
493            None => target,
494        };
495
496        let body = self.read_body(&headers, reader, buffer).await?;
497
498        let keep_alive = match headers.get("connection") {
499            Some(value) if value.eq_ignore_ascii_case("close") => false,
500            Some(value) if value.eq_ignore_ascii_case("keep-alive") => true,
501            _ => version != "HTTP/1.0",
502        };
503
504        let (path, query) = url::split_target(target);
505        let mut request = Request::new(method, target);
506        request.path = url::decode(path);
507        request.query = url::parse_query(query);
508        request.headers = headers;
509        request.peer = Some(peer);
510        Ok((request.with_body(body), keep_alive))
511    }
512
513    async fn read_body(
514        &self,
515        headers: &Headers,
516        reader: &mut tokio::net::tcp::OwnedReadHalf,
517        buffer: &mut Vec<u8>,
518    ) -> Result<Vec<u8>> {
519        // Exact, not `contains`: `xchunked` is not chunked, and the parse
520        // above has already refused anything whose last encoding is not
521        // `chunked`.
522        if headers.get("transfer-encoding").is_some() {
523            return self.read_chunked_body(reader, buffer).await;
524        }
525
526        let Some(length) = headers.content_length() else {
527            return Ok(Vec::new());
528        };
529        if length > self.limits.max_body_bytes {
530            return Err(Error::Protocol("request body is too large".into()));
531        }
532
533        while buffer.len() < length {
534            let mut chunk = vec![0u8; (length - buffer.len()).min(64 * 1024)];
535            let read = tokio::time::timeout(self.limits.header_timeout, reader.read(&mut chunk))
536                .await
537                .map_err(|_| Error::Protocol("timed out reading request body".into()))?
538                .map_err(Error::Io)?;
539            if read == 0 {
540                return Err(Error::Protocol("request body ended early".into()));
541            }
542            buffer.extend_from_slice(&chunk[..read]);
543        }
544
545        Ok(buffer.drain(..length).collect())
546    }
547
548    async fn read_chunked_body(
549        &self,
550        reader: &mut tokio::net::tcp::OwnedReadHalf,
551        buffer: &mut Vec<u8>,
552    ) -> Result<Vec<u8>> {
553        let mut body = Vec::new();
554
555        loop {
556            // Each chunk starts with its size in hex on its own line.
557            let line_end = loop {
558                if let Some(at) = find_crlf(buffer) {
559                    break at;
560                }
561                if !fill(reader, buffer, self.limits.header_timeout).await? {
562                    return Err(Error::Protocol("chunked body ended early".into()));
563                }
564            };
565
566            let header: Vec<u8> = buffer.drain(..line_end + 2).collect();
567            let size_text = String::from_utf8_lossy(&header[..line_end]);
568            let size = usize::from_str_radix(size_text.split(';').next().unwrap_or("").trim(), 16)
569                .map_err(|_| Error::Protocol("invalid chunk size".into()))?;
570
571            if size == 0 {
572                // The final chunk may be followed by trailer lines; both end at
573                // a blank line.
574                loop {
575                    let end = loop {
576                        if let Some(at) = find_crlf(buffer) {
577                            break at;
578                        }
579                        if !fill(reader, buffer, self.limits.header_timeout).await? {
580                            return Ok(body);
581                        }
582                    };
583                    buffer.drain(..end + 2);
584                    if end == 0 {
585                        return Ok(body);
586                    }
587                }
588            }
589
590            if body.len() + size > self.limits.max_body_bytes {
591                return Err(Error::Protocol("request body is too large".into()));
592            }
593
594            while buffer.len() < size + 2 {
595                if !fill(reader, buffer, self.limits.header_timeout).await? {
596                    return Err(Error::Protocol("chunked body ended early".into()));
597                }
598            }
599            body.extend(buffer.drain(..size));
600            buffer.drain(..2);
601        }
602    }
603}
604
605async fn fill(
606    reader: &mut tokio::net::tcp::OwnedReadHalf,
607    buffer: &mut Vec<u8>,
608    timeout: Duration,
609) -> Result<bool> {
610    let mut chunk = [0u8; 4096];
611    let read = tokio::time::timeout(timeout, reader.read(&mut chunk))
612        .await
613        .map_err(|_| Error::Protocol("timed out reading request body".into()))?
614        .map_err(Error::Io)?;
615    buffer.extend_from_slice(&chunk[..read]);
616    Ok(read > 0)
617}
618
619/// Byte offset just past the blank line that ends the header block.
620fn find_head_end(buffer: &[u8]) -> Option<usize> {
621    buffer.windows(4).position(|w| w == b"\r\n\r\n").map(|at| at + 4)
622}
623
624fn find_crlf(buffer: &[u8]) -> Option<usize> {
625    buffer.windows(2).position(|w| w == b"\r\n")
626}
627
628#[cfg(test)]
629mod tests {
630
631
632    /// Parse just the headers of a raw message and run the framing checks over
633    /// them, which is what `parse` does before it reads a body.
634    fn framing_of(raw: &str) -> Result<()> {
635        let mut headers = Headers::new();
636        for line in raw.split("\r\n").skip(1) {
637            if line.is_empty() {
638                break;
639            }
640            let (name, value) = line.split_once(':').expect("a header line");
641            if name.ends_with(' ') || name.ends_with('\t') {
642                return Err(Error::Protocol("whitespace before the colon".into()));
643            }
644            headers.append(name.trim(), value.trim());
645        }
646        check_framing(&headers)
647    }
648
649    /// Request smuggling, in the four shapes this server used to accept.
650    ///
651    /// Each one is a message whose end has two answers. A front-end that picks
652    /// the other answer leaves bytes in this connection's buffer, and the
653    /// keep-alive loop reads them as the next request — so an attacker prefixes
654    /// a request onto a pooled connection and captures or rewrites whatever
655    /// arrives next. None of the session or CSRF work above this helps: the
656    /// request the router sees was never the one the visitor sent.
657    #[test]
658    fn a_message_that_says_where_it_ends_twice_is_refused() {
659        let ambiguous = [
660            (
661                "two lengths that disagree",
662                "POST / HTTP/1.1\r\nhost: x\r\ncontent-length: 6\r\ncontent-length: 0\r\n\r\nsmuggl",
663            ),
664            (
665                "a length and a chunked encoding",
666                "POST / HTTP/1.1\r\nhost: x\r\ncontent-length: 6\r\ntransfer-encoding: chunked\r\n\r\n0\r\n\r\n",
667            ),
668            (
669                "an encoding that is not chunked last",
670                "POST / HTTP/1.1\r\nhost: x\r\ntransfer-encoding: chunked, identity\r\n\r\n0\r\n\r\n",
671            ),
672            (
673                "whitespace before the colon",
674                "POST / HTTP/1.1\r\nhost: x\r\ncontent-length : 6\r\n\r\nsmuggl",
675            ),
676        ];
677
678        for (what, raw) in ambiguous {
679            assert!(
680                framing_of(raw).is_err(),
681                "{what}: accepted a message with two answers for where its body ends"
682            );
683        }
684    }
685
686    /// And the ordinary shapes still parse, or the fix would be a denial of
687    /// service dressed as a security patch.
688    #[test]
689    fn an_unambiguous_message_still_parses() {
690        for raw in [
691            "GET / HTTP/1.1\r\nhost: x\r\n\r\n",
692            "POST / HTTP/1.1\r\nhost: x\r\ncontent-length: 3\r\n\r\nabc",
693            "POST / HTTP/1.1\r\nhost: x\r\ntransfer-encoding: chunked\r\n\r\n0\r\n\r\n",
694            // Repeated but agreeing is legal, and some proxies do it.
695            "POST / HTTP/1.1\r\nhost: x\r\ncontent-length: 3\r\ncontent-length: 3\r\n\r\nabc",
696        ] {
697            assert!(framing_of(raw).is_ok(), "refused an ordinary message: {raw:?}");
698        }
699    }
700
701    /// Production must not wander. This is the check that the default is not
702    /// quietly the same everywhere — the dangerous outcome is silent.
703    #[test]
704    fn production_gets_one_attempt_and_development_gets_more() {
705        use rustlavel_core::Config;
706
707        let production = Config::with_defaults();
708        production.set("app.env", "production");
709        assert_eq!(port_attempts(&production), 1);
710
711        let local = Config::with_defaults();
712        local.set("app.env", "local");
713        assert!(port_attempts(&local) > 1);
714    }
715
716    /// And somebody who wants the other behaviour can say so.
717    #[test]
718    fn the_setting_overrides_the_environment_both_ways() {
719        use rustlavel_core::Config;
720
721        let production = Config::with_defaults();
722        production.set("app.env", "production");
723        production.set("server.port_attempts", "5");
724        assert_eq!(port_attempts(&production), 5);
725
726        let local = Config::with_defaults();
727        local.set("app.env", "local");
728        local.set("server.port_attempts", "1");
729        assert_eq!(port_attempts(&local), 1);
730    }
731
732    /// The reason this exists: a port left occupied should cost a warning, not
733    /// a failed run.
734    #[tokio::test]
735    async fn a_taken_port_moves_to_the_next_one() {
736        let held = TcpListener::bind("127.0.0.1:0").await.unwrap();
737        let taken = held.local_addr().unwrap().port();
738
739        let listener = bind_walking(&format!("127.0.0.1:{taken}"), 10).await.unwrap();
740        assert_ne!(listener.local_addr().unwrap().port(), taken);
741        assert!(listener.local_addr().unwrap().port() > taken);
742    }
743
744    /// One attempt is what production asks for, and it must actually mean one:
745    /// a server that moves without being allowed to is the failure this whole
746    /// feature has to avoid.
747    #[tokio::test]
748    async fn a_single_attempt_does_not_move() {
749        let held = TcpListener::bind("127.0.0.1:0").await.unwrap();
750        let taken = held.local_addr().unwrap().port();
751
752        let error = bind_walking(&format!("127.0.0.1:{taken}"), 1).await.unwrap_err();
753        let message = error.to_string();
754        assert!(message.contains(&taken.to_string()), "{message}");
755        assert!(message.contains("lsof"), "the message has to say what to do: {message}");
756    }
757
758    /// Port zero already means "any free port". Walking up from it would turn
759    /// a working request into a scan of the low ports.
760    #[tokio::test]
761    async fn port_zero_is_left_to_the_operating_system() {
762        let listener = bind_walking("127.0.0.1:0", 10).await.unwrap();
763        assert_ne!(listener.local_addr().unwrap().port(), 0);
764    }
765
766    /// Running out of ports has to name the range actually tried, or the
767    /// message sends somebody looking at the wrong port.
768    #[tokio::test]
769    async fn exhausting_the_range_says_what_it_tried() {
770        // Hold three consecutive ports, then allow exactly those three.
771        let first = TcpListener::bind("127.0.0.1:0").await.unwrap();
772        let start = first.local_addr().unwrap().port();
773        let mut held = vec![first];
774        for offset in 1..3u16 {
775            // Another test may hold it; the assertion below still holds.
776            if let Ok(listener) = TcpListener::bind(format!("127.0.0.1:{}", start + offset)).await {
777                held.push(listener);
778            }
779        }
780
781        let error = bind_walking(&format!("127.0.0.1:{start}"), 3).await;
782        if let Err(error) = error {
783            let message = error.to_string();
784            assert!(message.contains(&start.to_string()), "{message}");
785            assert!(message.contains(&(start + 2).to_string()), "{message}");
786        }
787    }
788    use super::*;
789
790    #[test]
791    fn finds_the_end_of_a_header_block() {
792        assert_eq!(find_head_end(b"GET / HTTP/1.1\r\n\r\nbody"), Some(18));
793        assert_eq!(find_head_end(b"GET / HTTP/1.1\r\n"), None);
794    }
795
796    #[tokio::test]
797    async fn parses_a_request_with_a_body() {
798        let server = Server::new(Router::new(), Context::default());
799        let head = b"POST /users?page=2 HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nContent-Length: 14\r\n\r\n";
800
801        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
802        let addr = listener.local_addr().unwrap();
803        tokio::spawn(async move {
804            let (mut stream, _) = listener.accept().await.unwrap();
805            stream.write_all(br#"{"name":"ada"}"#).await.unwrap();
806        });
807        let stream = TcpStream::connect(addr).await.unwrap();
808        let (mut reader, _writer) = stream.into_split();
809
810        let mut buffer = Vec::new();
811        let (mut request, keep_alive) =
812            server.parse(head, &mut reader, &mut buffer, addr).await.unwrap();
813
814        assert_eq!(request.method(), Method::Post);
815        assert_eq!(request.path(), "/users");
816        assert_eq!(request.query("page"), Some("2"));
817        assert_eq!(request.header("host"), Some("localhost"));
818        assert_eq!(request.input("name").as_deref(), Some("ada"));
819        assert!(keep_alive);
820    }
821
822    #[tokio::test]
823    async fn http_1_0_closes_by_default() {
824        let server = Server::new(Router::new(), Context::default());
825        let head = b"GET / HTTP/1.0\r\nHost: localhost\r\n\r\n";
826
827        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
828        let addr = listener.local_addr().unwrap();
829        tokio::spawn(async move {
830            let _ = listener.accept().await;
831        });
832        let (mut reader, _w) = TcpStream::connect(addr).await.unwrap().into_split();
833
834        let mut buffer = Vec::new();
835        let (_request, keep_alive) =
836            server.parse(head, &mut reader, &mut buffer, addr).await.unwrap();
837
838        assert!(!keep_alive);
839    }
840
841    #[tokio::test]
842    async fn rejects_a_body_larger_than_the_limit() {
843        let mut server = Server::new(Router::new(), Context::default());
844        server.limits.max_body_bytes = 8;
845        let head = b"POST / HTTP/1.1\r\nContent-Length: 9999\r\n\r\n";
846
847        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
848        let addr = listener.local_addr().unwrap();
849        tokio::spawn(async move {
850            let _ = listener.accept().await;
851        });
852        let (mut reader, _w) = TcpStream::connect(addr).await.unwrap().into_split();
853
854        let mut buffer = Vec::new();
855        let error = server.parse(head, &mut reader, &mut buffer, addr).await.unwrap_err();
856
857        assert!(error.to_string().contains("too large"));
858    }
859}