# sark
HTTP, WebSocket, and gRPC for Dope.
Requires Rust 1.98. Production applications must use `panic = "abort"` in
their workspace's release profile.
## Run an example
From this workspace, start the download server:
```sh
cargo +stable run --release -p sark-fiber --example download
```
In another terminal:
```sh
curl --fail http://127.0.0.1:8080/download/3
```
The response is three `chunk` lines. Append `-- --bind ADDRESS` to change the listener address.
| [Download](fiber/examples/download.rs) | `-p sark-fiber --example download` | Iterator streaming; `--bind` defaults to `127.0.0.1:8080` |
| [Redis](fiber/examples/redis.rs) | `-p sark-fiber --example redis` | `GET /` increments a counter; `POST /reset` deletes it |
| [Leaderboard](fiber/examples/leaderboard.rs) | `-p sark-fiber --example leaderboard` | JSON input and Redis sorted sets |
| [Postgres](fiber/examples/pg.rs) | `-p sark-fiber --example pg` | Queries and transactions |
| [HTTP/2](h2/examples/server.rs) | `-p sark-h2 --example server` | Fixed responses; `--bind` defaults to `127.0.0.1:18080` |
| [Deadlines](core/examples/deadline.rs) | `-p sark-core --example deadline` | Cancellation and rescheduling |
Use each target with the same `cargo +stable run --release` command.
Redis examples require an existing Redis server (`--redis-addr`, default
`127.0.0.1:6379`). The Postgres example requires a database matching its `User`
model and accepts `--pg-addr`, `--pg-user`, `--pg-password`, and `--pg-database`.
See [input defaults](fiber/examples/input/mod.rs). The examples do not create or seed databases.
## Handlers and application state
Declare request fields with `#[sark::request]`, implement a named function with
`#[sark::handler]`, and register it with `sark::define_route!`. A handler only
needs a request parameter when it consumes request data. State-only handlers
take the installed provider `&C` directly; they need no empty request type.
For example, the Postgres list handler is:
```rust
#[sark::handler]
async fn list_users<C>(pg: &C) -> Result<Response<Rows<C::Batch>>, body::Error<'_>>
where
C: Client<'_, PG_CONNECTOR_ID, Schema = Db>,
{
let users = User::page(pg.query(), 0, USER_PAGE_CAPACITY).await?;
Ok(body::response(users.validate()?)?)
}
```
See [pg.rs](fiber/examples/pg.rs) and its [response encoder](fiber/examples/body/mod.rs).
`C::Batch` retains the query result through encoding. Application state need not
be `Copy`; it must remain inside its client scope until the session finishes.
Each route sets its body policy and task capacity; `sark_fiber::Config` sets
the application's task limit.
Transactions:
```rust
let tx = pg.transaction().begin().await?;
let outcome = User::rename(tx.query(), id.get(), name).await;
tx.finish(outcome).await?;
```
`finish` commits a successful operation or rolls back a failed one. Its typed
failure preserves both operation and rollback errors when both fail. The
example borrows `name` from the request across the operation.
## Server APIs
Generated H1 applications use `App::serve` or `App::serve_with` for an explicit
wire binding. Keep pinned state, the application, provider, and completion
storage alive until the session finishes. Workers own independent applications;
listener, upstream pool, and task capacities are separate settings.
Use `sark_h1::server::listener::Config::tcp(bind, capacity, head_timeout)` with
a validated `conn::Capacity`, or `Config::new` with a prepared Dope listener
for custom socket and egress settings.
Iterator responses use `sark_fiber::fiber::IntoStream`, as in the
[download example](fiber/examples/download.rs). Asynchronous producers implement
`dope_fiber::abi::Stream`.
`define_router!` creates a synchronous H1 router. `define_task_route!` creates
H2/H3 task applications using `sark-fiber`. H2 function handlers use
`sark_h2::server::Server::from_fn`, as in the [HTTP/2 example](h2/examples/server.rs).
JSON, WebSocket, and gRPC applications depend on `sark-json`, `sark-ws`, and
`sark-grpc` respectively. JSON uses `#[sark_json::json]`; gRPC uses Prost messages
and the `sark-grpc-build` service generator.
## HTTP/3 connection lifetime
For HTTP/3, choose positive `Duration` values for `max_age` and `drain_timeout`
when binding an existing `sark_h3::manifold::server::startup::Plan`:
```rust
let lifetime = sark_core::life::session::Config::try_new(max_age, drain_timeout)?;
let endpoint = plan.try_with::<dope_quic::runtime::mux::app::Silent, _, _, _, _, _>(
router, observer, lifetime, deadline, sark_h3::manifold::server::Tasks::new(), driver,
)?;
```
Age starts when the connection is established. Expiry begins graceful draining
and rejects new requests; drain expiry closes the connection. Request deadlines
end when their request retires. QUIC idle timeout can be refreshed by traffic,
so neither bounds an active connection's lifetime. Pass
`sark_core::life::session::Disabled` instead of `lifetime` only when finite
connection rotation is not required.
© 2026 inkyu