eggnet-tls 0.2.0

Neutral rustls server identity, client-auth, trust, and reload configuration
Documentation

eggserve

CI Crates.io PyPI PyPI Downloads License: MIT

EggServe is a hardened, HTTP-correct static file server, plus a reusable static-serving library for Python and Rust. It is a secure alternative to python -m http.server with the same mental model and safe defaults.

Safe defaults: loopback bind, path confinement, no symlinks, no dotfiles, no directory listing. Broader behavior is always an explicit opt-in. See security policy.

Install

pip install eggserve          # Python library + CLI (CPython 3.11+)
cargo install --path crates/eggserve-bin   # CLI from source

Quick start

Python

Static files with the familiar http.server shape:

from functools import partial
from eggserve.server import SimpleHTTPRequestHandler, ThreadingHTTPServer

handler = partial(SimpleHTTPRequestHandler, directory="public")
with ThreadingHTTPServer(("127.0.0.1", 8000), handler) as server:
    server.serve_forever()

A custom handler on the same runtime (bounded, synchronous):

from eggserve.server import BaseHTTPRequestHandler, ThreadingHTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = b"ok\n" if self.path == "/health" else b"not found\n"
        status = 200 if self.path == "/health" else 404
        self.send_response(status)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

with ThreadingHTTPServer(("127.0.0.1", 8000), Handler) as server:
    server.serve_forever()

A handler-only service without the http.server facade:

from eggserve import lowlevel

def handler(request):
    if request.path == "/health":
        return lowlevel.Response.text(200, "ok\n")
    return lowlevel.Response.text(404, "not found\n")

server = lowlevel.Server(
    config=lowlevel.RuntimeConfig(bind="127.0.0.1", port=8000),
    handler=handler,
)
server.start()
server.wait_ready()

See Python API and the compatibility contract for intentional http.server deviations.

Rust

Add the smallest crate you need (eggserve-core for the composed server):

[dependencies]
eggserve-core = "0.2"
tokio = { version = "1", features = ["full"] }

Serve a confined static directory:

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:8000".parse()?)
        .build()?)
    .static_service("public")?;
let handle = server.start().await?;
handle.ready().await?;
# Ok(())
# }

Serve a custom service instead:

use eggserve_core::primitives::canonical::{Response, ResponseBody, StatusCode};
use eggserve_core::server::{service_fn, Request, RuntimeConfig, Server, ServiceError};

# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let server = Server::builder()
    .runtime(RuntimeConfig::builder()
        .bind("127.0.0.1:8000".parse()?)
        .build()?)
    .build()?;
let service = service_fn(|request: Request| async move {
    let body = b"ok\n";
    Response::builder()
        .status(StatusCode::OK)
        .body(ResponseBody::Bytes(body.to_vec()))
        .map_err(|e| ServiceError::internal(e.to_string()))
});
let handle = server.start_with_service(service).await?;
handle.ready().await?;
# Ok(())
# }

H1 is the supported transport; H2/H3 are opt-in and experimental. See public API boundary and downstream app servers.

CLI

eggserve --directory ./public            # loopback, static only
eggserve --directory ./public --public --port 8080   # explicit public bind

See CLI reference.

Examples

Runnable starting points in examples/:

  • Python: static facade, custom handler, lowlevel service, HTTPS
  • Rust: static server, custom service, streaming service, caller-owned stream
  • CLI: local static server, explicit public bind, directory listing opt-in

Documentation