< 返回版块

dakai-chen 发表于 2025-02-25 20:07

项目地址

https://github.com/dakai-chen/boluo

介绍

boluo 是一个基于 tokiohyper 开发的轻量级路由层,几乎没有额外的性能开销,拥有极快的运行速度。

特点

  • 简单清晰的路由定义,支持嵌套路由、宏定义路由和路由合并。
  • 提供核心 Trait ServiceMiddleware,灵活且易于扩展。
  • 提供统一的错误处理机制,简化了错误处理逻辑。

可选功能

功能名 描述 默认启用
http1 启用HTTP1服务器
http2 启用HTTP2服务器
multipart 添加对multipart/form-data格式的支持
sse 添加对服务器发送事件的支持
ws 添加对网络套接字的支持
static-file 添加对静态文件的支持

快速开始

新建项目:

cargo new demo && cd demo

添加依赖:

[dependencies]
boluo = "0.6"
tokio = { version = "1", features = ["full"] }

用以下内容覆盖src/main.rs

use boluo::response::IntoResponse;
use boluo::route::Router;
use boluo::server::Server;
use tokio::net::TcpListener;

#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();

    let app = Router::new().mount(hello);

    Server::new(listener).run(app).await.unwrap();
}

#[boluo::route("/", method = "GET")]
async fn hello() -> impl IntoResponse {
    "Hello, World!"
}

运行项目:

cargo run

访问服务:

curl http://127.0.0.1:3000/

更多示例

在这里可以找到更多的示例代码。在示例目录中,你可以通过以下命令运行示例:

cargo run --bin hello

支持的最低Rust版本(MSRV)

支持的最低Rust版本为1.85.0

Demo

use std::convert::Infallible;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;

use boluo::BoxError;
use boluo::data::Extension;
use boluo::extract::Path;
use boluo::handler::handler_fn;
use boluo::http::StatusCode;
use boluo::middleware::simple_middleware_fn;
use boluo::request::Request;
use boluo::response::{IntoResponse, Response};
use boluo::route::{RouteError, RouteErrorKind, Router, get};
use boluo::server::Server;
use boluo::service::{Service, ServiceExt};
use boluo::ws::WebSocketUpgrade;
use tokio::net::TcpListener;

#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();

    // 构建路由
    let app = Router::new()
        .route("/", get(handler_fn(async || "Welcome to Boluo")))
        .mount(ws)
        .mount(calc)
        .mount(count)
        .or_else(handler_app_error)
        .or_else(handler_any_error)
        .with(simple_middleware_fn(log))
        .with(Extension(Arc::new(AtomicU32::new(0))));

    // 关机信号
    let shutdown = async {
        let _ = tokio::signal::ctrl_c().await;
        Some(Duration::from_secs(30)) // 收到关机信号,30秒后强制关闭服务器
    };

    Server::new(listener)
        .run_with_graceful_shutdown(app, shutdown)
        .await
        .unwrap();
}

// -----------------------------------------------------------------------------
// 处理程序
// -----------------------------------------------------------------------------

/// WebSocket,将接收到的消息原样返回
#[boluo::route("/ws", method = "GET")]
async fn ws(upgrade: WebSocketUpgrade) -> impl IntoResponse {
    upgrade
        .on_upgrade_error(|err| println!("websocket upgrade error: {err}"))
        .on_upgrade(async |mut socket| {
            while let Some(Ok(message)) = socket.recv().await {
                let _ = socket.send(message).await;
            }
        })
}

/// 四则运算
#[boluo::route("/calc/{op}/{a}/{b}", method = "GET")]
async fn calc(Path((op, a, b)): Path<(String, f64, f64)>) -> Result<impl IntoResponse, AppError> {
    match op.as_str() {
        "add" => Ok(format!("{} + {} = {}", a, b, a + b)),
        "sub" => Ok(format!("{} - {} = {}", a, b, a - b)),
        "mul" => Ok(format!("{} * {} = {}", a, b, a * b)),
        "div" => Ok(format!("{} / {} = {}", a, b, a / b)),
        _ => Err(AppError::CalcOperationNotSupported),
    }
}

/// 计数
#[boluo::route("/count", method = "GET")]
async fn count(Extension(cnt): Extension<Arc<AtomicU32>>) -> impl IntoResponse {
    format!("{}", cnt.fetch_add(1, Ordering::Relaxed))
}

// -----------------------------------------------------------------------------
// 中间件
// -----------------------------------------------------------------------------

/// 一个简单的日志中间件
async fn log<S>(req: Request, service: &S) -> Result<S::Response, S::Error>
where
    S: Service<Request, Response = Response>,
    S::Error: std::fmt::Display,
{
    println!("<< {:?} {} {}", req.version(), req.method(), req.uri());
    let result = service.call(req).await;
    match &result {
        Ok(response) => println!(":: {}", response.status()),
        Err(error) => println!("!! {error}"),
    }
    result
}

// -----------------------------------------------------------------------------
// 错误处理
// -----------------------------------------------------------------------------

/// 自定义错误
#[derive(Debug, thiserror::Error)]
enum AppError {
    #[error("Operation not supported")]
    CalcOperationNotSupported,
}

/// 处理任何错误
async fn handler_any_error(error: BoxError) -> Result<Response, Infallible> {
    if let Some(e) = error.downcast_ref::<RouteError>() {
        let status_code = match e.kind() {
            RouteErrorKind::NotFound => StatusCode::NOT_FOUND,
            RouteErrorKind::MethodNotAllowed => StatusCode::METHOD_NOT_ALLOWED,
        };
        return status_code.into_response();
    }
    StatusCode::INTERNAL_SERVER_ERROR.into_response()
}

/// 针对 AppError 类型的错误处理
async fn handler_app_error(error: BoxError) -> Result<Response, BoxError> {
    let Some(e) = error.downcast_ref::<AppError>() else {
        return Err(error);
    };
    match e {
        AppError::CalcOperationNotSupported => {
            (StatusCode::BAD_REQUEST, format!("{e}")).into_response()
        }
    }
}

评论区

写评论
miaomiao1992 2025-02-27 12:12

不错不错,总代码行数也就几万行,适合学习阅读

设计思路类似axum,有中文注释,大拇指!

作者 dakai-chen 2025-02-26 10:02

是的,就是菠萝

--
👇
Mike Tang: 菠萝?

Mike Tang 2025-02-26 06:23

菠萝?

1 共 3 条评论, 1 页