Skip to main content

goose_http/server/
mod.rs

1//! Tokio-powered listener orchestration for Goose HTTP.
2//!
3//! The [`Server`] owns the accept loop and spawns per-connection tasks that
4//! drive the [`conn`](crate::conn) state machine. Concrete routing and
5//! application logic hooks plug into [`ServerBuilder`] during configuration.
6
7use std::{
8    sync::{
9        Arc,
10        atomic::{AtomicU64, Ordering},
11    },
12    time::Duration,
13};
14
15use thiserror::Error;
16use tokio::{net::TcpListener, task, time};
17
18use crate::{
19    conn::{Connection, ConnectionConfig},
20    log,
21    routing::{DefaultRouter, Handler},
22};
23
24/// Top-level HTTP server handle.
25pub struct Server {
26    addr: String,
27    handler: Arc<dyn Handler>,
28    next_id: AtomicU64,
29    config: ConnectionConfig,
30}
31
32impl Server {
33    /// Create a builder for configuring a server instance.
34    pub fn builder() -> ServerBuilder {
35        ServerBuilder::default()
36    }
37
38    /// Returns the configured bind address.
39    pub fn addr(&self) -> &str {
40        &self.addr
41    }
42
43    /// Start accepting connections and spawn per-connection tasks.
44    pub async fn run(&self) -> Result<(), ServerError> {
45        log::init();
46        let listener = TcpListener::bind(&self.addr)
47            .await
48            .map_err(|error| ServerError::Bind {
49                addr: self.addr.clone(),
50                source: error,
51            })?;
52
53        loop {
54            let (stream, _peer) = match listener.accept().await {
55                Ok(pair) => pair,
56                Err(error) => {
57                    log::warn(&format!("accept failed: {error}"));
58                    // Back off briefly on accept failures to avoid tight loop.
59                    time::sleep(Duration::from_millis(100)).await;
60                    continue;
61                }
62            };
63
64            if let Err(error) = stream.set_nodelay(true) {
65                log::warn(&format!("failed to set TCP_NODELAY: {error}"));
66            }
67
68            let handler = Arc::clone(&self.handler);
69            let connection_id = self.next_id.fetch_add(1, Ordering::Relaxed);
70            let config = self.config.clone();
71
72            task::spawn(async move {
73                let connection = Connection::new(connection_id, stream, handler, config);
74                if let Err(error) = connection.run().await {
75                    log::warn(&format!(
76                        "connection {connection_id} closed with error: {error}"
77                    ));
78                }
79            });
80        }
81    }
82}
83
84/// Builder for constructing a [`Server`] with custom options.
85pub struct ServerBuilder {
86    addr: String,
87    handler: Arc<dyn Handler>,
88    config: ConnectionConfig,
89}
90
91impl Default for ServerBuilder {
92    fn default() -> Self {
93        Self {
94            addr: String::from("127.0.0.1:3000"),
95            handler: Arc::new(DefaultRouter),
96            config: ConnectionConfig::default(),
97        }
98    }
99}
100
101impl ServerBuilder {
102    /// Override the bind address used by the server.
103    pub fn with_addr(mut self, addr: impl Into<String>) -> Self {
104        self.addr = addr.into();
105        self
106    }
107
108    /// Provide a custom request handler implementation.
109    pub fn with_handler<H>(mut self, handler: H) -> Self
110    where
111        H: Handler,
112    {
113        self.handler = Arc::new(handler);
114        self
115    }
116
117    /// Override the timeout used when reading request headers.
118    pub fn with_header_read_timeout(mut self, timeout: Duration) -> Self {
119        self.config.header_read_timeout = timeout;
120        self
121    }
122
123    /// Override the timeout applied when draining request bodies.
124    pub fn with_body_read_timeout(mut self, timeout: Duration) -> Self {
125        self.config.body_read_timeout = timeout;
126        self
127    }
128
129    /// Override the idle timeout between pipelined requests.
130    pub fn with_idle_timeout(mut self, timeout: Duration) -> Self {
131        self.config.idle_timeout = timeout;
132        self
133    }
134
135    /// Provide a fully-specified connection configuration.
136    pub fn with_connection_config(mut self, config: ConnectionConfig) -> Self {
137        self.config = config;
138        self
139    }
140
141    /// Finalise the builder into a [`Server`].
142    pub fn build(self) -> Server {
143        Server {
144            addr: self.addr,
145            handler: self.handler,
146            next_id: AtomicU64::new(1),
147            config: self.config,
148        }
149    }
150}
151
152/// Errors that can occur while running the server accept loop.
153#[derive(Debug, Error)]
154pub enum ServerError {
155    #[error("failed to bind {addr}: {source}")]
156    Bind {
157        addr: String,
158        source: std::io::Error,
159    },
160}