eggserve-core 0.1.1

Security policy, path confinement, and static-serving primitives for eggserve
Documentation

eggserve

CI Crates.io PyPI PyPI Downloads License: MIT

EggServe is a hardened, HTTP-correct static file server and reusable Rust HTTP/static-serving library, with a Python http.server-shaped facade.

The CLI serves static files only. The Python package provides hardened static serving plus a bounded, synchronous custom-handler path shaped like http.server. The Rust crate exposes a low-level, embeddable HTTP runtime and service boundary. EggServe itself is not an application framework, ASGI/WSGI runtime, proxy, or general-purpose socketserver replacement.

Secure alternative to python -m http.server

python -m http.server is a useful local-development tool with a well-understood interface. EggServe provides a secure alternative built on the same mental model: loopback binding, path confinement, dotfile denial, and disabled directory listings are the defaults; broader behavior requires an explicit opt-in. It also adds native range and conditional responses, bounded resource limits, and the same hardened static service behind its CLI, Python, and Rust surfaces.

The concise surface comparison is in the Python compatibility contract.

CLI quickstart

Serve the small example fixture on loopback:

eggserve --directory ./examples/site

For a source checkout, the equivalent is:

cargo run -p eggserve-bin -- --directory ./examples/site

Make a public bind explicit when serving beyond the local machine:

eggserve --directory ./examples/site --public --port 8080

The positional form is eggserve [OPTIONS] [PORT] [DIRECTORY]. Explicit port sources occupy the PORT slot, so a numeric directory remains unambiguous after them—for example, eggserve --port 9000 1234 serves directory 1234. Use --directory 1234 when selecting a numeric directory without a positional port; a single positional numeric token continues to mean PORT.

The CLI is a static file server. Directory listings, symlink following, and dotfile serving are separate explicit flags. Static metadata can be set with --content-type and repeatable -H/--header; see the CLI reference and security policy.

Python http.server facade

The canonical Python static-serving example is examples/python_http_server_static.py. Run it with python examples/python_http_server_static.py; it is source- familiar while keeping the filesystem and transport in Rust.

Stock SimpleHTTPRequestHandler with the documented default eligibility uses the native static fast path. Directory listings, dotfiles, and symlinks remain denied unless explicitly enabled through the supported facade settings. The Python 3.15-shaped static metadata hooks are supported: set default_content_type for unknown suffixes and pass ordered extra_response_headers through a stock handler or functools.partial. Extra headers are emitted only on final 200 static responses and cannot override runtime-owned metadata. See examples/python_custom_headers.py for a working demonstration.

For bounded synchronous custom responses, use the complete examples/python_custom_handler.py. The optional subprocess lifecycle example is examples/python_subprocess.py; it is not the canonical http.server replacement.

When the tls feature is available, HTTPS serving uses HTTPSServer / ThreadingHTTPSServer — see examples/python_https_server.py.

Custom handlers are synchronous and receive bounded in-memory rfile/wfile facades. They do not receive raw sockets, do not provide unbounded streaming, and do not turn EggServe into an application server. The optional subprocess helpers are under eggserve.subprocess; the primary API is eggserve.server. See the Python API reference for the full six-class surface and the compatibility contract for intentional deviations from the stdlib.

Rust library

eggserve-core is the intended Rust library crate for the 0.x line. It exposes primitives as the semver-considered public facade and server as an experimental transport-owning runtime; there is no additional eggserve facade crate. Rust applications do not need a direct Hyper dependency.

The concise static-server flow is:

use eggserve_core::server::{RuntimeConfig, Server};

# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let server = Server::builder()
    .runtime(RuntimeConfig::builder()
        .bind("127.0.0.1:0".parse()?)
        .build()?)
    .static_service("public")?;
let handle = server.start().await?;
handle.ready().await?;
println!("listening on {}", handle.local_addr());
// ... make requests ...
handle.shutdown();
handle.wait().await?;
# Ok(())
# }

The executable, mechanically checked examples are the static server, the custom service, and the primitives demo. They use public EggServe modules only, include readiness plus graceful shutdown, and are the recommended starting points for custom services.

The runtime owns listeners, HTTP/1 parsing, framing, timeouts, and lifecycle; Service owns request handling and response construction. The server module is experimental before 1.0. See the Rust architecture overview, primitives facade, and runtime contract.

Security and compatibility boundaries

  • Loopback bind, no symlinks, no dotfiles, and no directory listing are the safe defaults for static serving.
  • Static serving is GET/HEAD only and rejects request bodies; custom services may opt into bounded bodies under the runtime ceiling.
  • Path traversal and symlink escape are denied at library level. Unix safe defaults use descriptor-relative resolution; Windows is qualified for the executed handle-relative classes but remains trusted/local-content only.
  • HTTP/1.1, ranges, conditional requests, canonical response normalization, and bounded resource admission are part of the implemented contract.
  • The CLI accepts hostnames in --bind, repeatable safe -H/--header static metadata, and --content-type; TLS accepts a combined cert/key PEM when --tls-key is omitted.
  • Raw socket ownership, translate_path(), arbitrary SSLContext handling, async Python handlers, unbounded Python response streaming, and ASGI/WSGI are intentionally unavailable.

See the security policy, threat model, Python compatibility matrix, and non-goals.

Installation

# Python wheel: CPython 3.11+; Linux, macOS, and Windows wheels are built
# according to the support matrix.
pip install eggserve

pipx run eggserve

# From source (requires a Rust toolchain)
cargo install --path crates/eggserve-bin

The source-checkout command installs the eggserve-bin package's eggserve binary. Rust embedders should add eggserve-core as their library dependency; the executable crate is intentionally a thin CLI surface.

The Python wheel includes the native extension and extension-backed CLI entry point; it does not bundle a second standalone CLI binary. See toolchain and wheel support.

Deeper references

CLI and installation:

Python:

Rust library:

Security:

Architecture:

Local verification

./scripts/verify.sh fast    # format, clippy, and workspace tests
./scripts/verify.sh full    # fast + examples + TLS + installed Python wheel checks
./scripts/verify.sh deep    # expensive suites selected for release risk

The routine CI workflow has separate Rust and Python jobs. Platform qualification and release certification are manual workflows; see the release process.