# eggserve
[](https://github.com/eggstack/eggserve/actions/workflows/ci.yml)
[](https://crates.io/crates/eggserve-core)
[](https://pypi.org/project/eggserve/)
[](https://pepy.tech/projects/eggserve)
[](https://github.com/eggstack/eggserve/blob/main/LICENSE)
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](docs/security-policy.md).
## Install
```sh
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:
```python
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):
```python
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:
```python
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](docs/python-api.md) and the
[compatibility contract](docs/python-http-server-compatibility.md) for
intentional `http.server` deviations.
### Rust
Add the smallest crate you need (`eggserve-core` for the composed server):
```toml
[dependencies]
eggserve-core = "0.2"
tokio = { version = "1", features = ["full"] }
```
Serve a confined static directory:
```rust,no_run
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:
```rust,no_run
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](docs/public-api-boundary.md) and
[downstream app servers](docs/downstream-app-server.md).
### CLI
```sh
eggserve --directory ./public # loopback, static only
eggserve --directory ./public --public --port 8080 # explicit public bind
```
See [CLI reference](docs/cli.md).
## Examples
Runnable starting points in [examples/](examples/README.md):
- 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
- [CLI reference](docs/cli.md) · [deployment](docs/deployment.md) · [timeouts](docs/timeout-reference.md) · [TLS](docs/tls.md)
- [Python API](docs/python-api.md) · [compatibility contract](docs/python-http-server-compatibility.md)
- [HTTP primitives](docs/http-primitives.md) · [migration guide](docs/migration-guide.md)
- [Threat model](docs/threat-model.md) · [non-goals](docs/non-goals.md)
- [Architecture overview](architecture/overview.md)