pub struct Server { /* private fields */ }Expand description
A static file server for serving files securely from a root directory.
Server canonicalizes the root directory once at creation time and uses the
canonical form for all subsequent requests, avoiding repeated filesystem calls.
§Security
The server protects against:
- Path traversal attacks (e.g.,
../../etc/passwd) - Accessing files outside the root via symlinks
- Disclosing filesystem structure (traversal and missing files both return 404)
§Cloning
Server is cheap to clone (a PathBuf and a usize). Multiple clones can be used
concurrently in async tasks without synchronization overhead.
§Example
use mini_static::Server;
use std::path::Path;
use std::time::Duration;
let server = Server::new(Path::new("./public"))?;
let (port, _handle) = server.run(Duration::from_secs(30)).await?;
println!("Server running on port {}", port);Implementations§
Source§impl Server
impl Server
Sourcepub fn new(root: &Path) -> Result<Self, StaticError>
pub fn new(root: &Path) -> Result<Self, StaticError>
Create a new server with the given root directory.
Canonicalizes the root once at startup. All subsequent requests use the canonical root without re-canonicalizing it, making this suitable for long-lived servers.
§Arguments
root- The root directory to serve files from.
§Errors
Returns Err(StaticError::Io) if the root cannot be canonicalized (e.g., doesn’t exist,
no read permissions).
Sourcepub fn with_max_connections(self, max: usize) -> Self
pub fn with_max_connections(self, max: usize) -> Self
Set the maximum number of connections served concurrently (default 1024).
Once this many connections are in flight, run()’s accept loop stops accepting
new ones — without pausing the accept loop, a client that opens a connection and
sends nothing (see the header-read timeout docs on run()) could otherwise be
used, in enough parallel copies, to exhaust the process’s file descriptors or
memory with no bound at all.
Sourcepub fn resolve(&self, request_path: &str) -> Result<PathBuf, StaticError>
pub fn resolve(&self, request_path: &str) -> Result<PathBuf, StaticError>
Resolve a request path under the server’s root.
This is a lower-level API for resolving paths without generating HTTP responses.
For most use cases, prefer handle_request_with_method() or the run() methods.
§Arguments
request_path- The HTTP request path (e.g.,/path/to/file.html).
§Returns
Ok(PathBuf)if the path resolves to a file within root.Err(StaticError)if the path is invalid, missing, or attempts traversal.
Sourcepub fn handle_request(&self, request_path: &str) -> Response<ResponseBody>
pub fn handle_request(&self, request_path: &str) -> Response<ResponseBody>
Handle an HTTP GET request for a resource path.
Convenience method equivalent to handle_request_with_method(&Method::GET, request_path).
§Arguments
request_path- The HTTP request path (e.g.,/index.html).
Sourcepub fn handle_request_with_method(
&self,
method: &Method,
request_path: &str,
) -> Response<ResponseBody>
pub fn handle_request_with_method( &self, method: &Method, request_path: &str, ) -> Response<ResponseBody>
Handle an HTTP request with an explicit method.
Only GET and HEAD methods are allowed. Other methods return 405 Method Not Allowed with an Allow header listing the permitted methods.
§Arguments
method- The HTTP method (GET and HEAD are allowed; others return 405).request_path- The HTTP request path (e.g.,/index.html).
Sourcepub async fn run_on(
&self,
addr: SocketAddr,
header_timeout: Duration,
) -> Result<(u16, ServerHandle), StaticError>
pub async fn run_on( &self, addr: SocketAddr, header_timeout: Duration, ) -> Result<(u16, ServerHandle), StaticError>
Run the server on a specific address with a configurable header-read timeout.
Spawns the server in a background Tokio task and returns immediately with the
assigned port number and a ServerHandle. Call handle.shutdown().await to
stop accepting new connections and wait for in-flight connections to finish.
§Header-Read Timeout
Connections that don’t send complete HTTP headers within header_timeout are closed.
This prevents slowloris attacks and resource exhaustion from incomplete requests.
§Arguments
addr- Socket address to bind to (e.g.,127.0.0.1:0for loopback ephemeral, or0.0.0.0:8080to bind all interfaces on a fixed port).header_timeout- Maximum time to wait for complete HTTP headers on each connection.
§Returns
Ok((u16, ServerHandle))with the assigned port number and a handle for graceful shutdown.Err(StaticError::Io)if binding to the socket fails.
Sourcepub async fn run(
&self,
header_timeout: Duration,
) -> Result<(u16, ServerHandle), StaticError>
pub async fn run( &self, header_timeout: Duration, ) -> Result<(u16, ServerHandle), StaticError>
Run the server on loopback (127.0.0.1) with a configurable header-read timeout.
Binds to an ephemeral port and spawns the server in a background Tokio task.
Returns immediately with the assigned port number and a ServerHandle. Dropping
the handle without calling shutdown() leaves the server running in the
background for the life of the process — the same behavior run() always had.
Call handle.shutdown().await to stop accepting new connections and wait for
in-flight connections to finish.
§Header-Read Timeout
Connections that don’t send complete HTTP headers within header_timeout are closed.
This prevents slowloris attacks and resource exhaustion from incomplete requests.
§Arguments
header_timeout- Maximum time to wait for complete HTTP headers on each connection.
§Returns
Ok((u16, ServerHandle))with the ephemeral port number assigned by the OS and a handle for graceful shutdown.Err(StaticError::Io)if binding to the socket fails.
§Example
use mini_static::Server;
use std::path::Path;
use std::time::Duration;
let server = Server::new(Path::new("./public"))?;
let (port, handle) = server.run(Duration::from_secs(30)).await?;
println!("Server running on http://127.0.0.1:{}", port);
// ... later, to stop it gracefully:
handle.shutdown().await;Sourcepub async fn run_all(
&self,
port: u16,
header_timeout: Duration,
) -> Result<(u16, ServerHandle), StaticError>
pub async fn run_all( &self, port: u16, header_timeout: Duration, ) -> Result<(u16, ServerHandle), StaticError>
Run the server on all interfaces (0.0.0.0) with a configurable header-read timeout.
Binds to a specified port on all network interfaces. Useful for containerized
deployments, reverse-proxy setups, or services that need to accept connections
from anywhere. Spawns the server in a background Tokio task and returns immediately
with the assigned port and a ServerHandle.
§Header-Read Timeout
Connections that don’t send complete HTTP headers within header_timeout are closed.
This prevents slowloris attacks and resource exhaustion from incomplete requests.
§Arguments
port- Port number to bind to (0 for ephemeral port assignment).header_timeout- Maximum time to wait for complete HTTP headers on each connection.
§Returns
Ok((u16, ServerHandle))with the assigned port number and a handle for graceful shutdown.Err(StaticError::Io)if binding to the socket fails.
§Example
use mini_static::Server;
use std::path::Path;
use std::time::Duration;
let server = Server::new(Path::new("./public"))?;
let (_port, handle) = server.run_all(8080, Duration::from_secs(30)).await?;
println!("Server listening on 0.0.0.0:8080");
handle.shutdown().await;Sourcepub async fn run_ephemeral(&self) -> Result<(u16, ServerHandle), StaticError>
pub async fn run_ephemeral(&self) -> Result<(u16, ServerHandle), StaticError>
Run the server on loopback (127.0.0.1) with a default header-read timeout.
Convenience wrapper around run() that uses a default 30-second header-read timeout.
Returns immediately with the ephemeral port number and a ServerHandle; the server
continues in a background Tokio task until the handle’s shutdown() is awaited or
the Tokio runtime shuts down.
This is the recommended method for tests and lightweight services that don’t require custom timeout configuration.
§Returns
Ok((u16, ServerHandle))with the ephemeral port number assigned by the OS and a handle for graceful shutdown.Err(StaticError::Io)if binding to the socket fails.
§Example
use mini_static::Server;
use std::path::Path;
let server = Server::new(Path::new("./public"))?;
let (port, handle) = server.run_ephemeral().await?;
println!("Server ready on http://127.0.0.1:{}", port);
handle.shutdown().await;Sourcepub fn handle_request_with_headers(
&self,
method: &Method,
request_path: &str,
_range_header: Option<&str>,
_if_range_header: Option<&str>,
) -> Response<ResponseBody>
pub fn handle_request_with_headers( &self, method: &Method, request_path: &str, _range_header: Option<&str>, _if_range_header: Option<&str>, ) -> Response<ResponseBody>
Handle an HTTP request with method and optional Range/If-Range headers (synchronous API).
This is the synchronous version of request handling used internally by the
async server loop. For most use cases, prefer using run() or run_ephemeral()
which handle the full async lifecycle.
Only GET and HEAD methods are allowed; other methods return 405 Method Not Allowed. All errors (missing files, traversal attempts, I/O failures) are returned as 404 to avoid leaking filesystem structure information.
§Range Request Handling
mini-static does not yet serve 206 Partial Content — every request,
ranged or not, gets the full body with 200. This is RFC 9110-correct behavior
(as opposed to incorrectly answering 416), but partial-content serving is
deferred to a later phase.
§Arguments
method- The HTTP method (GET and HEAD only).request_path- The HTTP request path (e.g.,/index.html)._range_header- Optional Range header (currently unused)._if_range_header- Optional If-Range header (currently unused).