9 个版本 (5 个重大变更)

0.6.2 2024 年 7 月 24 日
0.6.1 2024 年 7 月 24 日
0.5.0 2024 年 5 月 22 日
0.4.1 2024 年 3 月 31 日
0.1.0 2023 年 12 月 29 日

#1498 in 网页编程

Download history 8/week @ 2024-05-02 223/week @ 2024-05-16 201/week @ 2024-05-23 24/week @ 2024-05-30 11/week @ 2024-06-06 6/week @ 2024-06-13 22/week @ 2024-06-20 39/week @ 2024-06-27 62/week @ 2024-07-04 68/week @ 2024-07-11 199/week @ 2024-07-18 316/week @ 2024-07-25 81/week @ 2024-08-01 147/week @ 2024-08-08 163/week @ 2024-08-15

每月 874 次下载
用于 zip_static_handler

Apache-2.0

1MB
26K SLoC

可组合的 Rust 异步网页框架

特性

  • 100% 安全的 Rust
  • 可组合的 API,满足高级使用,并可以轻松扩展到和/或与中/低级别混合
  • 静态类型,使用运行时动态类型转换最少
  • 最小依赖树和快速编译时间

lib.rs:

一个专注于内存效率、可组合性和快速编译时间的网页框架。

快速开始

use xitca_web::{handler::handler_service, route::get, App};

fn main() -> std::io::Result<()> {
    App::new()
        .at("/", get(handler_service(|| async { "Hello,World!" })))
        .serve()
        .bind("localhost:8080")?
        .run()
        .wait()
}

内存高效

  • 零拷贝魔法类型
  • 零成本服务树

零拷贝

use xitca_web::{
    error::Error,
    handler::{handler_service, json::LazyJson, path::PathRef},
    route::{get, post},
    App
};

// PathRef is able to borrow http request's path string as reference
// without copying it.
async fn url_query(PathRef(path): PathRef<'_>) -> &'static str {
    println!("{path}");
    "zero copy path"    
}

// deserializable user type.
#[derive(serde::Deserialize)]
struct User<'a> {
    name: &'a str
}

// LazyJson is able to lazily deserialize User type with zero copy &str.
async fn json(lazy: LazyJson<User<'_>>) -> Result<&'static str, Error> {
    let User { name } = lazy.deserialize()?;
    println!("{name}");
    Ok("zero copy json")    
}

// Almost all magic extract types in xitca-web utilize zero copy
// to avoid unnecessary memory copy.
App::new()
    // a route handling incoming url query.
    .at("/query", get(handler_service(url_query)))
    // a route handling incoming json object.
    .at("/json", post(handler_service(json)))
    .serve()
    .bind("localhost:8080")?
    .run()
    .wait()

零成本

use xitca_web::{
    handler::{handler_service},
    http::WebResponse,
    route::get,
    middleware::Extension,
    service::{Service, ServiceExt},
    App, WebContext
};
App::new()
    .at("/", get(handler_service(|| async { "hello,world!" })))
    // ServiceExt::enclosed_fn combinator enables async function as middleware.
    // the async function is unboxed and potentially inlined with other async services
    // for efficient binary code with less memory allocation.
    .enclosed_fn(middleware_fn)
    // ServiceExt::enclosed combinator enables type impl Service trait as middleware.
    // the middleware trait method is unboxed and potentially inlined with other async services
    // for efficient binary code with less memory allocation.
    .enclosed(Extension::new(()))
    .serve()
    .bind("localhost:8080")?
    .run()
    .wait()

// a simple middleware just forward request to inner service logic.
async fn middleware_fn<S, T, E>(service: &S, ctx: WebContext<'_>) -> Result<T, E>
where
    S: for<'r> Service<WebContext<'r>, Response = T, Error = E>
{
    service.call(ctx).await
}

可组合

  • 易于混合各种级别的抽象和较少意见的 API
  • 跨 crate 集成的常用类型和特质

抽象多样性

use xitca_web::{
    body::ResponseBody,
    error::Error,
    handler::{handler_service, handler_sync_service, FromRequest},
    http::{Method, WebResponse},
    route::get,
    service::fn_service,
    App, WebContext
};

App::new()
    // high level abstraction. see fn high for detail.
    .at("/high", get(handler_service(high)))
    // low level abstraction. see fn low for detail.
    .at("/low", get(fn_service(low)))
    // abstraction for synchronous. see fn sync for detail.
    .at("/sync", get(handler_sync_service(sync)))
    .serve()
    .bind("localhost:8080")?
    .run()
    .wait()

// magic function with arbitrary receiver type and output type
// that can be extracted from http requests and packed into http
// response.
async fn high(method: &Method) -> &'static str {
    // extract http method from http request.
    assert_eq!(method, Method::GET);
    // pack string literal into http response.
    "high level"     
}

// function with concrete typed input and output where http types
// are handled manually.
async fn low(ctx: WebContext<'_>) -> Result<WebResponse, Error> {
    // manually check http method.
    assert_eq!(ctx.req().method(), Method::GET);

    // high level abstraction can be opt-in explicitly if desired.
    // below is roughly what async fn high did to receive &Method as
    // function argument.
    let method = <&Method>::from_request(&ctx).await?;
    assert_eq!(method, Method::GET);
    
    // manually pack http response.
    Ok(WebResponse::new(ResponseBody::from("low level")))       
}

// high level abstraction but for synchronous function. this function
// is powered by background thread pool so it does not block the async
// code.
fn sync(method: Method) -> &'static str {
    assert_eq!(method, Method::GET);
    // blocking thread for long period of time does not impact xitca-web
    // async internal.
    std::thread::sleep(std::time::Duration::from_secs(3));
    "sync"    
}

中间件可组合性

use xitca_web::{
    error::Error,
    handler::{handler_service},
    http::WebResponse,
    route::get,
    service::{Service, ServiceExt},
    App, WebContext
};

// ServiceExt::enclosed_fn combinator enables async function as middleware.
// in xitca_web almost all service can be enclosed by an middleware.
App::new()
    .at("/",
        get(
            // apply middleware to handler_service
            handler_service(|| async { "hello,world!" })
                .enclosed_fn(middleware_fn)
        )
        // apply middleware to route
        .enclosed_fn(middleware_fn)
    )
    // apply middleware to application
    .enclosed_fn(middleware_fn)
    .serve()
    .bind("localhost:8080")?
    .run()
    .wait()

// a simple middleware just forward request to inner service logic.
async fn middleware_fn<S, T, E>(service: &S, ctx: WebContext<'_>) -> Result<T, E>
where
    S: for<'r> Service<WebContext<'r>, Response = T, Error = E>
{
    service.call(ctx).await
}

有关更详细的中间件文档,请参阅 [中间件]

跨 crate 集成

在 xitca-web 应用程序中使用 tower-http。

use tower_http::services::ServeDir;
use xitca_web::{
    service::tower_http_compat::TowerHttpCompat,
    App
};

App::new()
    .at("/", TowerHttpCompat::new(ServeDir::new("/some_folder")))
    .serve()
    .bind("localhost:8080")?
    .run()
    .wait()

快速编译时间

  • 增量 proc macro
  • 轻量级依赖树

可选的 proc macro

在 xitca-web 中,proc macro 是可选的。这导致快速编译时间,且没有公开的 proc macro。但仍然可以为更高层次的 API 启用宏。

use xitca_web::{codegen::route, App};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    App::new()
        .at_typed(index)
        .serve()
        .bind("localhost:8080")?
        .run()
        .await
}

#[route("/", method = get)]
async fn index() -> &'static str {
    "Hello,World!"
}

宏代码生成模块。http 类型路由服务。

示例

// a simple async function service.
let service = handler_service(|_: ()| async { });

let custom_method = Method::from_bytes(b"huh")?;

// route get http method and a user custom method to handler_service
Route::new([Method::GET, custom_method]).route(service.clone());

// shortcut for single http method route(s). they can chained multiple times.
get(service.clone()).post(service.clone()).put(service);

依赖

~3–15MB
~179K SLoC