Skip to main content

spars_httpd/
lib.rs

1// SPDX-FileCopyrightText: 2025 Cullen Walsh <ckwalsh@cullenwalsh.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::any::Any;
5use std::net::TcpListener;
6use std::sync::Arc;
7use std::sync::atomic::AtomicBool;
8use std::sync::atomic::Ordering;
9
10use async_executor::LocalExecutor;
11use async_io::Async;
12use thiserror::Error;
13
14mod conn;
15mod handler;
16mod response;
17mod settings;
18
19pub use handler::Handler;
20pub use handler::HandlerBuildError;
21pub use response::Response;
22pub use response::StatusCode;
23pub use settings::*;
24
25#[cfg(feature = "signal-hook")]
26mod signals;
27
28#[derive(Debug, Error)]
29pub enum SparsError {
30    #[error("Failed to create async listener: {0}")]
31    FailedToCreateAsyncListener(std::io::Error),
32
33    #[error("Failed to create signal handler: {0}")]
34    FailedToStartSignalHandler(std::io::Error),
35
36    #[error("Failed to join signal handler")]
37    FailedToJoinSignalHandler(Box<dyn Any + Send>),
38
39    #[error("Signal handler failed: {0}")]
40    SignalHandlerFailed(std::io::Error),
41}
42
43pub fn serve<L: TryInto<Async<TcpListener>, Error = std::io::Error>, H: Into<Arc<Handler>>>(
44    listener: L,
45    handler: H,
46) -> Result<(), SparsError> {
47    let listener = listener
48        .try_into()
49        .map_err(SparsError::FailedToCreateAsyncListener)?;
50    let stop_flag = Arc::new(AtomicBool::new(false));
51
52    #[cfg(feature = "signal-hook")]
53    let signal_handler = signals::spawn_signal_handler(Arc::clone(&stop_flag), &listener)
54        .map_err(SparsError::FailedToStartSignalHandler)?;
55
56    serve_with_stop_flag(listener, handler, stop_flag)?;
57
58    #[cfg(feature = "signal-hook")]
59    signal_handler
60        .join()
61        .map_err(SparsError::FailedToJoinSignalHandler)?
62        .map_err(SparsError::SignalHandlerFailed)?;
63
64    Ok(())
65}
66
67pub fn serve_with_stop_flag<H: Into<Arc<Handler>>>(
68    listener: Async<TcpListener>,
69    handler: H,
70    stop_flag: Arc<AtomicBool>,
71) -> Result<(), SparsError> {
72    let handler = handler.into();
73
74    let ex = LocalExecutor::new();
75
76    async_io::block_on(ex.run(async {
77        while !stop_flag.load(Ordering::SeqCst) {
78            match listener.accept().await {
79                Ok((stream, _addr)) => {
80                    ex.spawn({
81                        let handler = Arc::clone(&handler);
82                        let stop_flag = Arc::clone(&stop_flag);
83
84                        conn::handle_conn(handler, stop_flag, stream)
85                    })
86                    .detach();
87                }
88                Err(_e) => {
89                    // eprintln!("Error while establishing connection: {}", _e);
90                }
91            }
92        }
93    }));
94
95    Ok(())
96}