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:
In another terminal:
The response is three chunk lines. Append -- --bind ADDRESS to change the listener address.
| Example | Run target | Usage |
|---|---|---|
| Download | -p sark-fiber --example download |
Iterator streaming; --bind defaults to 127.0.0.1:8080 |
| Redis | -p sark-fiber --example redis |
GET / increments a counter; POST /reset deletes it |
| Leaderboard | -p sark-fiber --example leaderboard |
JSON input and Redis sorted sets |
| Postgres | -p sark-fiber --example pg |
Queries and transactions |
| HTTP/2 | -p sark-h2 --example server |
Fixed responses; --bind defaults to 127.0.0.1:18080 |
| Deadlines | -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. 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:
async
See pg.rs and its response encoder.
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:
let tx = pg.transaction.begin.await?;
let outcome = rename.await;
tx.finish.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. 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.
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:
let lifetime = try_new?;
let endpoint = plan.?;
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