6 releases (3 breaking)

0.4.1 Mar 31, 2024
0.4.0 Mar 4, 2024
0.3.0 Feb 16, 2024
0.2.2 Jan 24, 2024
0.1.0 Dec 29, 2023

#1 in #xitca

Download history 68/week @ 2023-12-30 16/week @ 2024-01-06 83/week @ 2024-01-13 57/week @ 2024-01-20 20/week @ 2024-01-27 253/week @ 2024-02-03 320/week @ 2024-02-10 138/week @ 2024-02-17 49/week @ 2024-02-24 211/week @ 2024-03-02 91/week @ 2024-03-09 19/week @ 2024-03-16 43/week @ 2024-03-23 348/week @ 2024-03-30 59/week @ 2024-04-06 15/week @ 2024-04-13

471 downloads per month
Used in zip_static_handler

Apache-2.0

1MB
26K SLoC

A composable Rust async web framework

Features

  • 100% safe Rust
  • composable API that satisfy high level usage and can be easily extended to and/or mixed with mid/low level
  • static typed with minimal usage of runtime dynamic type casting
  • minimal dependency tree and fast compile time

lib.rs:

a web framework focus on memory efficiency, composability, and fast compile time.

Quick start

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()
}

Memory efficient

  • zero copy magic types
  • zero cost service tree

Zero copy

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()

Zero cost

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
}

Composable

  • Easy mixture of various level of abstractions and less opinionated APIs
  • Common types and traits for cross crates integration of majority rust web ecosystem

Abstraction variety

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"    
}

Middleware composability

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
}

For more detailed middleware documentation please reference [middleware]

Cross crates integration

use tower-http inside xitca-web application.

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()

Fast compile time

  • additive proc macro
  • light weight dependency tree

opt-in proc macro

in xitca-web proc macro is opt-in. This result in a fast compile time with zero public proc macro. That said you still can enable macros for a higher level style of 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!"
}

macro code generation module. http types route services.

Examples:

// 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);

Dependencies

~3–18MB
~196K SLoC