Ohkami
A performant, declarative, and runtime-flexible web framework for Rust
Install / Use
/learn @ohkami-rs/OhkamiREADME
- macro-less and type-safe APIs for declarative, ergonomic code
- runtime-flexible :
tokio,smol,nio,glommio,monoio,compioandworker(Cloudflare Workers),lambda(AWS Lambda) - good performance, no-network testing, well-structured middlewares, Server-Sent Events, WebSocket, highly integrated OpenAPI document generation, ...
Quick Start
- Add to
dependencies:
[dependencies]
ohkami = { version = "0.24", features = ["rt_tokio"] }
tokio = { version = "1", features = ["full"] }
- Write your first code with Ohkami : examples/quick_start
use ohkami::{Ohkami, Route};
use ohkami::claw::{Path, status};
async fn health_check() -> status::NoContent {
status::NoContent
}
async fn hello(Path(name): Path<&str>) -> String {
format!("Hello, {name}!")
}
#[tokio::main]
async fn main() {
Ohkami::new((
"/healthz"
.GET(health_check),
"/hello/:name"
.GET(hello),
)).howl("localhost:3000").await
}
- Run and check the behavior :
$ cargo run
$ curl http://localhost:3000/healthz
$ curl http://localhost:3000/hello/your_name
Hello, your_name!
<br>
Core APIs
Ohkami
Ohkami is the main entry point of Ohkami application:
a collection of Routes and Fangs, and provides .howl()/.howls() method to run the application.
Ohkami::new((
// global fangs
Fang1,
Fang2,
// routes
"/hello"
.GET(hello_handler)
.POST(hello_post_handler),
"/goodbye"
.GET((
// local fangs
Fang3,
Fang4,
goodbye_handler // handler
)),
)).howl("localhost:3000").await;
.howls() (tls feature only) is used to run Ohkami with TLS (HTTPS) support
upon rustls ecosystem.
howl(s) supports graceful shutdown by Ctrl-C ( SIGINT ) on native runtimes.
Route
Route is the core trait to define Ohkami's routing:
.GET(),.POST(),.PUT(),.PATCH(),.DELETE(),.OPTIONS()to define API endpoints.By({another Ohkami})to nestOhkamis.Mount({directory path})to serve static directory (pre-compressed files withgzip,deflate,br,zstdare supported)
Here GET, POST, etc. takes a handler function:
async fn({FromRequest type},*) -> {IntoResponse type}
On native runtimes, whole a handler must be Send + Sync + 'static
and the return future must be Send + 'static.
fangs
Ohkami's request handling system is called fang; all handlers and middlewares are built on it.
/* simplified for description */
pub trait Fang<Inner: FangProc> {
type Proc: FangProc;
fn chain(&self, inner: Inner) -> Self::Proc;
}
pub trait FangProc {
async fn bite<'b>(&'b self, req: &'b mut Request) -> Response;
}
built-ins:
BasicAuth,Cors,Csrf,Jwt(authentication/security)Context(reuqest context)Enamel(security headers; experimantal)Timeout(handling timeout; native runtimes only)openapi::Tag(tag for OpenAPI document generation;openapifeature only)
Ohkami provides FangAction utility trait to implement Fang trait easily:
/* simplified for description */
pub trait FangAction {
async fn fore<'a>(&'a self, req: &'a mut Request) -> Result<(), Response> {
// default implementation is empty
Ok(())
}
async fn back<'a>(&'a self, res: &'a mut Response) {
// default implementation is empty
}
}
Additionally, you can apply fangs both as global fangs to an Ohkami or
as local fangs to a specific handler (described below).
claws
Ohkami provides claw API: handler parts designed for declarative way to
extract request data and construct response data:
content- typed content {extracted from request / for response} of specific format- built-ins:
Json<T>,Text<T>,Html<T>,UrlEncoded<T>,Multipart<T>
- built-ins:
param- typed parameters extracted from request- built-ins:
Path<P>,Query<T>
- built-ins:
header- types for specific header extracted from request- built-ins: types for standard request headers
status- types for response with specific status code- built-ins: types for standard response status codes
<sm><i>( here <code>T</code> means a type that implements <code>serde::Deserialize</code> for request and <code>serde::Serialize</code> for response, and <code>P</code> means a type that implements <code>FromParam</code> or a tuple of such types. )</i></sm>
The number of path parameters extracted by Path is automatically asserted
to be the same or less than the number of path parameters contained in the route path
when the handler is registered to routing.
async fn handler0(
Path(param): Path<FromParamType>,
) -> Json<SerializeType> {
// ...
}
async fn handler1(
Json(req): Json<Deserialize0>,
Path((param0, param1)): Path<(FromParam0, FromParam1)>,
Query(query): Query<Deserialize1>,
) -> status::Created<Json<Serialize0>> {
// ...
}
<br>
Feature flags
"rt_tokio", "rt_smol", "rt_nio", "rt_glommio", "rt_monoio", "rt_compio" : native async runtime
"rt_worker" : Cloudflare Workers
- worker v0.7.*
Ohkami has first-class support for Cloudflare Workers:
#[worker]macro to define a Worker#[bindings],ws::SessionMaphelper- better
DurableObject - not require
SendSyncbound for handlers or fangs - worker_openapi.js script to generate OpenAPI document from
#[worker]fn
And also maintains useful project template. Run :
npm create cloudflare <project dir> -- --template https://github.com/ohkami-rs/templates/worker
then <project dir> will have wrangler.jsonc, package.json and a Rust library crate.
#[ohkami::worker] async? fn({bindings}?) -> Ohkami is the Worker definition.
Local dev by npm run dev and deploy by npm run deploy !
See
worker*temaplates in template repositoryworker*samples in samples directory#[worker]'s documentation comment in macro definitions
for wokring examples and detailed usage of #[worker] (and/or openapi).
"rt_lambda" : AWS Lambda
- lambda_runtime v1.0.* with
tokio
Both Function URLs and API Gateway are supported, and WebSocket is not supported.
cargo lambda will be good partner. Let's run :
cargo lambda new <project dir> --template https://github.com/ohkami-rs/templates
lambda_runtime::run(your_ohkami) make you_ohkami run on Lambda Function.
Local dev by
cargo lambda watch
and deploy by
cargo lambda build --release [--compiler cargo] [and more]
cargo lambda deploy [--role <arn-of-a-iam-role>] [and more]
See
- README of template
- Cargo Lambda document
for details.
"sse" : Server-Sent Events
Ohkami responds with HTTP/1.1 Transfer-Encoding: chunked.
Use some reverse proxy to do with HTTP/2,3.
use ohkami::{Ohkami, Route};
use ohkami::sse::DataStream;
use tokio::time::{sleep, Duration};
async fn handler() -> DataStream {
DataStream::new(|mut s| async move {
s.send("starting streaming...");
for i in 1..=5 {
sleep(Duration::from_secs(1)).await;
s.send(format!("MESSAGE #{i}"));
}
s.send("streaming finished!");
})
}
#[tokio::main]
async fn main() {
Ohkami::new((
"/sse".GET(handler),
)).howl("localhost:3020").await
}
"ws" : WebSocket
use ohkami::{Ohkami, Route};
use ohkami::ws::{WebSocketContext, WebSocket, Message};
async fn echo_text(ctx: WebSocketContext<'_>) -> WebSocket {
ctx.upgrade(|mut conn| async move {
while let Ok(Some(Message::Text(text))) = conn.recv().await {
conn.send(text).await.expect("failed to send text");
}
})
}
#[tokio::main]
async fn main() {
Ohkami::new((
"/ws".GET(echo_text),
)).howl("localhost:3030").await
}
- On
"rt_worker", both normal ( stateless ) WebSocket and WebSocket on Durable Object are avail
