# 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