sark-core 0.23.0

Fiber-free request, route, dispatch, and lifecycle core for Sark
Documentation

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:

cargo +stable run --release -p sark-fiber --example download

In another terminal:

curl --fail http://127.0.0.1:8080/download/3

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:

#[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 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 = 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. 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 = 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