diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5f3166 --- /dev/null +++ b/README.md @@ -0,0 +1,218 @@ +# urus + +A cowboy/bandit-style HTTP library for the smarm actor runtime. + +## Overview + +`urus` is a lightweight, actor-first HTTP/1.1 library designed to integrate seamlessly with the [smarm](https://github.com/Markk116/smarm) actor runtime. Instead of traditional shared mutable state and locking patterns, `urus` embraces the actor model: connection handling is fully concurrent, and request processing pipelines are message-passing all the way down. + +**Key design principles:** +- **No locks**: State is owned by actors; concurrency is via channels. +- **Actor-native**: Built from the ground up for smarm; each connection is an actor. +- **Pluggable pipelines**: Compose HTTP request handling logic with `Plug` and `Pipeline`. +- **Fast HTTP/1.1 parsing**: Uses `httparse` for robust, battle-tested RFC 7230 compliance. + +## Quick Start + +```toml +[dependencies] +urus = { path = "../urus" } +smarm = { path = "../smarm" } +``` + +### Minimal Example + +```rust +use urus::{Pipeline, Router, Conn, Next, serve}; + +let pipeline = Pipeline::new().plug( + Router::new() + .get("/", |c: Conn, _n: Next| { + c.put_status(200).put_body("hello") + }) +); + +serve("0.0.0.0:8080", pipeline).unwrap(); +``` + +Then: +```bash +cargo run --example hello +curl http://localhost:8080/ +``` + +### Routing with Path Parameters + +```rust +Router::new() + .get("/users/:id", |c: Conn, _n: Next| { + let id = c.params.get("id").unwrap_or("").to_string(); + c.put_status(200).put_body(format!("user: {}", id)) + }) +``` + +Path parameters are extracted into `c.params: HashMap`. + +## Core Concepts + +### `Conn` — The Connection Object + +The `Conn` struct represents a single HTTP request/response pair: + +```rust +pub struct Conn { + pub method: Method, + pub path: String, + pub params: HashMap, // Path parameters (e.g., :id) + pub assigns: Assigns, // Request-scoped state + pub body: Body, // Request body + pub headers: HeaderMap, // Request headers + pub status: u16, // Response status (default 200) + pub resp_headers: HeaderMap, // Response headers + pub resp_body: RespBody, // Response body + // ... (internal fields) +} +``` + +Builders make it ergonomic to modify response state: + +```rust +c.put_status(201) + .put_header("content-type", "application/json") + .put_body(r#"{"ok": true}"#) +``` + +`c.assigns` is a request-scoped key-value store (similar to Plug's Assigns in Elixir/Phoenix) for passing data between plugs in a pipeline. + +### `Pipeline` — Composable Handlers + +A pipeline chains plugs (middleware/handlers) together. Each plug receives a `Conn`, can modify it, and passes it to the next plug via the `Next` callback: + +```rust +use urus::{Pipeline, Plug, Conn, Next}; + +struct LoggingPlug; + +impl Plug for LoggingPlug { + fn call(&self, mut c: Conn, n: Next) -> Conn { + println!("{} {}", c.method, c.path); + n.call(c) + } +} + +let pipeline = Pipeline::new() + .plug(LoggingPlug) + .plug(Router::new().get("/", |c, _| c.put_body("ok"))); +``` + +### `Router` — Path Matching + +The built-in router matches HTTP methods and paths: + +```rust +Router::new() + .get("/", handler_fn) + .post("/users", create_user) + .get("/users/:id", get_user) + .put("/users/:id", update_user) + .delete("/users/:id", delete_user) +``` + +Handlers are closures taking `(Conn, Next) -> Conn`. Call `Next::call(c)` to continue to the next plug; omitting it short-circuits the pipeline (e.g., for authentication failures). + +### Server Configuration + +`serve(addr, pipeline)` binds and listens on the given address. For more control, use `serve_with(config, pipeline)`: + +```rust +use urus::{serve_with, Config}; +use std::net::SocketAddr; + +let cfg = Config { + listener_pool: 2, // Number of OS threads handling accept() + scheduler_threads: Some(2), // Number of smarm worker threads + ..Config::new("127.0.0.1:8080".parse().unwrap()) +}; + +serve_with(cfg, pipeline).unwrap(); +``` + +## Examples + +### CRUD with Actor Ownership + +See [`examples/crud.rs`](examples/crud.rs) for a complete example demonstrating: + +- A background "store" actor that owns all data. +- Handlers sending requests to the store via a channel. +- No locks, no shared mutable state. +- Automatic JSON serialization and file persistence. + +Run it: +```bash +cargo run --example crud +curl -s http://localhost:8080/users +curl -s -X POST -d '{"name":"alice","email":"a@x"}' http://localhost:8080/users +curl -s http://localhost:8080/users/1 +``` + +## Testing + +Integration tests spawn the server on an ephemeral port and issue real TCP requests: + +```bash +cargo test +``` + +Tests in `tests/integration.rs` cover: +- Basic routing (GET, POST, PUT, DELETE) +- Request body echoing +- HTTP header parsing +- Path parameter extraction +- Status code responses + +## Architecture + +### Connection Actors + +Each incoming TCP connection is handled by a dedicated actor (spawned in `conn_actor.rs`). The actor: + +1. Parses the HTTP request line and headers. +2. Reads the body (if present). +3. Runs the request through the pipeline. +4. Writes the HTTP response to the socket. +5. Closes the connection (or handles pipelining if HTTP/1.1 Keep-Alive is enabled). + +All of this happens concurrently with other connections—no thread pool juggling required. The smarm scheduler handles actor fairness. + +### Parsing + +HTTP request parsing uses the robust `httparse` crate, which handles: +- Chunked transfer encoding (for request bodies). +- Header validation. +- Method and URI parsing. +- HTTP version detection. + +### No Async/Await + +`urus` uses **synchronous code with blocking channels**. This is intentional: + +- Simpler to reason about; no complex state machines. +- Each actor runs in a smarm worker thread, blocked on I/O or channels. +- The scheduler multiplexes many actors across a thread pool. + +This avoids the complexity of async ecosystems while maintaining full concurrency. + +## Roadmap + +v1 covers HTTP/1.1, the plug pipeline, and a built-in router. Future versions may include: +- Middleware ecosystem (auth, logging, compression, etc.). +- WebSocket support. +- Benchmarking suite (see `urus-bench-spec.md`). +- Additional utilities and examples. + +Refer to `urus-spec.md` and `urus-v1-build-notes.md` in the artifact persistence for the original design and implementation notes. + +## License + +MIT