servlin/lib.rs
1//! Servlin
2//! ========
3//! [](https://crates.io/crates/servlin)
4//! [](http://www.apache.org/licenses/LICENSE-2.0)
5//! [](https://github.com/rust-secure-code/safety-dance/)
6//! [](https://github.com/mleonhard/servlin/actions)
7//!
8//! A modular HTTP server library in Rust.
9//!
10//! # Features
11//! - `forbid(unsafe_code)`
12//! - Threaded request handlers:<br>
13//! `FnOnce(Request) -> Response + 'static + Clone + Send + Sync`
14//! - Uses async code internally for excellent performance under load
15//! - JSON
16//! - Server-Sent Events (SSE)
17//! - Saves large request bodies to temp files
18//! - Sends 100-Continue
19//! - Limits number of threads and connections
20//! - Modular: roll your own logging, write custom versions of internal methods, etc.
21//! - No macros or complicated type params
22//! - Good test coverage (63%)
23//!
24//! # Limitations
25//! - New, not proven in production.
26//! - To do:
27//! - Request timeouts
28//! - `chunked` transfer-encoding for request bodies
29//! - gzip
30//! - brotli
31//! - TLS
32//! - automatically getting TLS certs via ACME
33//! - Drop idle connections when approaching connection limit.
34//! - Denial-of-Service mitigation: source throttling, minimum throughput
35//! - Complete functional test suite
36//! - Missing load tests
37//! - Disk space usage limits
38//!
39//! # Examples
40//! Complete examples: [`examples/`](https://github.com/mleonhard/servlin/tree/main/examples).
41//!
42//! Simple example:
43//! ```rust
44//! use serde::Deserialize;
45//! use serde_json::json;
46//! use servlin::{
47//! socket_addr_127_0_0_1,
48//! Error,
49//! HttpServerBuilder,
50//! Request,
51//! Response
52//! };
53//! use servlin::log::log_request_and_response;
54//! use std::sync::Arc;
55//! use temp_dir::TempDir;
56//!
57//! struct State {}
58//!
59//! fn hello(_state: Arc<State>, req: Request) -> Result<Response, Error> {
60//! #[derive(Deserialize)]
61//! struct Input {
62//! name: String,
63//! }
64//! let input: Input = req.json()?;
65//! Ok(Response::json(200, json!({"message": format!("Hello, {}!", input.name)}))?)
66//! }
67//!
68//! fn handle_req(state: Arc<State>, req: Request) -> Result<Response, Error> {
69//! match (req.method(), req.url().path.as_str()) {
70//! ("GET", "/ping") => Ok(Response::text(200, "ok")),
71//! ("POST", "/hello") => hello(state, req),
72//! _ => Ok(Response::text(404, "Not found")),
73//! }
74//! }
75//!
76//! let state = Arc::new(State {});
77//! let request_handler = move |req: Request| {
78//! log_request_and_response(req, |req| handle_req(state, req)).unwrap()
79//! };
80//! let cache_dir = TempDir::new().unwrap();
81//! safina::timer::start_timer_thread();
82//! let executor = safina::executor::Executor::new(1, 9).unwrap();
83//! # let permit = permit::Permit::new();
84//! # let server_permit = permit.new_sub();
85//! # std::thread::spawn(move || {
86//! # std::thread::sleep(std::time::Duration::from_millis(100));
87//! # drop(permit);
88//! # });
89//! executor.block_on(
90//! HttpServerBuilder::new()
91//! # .permit(server_permit)
92//! .listen_addr(socket_addr_127_0_0_1(8271))
93//! .max_conns(1000)
94//! .small_body_len(64 * 1024)
95//! .receive_large_bodies(cache_dir.path())
96//! .spawn_and_join(request_handler)
97//! ).unwrap();
98//! ```
99//! # Cargo Geiger Safety Report
100//! # Alternatives
101//! See [rust-webserver-comparison.md](https://github.com/mleonhard/servlin/blob/main/rust-webserver-comparison.md).
102//!
103//! # Changelog
104//! - v0.8.0 2025-07-06 - Use own [Url] struct.
105//! - v0.7.0 2025-06-30
106//! - Require Rust 2024 edition.
107//! - `log_request_and_response` to log `duration_ms` tag.
108//! - Fix typo in function name `Response::internal_server_errror_500`.
109//! - Close connection on 5xx error.
110//! - Acceptor thread to log errors, not panic.
111//! - Add [`Request::parse_url`].
112//! - Add [`Response::too_many_requests_429`].
113//! - Implement `Into<TagList>` for arrays.
114//! - Support asterisk request target.
115//! - Use `safina` v0.7.
116//! - v0.6.0 2024-11-02
117//! - Remove `servlin::reexports` module.
118//! - Use `safina` v0.6.0.
119//! - v0.5.1 2024-10-26 - Remove dependency on `once_cell`.
120//! - v0.5.0 2024-10-21 - Remove `LogFileWriterBuilder`.
121//! - v0.4.3 - Implement `From<Cow<'_, str>>` and `From<&Path>` for `TagValue`.
122//! - v0.4.2 - Implement `Seek` for `BodyReader`.
123//! - v0.4.1
124//! - Add `Request::opt_json`.
125//! - Implement `From<LoggerStoppedError>` for `Error`.
126//! - v0.4.0
127//! - Changed `Response::json` to return `Result<Response, Error>`.
128//! - Changed `log_request_and_response` to return `Result`.
129//! - Added `Response::unprocessable_entity_422`.
130//! - v0.3.2 - Fix bug in `Response::include_dir` redirects.
131//! - v0.3.1
132//! - Add `Response::redirect_301`
133//! - `Response::include_dir` to redirect from `/somedir` to `/somedir/` so relative URLs will work.
134//! - v0.3.0 - Changed `Response::include_dir` to take `&Request` and look for `index.html` in dirs.
135//! - v0.2.0
136//! - Added:
137//! - `log_request_and_response` and other logging tooling
138//! - `Response::ok_200()`
139//! - `Response::unauthorized_401()`
140//! - `Response::forbidden_403()`
141//! - `Response::internal_server_errror_500()`
142//! - `Response::not_implemented_501()`
143//! - `Response::service_unavailable_503()`
144//! - `EventSender::is_connected()`
145//! - `PORT_env()`
146//! - Removed `print_log_response` and `RequestBody::length_is_known`
147//! - Changed `RequestBody::len` and `is_empty` to return `Option`.
148//! - Bugfixes
149//! - v0.1.1 - Add `EventSender::unconnected`.
150//! - v0.1.0 - Rename library to Servlin.
151//!
152//! # TO DO
153//! - Fix limitations above
154//! - Support [HEAD](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD)
155//! responses that have Content-Length set and no body.
156//! - Add a server-wide limit on upload body size.
157//! - Limit disk usage for caching uploads.
158//! - Update `rust-webserver-comparison.md`
159//! - Add missing data
160//! - Add other servers from <https://www.arewewebyet.org/topics/frameworks/>
161//! - Rearrange
162//! - Generate geiger reports for each web server
163#![forbid(unsafe_code)]
164mod accept;
165mod ascii_string;
166mod body_async_reader;
167mod body_reader;
168mod content_type;
169mod cookie;
170mod error;
171mod event;
172mod head;
173mod headers;
174mod http_conn;
175mod http_error;
176pub mod log;
177mod rand;
178mod request;
179mod request_body;
180mod response;
181mod response_body;
182mod time;
183mod token_set;
184mod url;
185mod util;
186
187pub use crate::accept::{
188 PORT_env, socket_addr_127_0_0_1, socket_addr_127_0_0_1_any_port, socket_addr_all_interfaces,
189};
190pub use crate::ascii_string::AsciiString;
191pub use crate::body_async_reader::BodyAsyncReader;
192pub use crate::body_reader::BodyReader;
193pub use crate::content_type::ContentType;
194pub use crate::cookie::{Cookie, SameSite};
195pub use crate::error::Error;
196pub use crate::event::{Event, EventSender};
197pub use crate::headers::{Header, HeaderList};
198pub use crate::http_conn::HttpConn;
199pub use crate::request::Request;
200pub use crate::request_body::RequestBody;
201pub use crate::response::Response;
202pub use crate::response_body::ResponseBody;
203pub use crate::url::{PercentEncodePurpose, Url, UrlParseError, percent_decode, percent_encode};
204
205/// This part of the library is not covered by the semver guarantees.
206/// If you use these in your program, a minor version upgrade could break your build.
207///
208/// If you use these items in a published library,
209/// your library should depend on a specific version of this library.
210pub mod internal {
211 pub use crate::accept::*;
212 pub use crate::body_async_reader::*;
213 pub use crate::body_reader::*;
214 pub use crate::content_type::*;
215 pub use crate::cookie::*;
216 pub use crate::event::*;
217 pub use crate::head::*;
218 pub use crate::headers::*;
219 pub use crate::http_conn::*;
220 pub use crate::http_error::*;
221 pub use crate::request::*;
222 pub use crate::request_body::*;
223 pub use crate::response::*;
224 pub use crate::response_body::*;
225 pub use crate::time::*;
226 pub use crate::token_set::*;
227 pub use crate::util::*;
228}
229
230use crate::accept::accept_loop;
231use crate::http_conn::handle_http_conn;
232use crate::token_set::TokenSet;
233use async_net::TcpListener;
234use permit::Permit;
235use std::net::SocketAddr;
236use std::path::PathBuf;
237
238/// Builds an HTTP server.
239pub struct HttpServerBuilder {
240 opt_cache_dir: Option<PathBuf>,
241 listen_addr: SocketAddr,
242 max_conns: usize,
243 small_body_len: usize,
244 permit: Permit,
245}
246impl HttpServerBuilder {
247 /// Makes a new builder these default settings:
248 /// - Listens on 127.0.0.1
249 /// - Picks a random port
250 /// - 100 max connections
251 /// - 64 KiB small body length
252 /// - no cache dir, server rejects large request bodies
253 #[allow(clippy::new_without_default)]
254 #[must_use]
255 pub fn new() -> Self {
256 Self {
257 opt_cache_dir: None,
258 listen_addr: socket_addr_127_0_0_1_any_port(),
259 max_conns: 100,
260 small_body_len: 64 * 1024,
261 permit: Permit::new(),
262 }
263 }
264
265 #[must_use]
266 pub fn listen_addr(mut self, addr: SocketAddr) -> Self {
267 self.listen_addr = addr;
268 self
269 }
270
271 /// Sets the maximum number of connections to handle at one time.
272 ///
273 /// When the server is handling the maximum number of connections,
274 /// it waits for a connection to drop before accepting new ones.
275 ///
276 /// Each connection uses a file handle.
277 /// Some processes run with a limit on the number of file handles.
278 /// The OS kernel also has a limit for all processes combined.
279 ///
280 /// # Panics
281 /// Panics when `n` is zero.
282 #[must_use]
283 pub fn max_conns(mut self, n: usize) -> Self {
284 assert!(n > 0, "refusing to set max_conns to zero");
285 self.max_conns = n;
286 self
287 }
288
289 /// Save large request bodies to this directory.
290 ///
291 /// If you do not call this method, the server will refuse all
292 /// requests with bodies larger than `small_body_len` with `413 Payload Too Large`.
293 /// It will also refuse all bodies with unknown length.
294 ///
295 /// # Example
296 /// ```
297 /// use servlin::{HttpServerBuilder, Request, Response};
298 /// use std::io::Read;
299 ///
300 /// let cache_dir = temp_dir::TempDir::new().unwrap();
301 /// let handler = move |req: Request| {
302 /// if req.body.is_pending() {
303 /// return Response::get_body_and_reprocess(1024 * 1024);
304 /// }
305 /// let len = req.body.reader().unwrap().bytes().count();
306 /// Response::text(200, format!("body len={}", len))
307 /// };
308 /// # let permit = permit::Permit::new();
309 /// # let server_permit = permit.new_sub();
310 /// # std::thread::spawn(move || {
311 /// # std::thread::sleep(std::time::Duration::from_millis(100));
312 /// # drop(permit);
313 /// # });
314 /// safina::timer::start_timer_thread();
315 /// safina::executor::Executor::new(1, 1)
316 /// .unwrap()
317 /// .block_on(
318 /// HttpServerBuilder::new()
319 /// # .permit(server_permit)
320 /// .receive_large_bodies(cache_dir.path())
321 /// .spawn_and_join(handler)
322 /// )
323 /// .unwrap();
324 /// ```
325 #[must_use]
326 pub fn receive_large_bodies(mut self, cache_dir: &std::path::Path) -> Self {
327 self.opt_cache_dir = Some(cache_dir.to_path_buf());
328 self
329 }
330
331 /// Automatically receive request bodies up to length `n`,
332 /// saving them in memory.
333 ///
334 /// The default value is 64 KiB.
335 ///
336 /// Reject larger requests with `413 Payload Too Large`.
337 /// See [`receive_large_bodies`](ServerBuilder::receive_large_bodies).
338 ///
339 /// You can estimate the server memory usage with:
340 /// `small_body_len * max_conns`.
341 /// Using the default settings: 64 KiB * 100 connections => 6.4 MiB.
342 #[must_use]
343 pub fn small_body_len(mut self, n: usize) -> Self {
344 self.small_body_len = n;
345 self
346 }
347
348 /// Sets the permit used by the server.
349 ///
350 /// Revoke the permit to make the server gracefully shut down.
351 ///
352 /// # Example
353 /// ```
354 /// use std::net::SocketAddr;
355 /// use std::sync::Arc;
356 /// use permit::Permit;
357 /// use safina::executor::Executor;
358 /// use servlin::{Response, HttpServerBuilder};
359 /// # fn do_some_requests(addr: SocketAddr) -> Result<(),()> { Ok(()) }
360 ///
361 /// safina::timer::start_timer_thread();
362 /// let executor: Arc<Executor> = Arc::default();
363 /// let permit = Permit::new();
364 /// let (addr, stopped_receiver) = executor.block_on(
365 /// HttpServerBuilder::new()
366 /// .permit(permit.new_sub())
367 /// .spawn(move |req| Response::text(200, "yo"))
368 /// ).unwrap();
369 /// do_some_requests(addr).unwrap();
370 /// drop(permit); // Tell server to shut down.
371 /// stopped_receiver.recv().unwrap(); // Wait for server to stop.
372 /// ```
373 #[must_use]
374 pub fn permit(mut self, p: Permit) -> Self {
375 self.permit = p;
376 self
377 }
378
379 /// Spawns the server task.
380 ///
381 /// Returns `(addr, stopped_receiver)`.
382 /// The server is listening on `addr`.
383 /// After the server gracefully shuts down, it sends a message on `stopped_receiver`.
384 ///
385 /// # Errors
386 /// Returns an error when it fails to bind to the [`listen_addr`](ServerBuilder::listen_addr).
387 pub async fn spawn<F>(
388 self,
389 request_handler: F,
390 ) -> Result<(SocketAddr, safina::sync::Receiver<()>), std::io::Error>
391 where
392 F: FnOnce(Request) -> Response + 'static + Clone + Send + Sync,
393 {
394 let async_request_handler = |req: Request| async move {
395 let request_handler_clone = request_handler.clone();
396 // TODO: Handle threadpool backpressure.
397 // - Keep set of pending requests, worker threads pull from it.
398 // - Async task checks and removes disconnected connections
399 // - send 503s
400 // - priorities
401 // - rate limits
402 // - timeout request handlers with permit
403 safina::executor::schedule_blocking(move || request_handler_clone(req))
404 .await
405 .unwrap_or_else(|_| Response::text(500, "Server error"))
406 };
407 let conn_handler = move |permit, token, stream: async_net::TcpStream, addr| {
408 let http_conn = HttpConn::new(addr, stream);
409 safina::executor::spawn(handle_http_conn(
410 permit,
411 token,
412 http_conn,
413 self.opt_cache_dir,
414 self.small_body_len,
415 async_request_handler,
416 ));
417 };
418 let listener = TcpListener::bind(self.listen_addr).await?;
419 let addr = listener.local_addr()?;
420 let token_set = TokenSet::new(self.max_conns);
421 let (sender, receiver) = safina::sync::oneshot();
422 safina::executor::spawn(async move {
423 // Let's not make spawn accept_loop tasks, since that reduces throughput.
424 // To speed this up, we could use a separate accepter thread, or multiple threads.
425 accept_loop(self.permit, listener, token_set, conn_handler).await;
426 // TODO: Wait for connection tokens to return.
427 let _ignored = sender.send(());
428 });
429 Ok((addr, receiver))
430 }
431
432 /// Spawns the server task and waits for it to shutdown gracefully.
433 ///
434 /// # Errors
435 /// Returns an error when it fails to bind to the [`listen_addr`](ServerBuilder::listen_addr).
436 pub async fn spawn_and_join<F>(self, request_handler: F) -> Result<(), std::io::Error>
437 where
438 F: FnOnce(Request) -> Response + 'static + Clone + Send + Sync,
439 {
440 let (_addr, mut stopped_receiver) = self.spawn(request_handler).await?;
441 let _ignored = stopped_receiver.async_recv().await;
442 Ok(())
443 }
444}