14个版本 (6个重大更改)

0.7.4 2024年6月17日
0.7.1 2024年4月23日
0.6.0 2024年2月20日
0.4.0 2023年10月28日
0.2.0 2023年1月14日

#1419 in HTTP服务器


ohkami 中使用

MIT 许可证

42KB
608

Ohkami

Ohkami - [狼] 狼,在日语中 - 是一个直观且声明式的Web框架。

  • 无宏且类型安全的 API,用于直观且声明式的代码
  • 多运行时 支持:tokio, async-std, worker (Cloudflare Workers)
License build check status of ohkami crates.io

快速开始

  1. 添加到 dependencies
# This sample uses `tokio` runtime.
# `async-std` is available by feature "rt_async-std".

[dependencies]
ohkami = { version = "0.19", features = ["rt_tokio"] }
tokio  = { version = "1",    features = ["full"] }
  1. 使用Ohkami编写你的第一个代码: examples/quick_start
use ohkami::prelude::*;
use ohkami::typed::status::NoContent;

async fn health_check() -> NoContent {
    NoContent
}

async fn hello(name: &str) -> String {
    format!("Hello, {name}!")
}

#[tokio::main]
async fn main() {
    Ohkami::new((
        "/healthz"
            .GET(health_check),
        "/hello/:name"
            .GET(hello),
    )).howl("localhost:3000").await
}
  1. 运行并检查行为
$ cargo run
$ curl https://127.0.0.1:3000/healthz
$ curl https://127.0.0.1:3000/hello/your_name
Hello, your_name!

Cloudflare Workers通过 "rt_worker" 功能支持

npm create cloudflare ./path/to/project -- --template https://github.com/kana-rus/ohkami-templates/worker

然后你的项目目录有 wrangler.toml, package.json 和一个Rust库crate。

通过 npm run dev 本地开发,并通过 npm run deploy 部署!

( 请参阅 模板 的README以获取详细信息 )


代码片段

处理路径参数

use ohkami::prelude::*;

#[tokio::main]
async fn main() {
    Ohkami::new((
        "/api/hello/:name"
            .GET(hello),
    )).howl("localhost:5000").await
}

async fn hello(name: &str) -> String {
    format!("Hello, {name}!")
}

处理请求体/查询参数

use ohkami::prelude::*;
use ohkami::typed::status::Created;

use ohkami::typed::{Query, Payload};
use ohkami::builtin::payload::JSON;

/* `serde = 〜` is not needed in your [dependencies] */
use ohkami::serde::{Serialize, Deserialize};

/* Payload + Deserialize for request */
#[Payload(JSON)]
#[derive(Deserialize)]
struct CreateUserRequest<'req> {
    name:     &'req str,
    password: &'req str,
}

/* Payload + Serialize for response */
#[Payload(JSON)]
#[derive(Serialize)]
struct User {
    name: String,
}

async fn create_user(body: CreateUserRequest<'_>) -> Created<User> {
    Created(User {
        name: String::from("ohkami")
    })
}

/* Shorthand for Payload + Serialize */
#[Payload(JSON/S)]
struct SearchResult {
    title: String,
}

#[Query] /* Params like `?lang=rust&q=framework` */
struct SearchQuery<'q> {
    lang:    &'q str,
    #[query(rename = "q")] /* #[serde]-compatible #[query] attribute */
    keyword: &'q str,
}

async fn search(condition: SearchQuery<'_>) -> Vec<SearchResult> {
    vec![
        SearchResult { title: String::from("ohkami") },
    ]
}

有效载荷验证

where <validation expression>#[Payload()] 运行验证,当响应或解析请求时使用它。

<validation expression> 是一个具有 self: &Self 返回 Result<(), impl Display> 的表达式。

use ohkami::prelude::*;
use ohkami::{typed::Payload, builtin::payload::JSON};

#[Payload(JSON/D where self.valid())]
struct Hello<'req> {
    name:   &'req str,
    repeat: usize,
}

impl Hello<'_> {
    fn valid(&self) -> Result<(), String> {
        (self.name.len() > 0).then_some(())
            .ok_or_else(|| format!("`name` must not be empty"))?;
        (self.repeat > 0).then_some(())
            .ok_or_else(|| format!("`repeat` must be positive"))?;
        Ok(())
    }
}

使用中间件

Ohkami的请求处理系统称为"fang",中间件是建立在

use ohkami::prelude::*;

#[derive(Clone)]
struct GreetingFang;

/* utility trait for auto impl `Fang` */
impl FangAction for GreetingFang {
    async fn fore<'a>(&'a self, req: &'a mut Request) -> Result<(), Response> {
        println!("Welcomm request!: {req:?}");
        Ok(())
    }
    async fn back<'a>(&'a self, res: &'a mut Response) {
        println!("Go, response!: {res:?}");
    }
}

#[tokio::main]
async fn main() {
    Ohkami::with(GreetingFang, (
        "/".GET(|| async {"Hello, fangs!"})
    )).howl("localhost:3000").await
}

具有"sse"功能的Server-Sent Events

Ohkami以HTTP/1.1 Transfer-Encoding: chunked响应。
使用一些反向代理来处理HTTP/2,3。

use ohkami::prelude::*;
use ohkami::typed::DataStream;
use tokio::time::sleep;

async fn sse() -> DataStream<String> {
    DataStream::from_iter_async((1..=5).map(|i| async move {
        sleep(std::time::Duration::from_secs(1)).await;
        Ok(format!("Hi, I'm message #{i} !"))
    }))
}

#[tokio::main]
async fn main() {
    Ohkami::new((
        "/sse".GET(sse),
    )).howl("localhost:5050").await
}

Ohkami的打包

use ohkami::prelude::*;
use ohkami::typed::status::{Created, NoContent};
use ohkami::typed::Payload;
use ohkami::builtin::payload::JSON;

#[Payload(JSON/S)]
struct User {
    name: String
}

async fn list_users() -> Vec<User> {
    vec![
        User { name: String::from("actix") },
        User { name: String::from("axum") },
        User { name: String::from("ohkami") },
    ]
}

async fn create_user() -> Created<User> {
    Created(User {
        name: String::from("ohkami web framework")
    })
}

async fn health_check() -> NoContent {
    NoContent
}

#[tokio::main]
async fn main() {
    // ...

    let users_ohkami = Ohkami::new((
        "/"
            .GET(list_users)
            .POST(create_user),
    ));

    Ohkami::new((
        "/healthz"
            .GET(health_check),
        "/api/users"
            .By(users_ohkami), // nest by `By`
    )).howl("localhost:5000").await
}

测试

use ohkami::prelude::*;
use ohkami::testing::*; // <--

fn hello_ohkami() -> Ohkami {
    Ohkami::new((
        "/hello".GET(|| async {"Hello, world!"}),
    ))
}

#[cfg(test)]
#[tokio::test]
async fn test_my_ohkami() {
    let t = hello_ohkami().test();

    let req = TestRequest::GET("/");
    let res = t.oneshot(req).await;
    assert_eq!(res.status(), Status::NotFound);

    let req = TestRequest::GET("/hello");
    let res = t.oneshot(req).await;
    assert_eq!(res.status(), Status::OK);
    assert_eq!(res.text(), Some("Hello, world!"));
}

支持的协议

  • HTTP/1.1
  • HTTP/2
  • HTTP/3
  • HTTPS
  • Server-Sent Events
  • WebSocket

MSRV(最低支持的Rust版本)

最新稳定版

许可证

ohkami遵循MIT许可证(LICENSEhttps://opensource.org/licenses/MIT)。

依赖项

~1.2–1.8MB
~43K SLoC