V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
JerrySu5379
V2EX  ›  分享创造

Hotaru:一个理念构建的 Rust Web 框架 — 端点的一切都应该在一起

  •  
  •   JerrySu5379 · 1 月 6 日 · 1069 次点击
    这是一个创建于 96 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Hotaru:一个理念构建的 Rust Web 框架 — 端点的一切都应该在一起

    Hotaru 是一个采用宏语法的 Rust Web 框架,将 URL 、中间件、配置和处理器整合在一个代码块中。如果你正在用 Rust 构建 Web 服务,并且觉得属性宏分散各处不够优雅,这个框架可能适合你。希望获得反馈:endpoint!/middleware! 语法是否直观,还是隐藏了太多细节?

    仓库地址: https://github.com/Field-of-Dreams-Studio/hotaru

    文档: https://fds.rs/hotaru/tutorial/0.7.3/


    为什么我们要构建 Hotaru

    我是从 Python 转过来的,之前用 Flask 、FastAPI 那一套。当我转向 Rust 时,安全性的承诺打动了我——内存安全、没有空指针异常、编译器在运行前就能捕获 bug 。这些确实兑现了。

    但当我开始研究 Web 框架时,发现了一个让我不太满意的模式:

    #[get("/users/<id>")]
    #[middleware::auth]
    #[middleware::rate_limit(100)]
    async fn get_user(...) -> impl Responder {
    

    属性宏这种方式确实可行,很多人用它来交付生产环境的应用。但对我个人而言,配置分散在函数上方的感觉和我之前想要摆脱的装饰器模式很像。而且你还需要在别的地方手动注册路由。我想要的是能在一个地方看到端点的所有信息,并且自动完成注册。

    所以我们尝试了一种不同的方式。


    Hotaru 的设计理念

    我们围绕一个核心理念构建了 Hotaru:端点的所有信息都应该在一起,并且自动注册。URL 、中间件、配置、处理器——一个代码块,一目了然,定义即注册。

    基础语法

    endpoint! {
        APP.url("/users/<int:id>"),
        middleware = [.., auth_check, rate_limit],
        config = [HttpSafety::new().with_allowed_methods(vec![GET, POST])],
    
        pub get_user <HTTP> {
            let user_id = req.param("id").unwrap_or_default();
            json_response(object!({
                id: user_id,
                message: "User endpoint"
            }))
        }
    }
    

    这就是完整的语法。URL 模式(支持类型化参数如 <int:id>)、中间件栈、安全配置和处理器主体——全部在一个地方。req.param("id") 返回的值可以调用 .unwrap_or_default().string() 获取原始字符串。无需单独的注册步骤。宏在编译时展开为标准的异步 Rust 代码。

    新语法试验中

    注意: 我们正在测试一种新的语法风格,可以使用更接近 Rust 函数的写法并自定义请求变量名。这个功能还在试验阶段,欢迎反馈。

    endpoint! {
        APP.url("/new_syntax/<arg>"),
        middleware: [..],
        config: ["ConfigString"],
    
        pub fn new_syntax_endpoint(ctx: HTTP) {
            let arg = ctx.pattern("arg").unwrap_or_default();
            text_response(format!("New syntax endpoint called with arg: {}", arg))
        }
    }
    

    两种写法在底层编译为相同的代码。我们想知道:同时支持两种风格是提高了清晰度,还是引入了不必要的不一致性?


    核心特性

    1. 自动注册

    定义端点的同时自动注册。不需要手动 router.register(),不会忘记注册,也不用到处找路由在哪里组装的。这是大多数 Rust Web 框架缺少的功能。

    2. 中间件定义

    在大多数框架中定义中间件需要实现 trait 、包装服务、处理返回 future 的 future 。相当繁琐。

    在 Hotaru 中:

    middleware! {
        pub LogRequest <HTTP> {
            println!("[LOG] {} {}", req.method(), req.path());
            let start = std::time::Instant::now();
    
            let result = next(req).await;
    
            println!("[LOG] Completed in {:?}", start.elapsed());
            result
        }
    }
    

    就这样。你拿到 req(即 HttpContext),调用 next(req).await 继续执行链,可以在返回时修改结果。想要短路?不调用 next() 就行:

    middleware! {
        pub AuthCheck <HTTP> {
            let token = req.headers().get("Authorization");
    
            if token.is_none() {
                req.response = json_response(object!({
                    error: "unauthorized"
                })).status(StatusCode::UNAUTHORIZED);
                return req;
            }
    
            // 通过 locals 向下游传递类型化数据
            req.locals.set("user_id", "user-123".to_string());
            next(req).await
        }
    }
    

    新的 fn 风格也支持中间件(试验中):

    middleware! {
        pub fn Logger(req: HTTP) {
            println!("[LOG] {} {}", req.method(), req.path());
            next(req).await
        }
    }
    

    3. .. 模式

    这里我们借鉴了 Rust 结构体更新语法的设计。在大多数框架中,中间件组合要么全有要么全无,要么需要在构建器链中仔细排序。

    // 应用级全局中间件
    pub static APP: SApp = Lazy::new(|| {
        App::new()
            .binding("127.0.0.1:3000")
            .append_middleware::<Logger>()
            .append_middleware::<Metrics>()
            .build()
    });
    
    // 仅使用全局中间件
    endpoint! {
        APP.url("/health"),
        middleware = [..],
        pub health <HTTP> { text_response("ok") }
    }
    
    // 全局 + 认证
    endpoint! {
        APP.url("/api/users"),
        middleware = [.., auth_required],
        pub users <HTTP> { /* ... */ }
    }
    
    // 三明治结构:timing 先执行,然后是全局中间件,最后是缓存检查
    endpoint! {
        APP.url("/api/cached"),
        middleware = [timing, .., cache_layer],
        pub cached <HTTP> { /* ... */ }
    }
    
    // 完全跳过全局中间件
    endpoint! {
        APP.url("/raw"),
        middleware = [custom_only],
        pub raw <HTTP> { /* ... */ }
    }
    

    .. 会展开为你的全局中间件。你可以在它前面、后面添加内容,或者完全跳过。只需查看端点定义,就能准确知道每个路由会执行哪些中间件。

    4. 单端口多协议

    这最初是一个实验,后来成为了架构的核心。Hotaru 可以在同一个端口上提供 HTTP 、WebSocket 和自定义 TCP 协议服务。

    (我们实际上正在将这部分封装成更简洁的宏——新语法即将推出)

    pub static APP: SApp = Lazy::new(|| {
        App::new()
            .binding("127.0.0.1:3000")
            .handle(
                HandlerBuilder::new()
                    .protocol(ProtocolBuilder::new(HTTP::server(HttpSafety::default())))
                    .protocol(ProtocolBuilder::new(WebSocketProtocol::new()))
                    .protocol(ProtocolBuilder::new(CustomProtocol::new()))
            )
            .build()
    });
    

    框架会检查传入的字节并路由到正确的处理器。REST API 、WebSocket 、自定义二进制协议——同一端口,共享状态。

    无论什么协议,处理器看起来都一样:

    endpoint! {
        APP.url("/chat"),
        pub chat_http <HTTP> {
            html_response(include_str!("chat.html"))
        }
    }
    
    endpoint! {
        APP.url("/chat"),
        pub chat_ws <WebSocket> {
            // 相同 URL ,不同协议
            ws.on_message(|msg| { /* ... */ }).await
        }
    }
    

    5. Akari:我们的辅助 Crate

    我们提供了一个轻量级工具 crate 叫 Akari ,用于处理 JSON 和模板,无需引入 serde 。object! 宏可以内联构建 JSON:

    json_response(object!({
        status: "success",
        data: {
            users: [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}],
            total: 2
        }
    }))
    

    不需要派生宏,不需要为临时响应定义结构体。如果你想用 serde 也可以——不是禁止使用,只是不强制要求。


    早期性能数据

    我们进行了一些初步基准测试,以验证宏方法不会增加运行时开销。这些是单机上的早期数据——仅供参考方向,不作为定论。

    框架 请求数/秒 (JSON) 相对性能
    Hotaru 173,254 100%
    Rocket 171,904 99.2%
    Actix-web 149,244 86.1%
    Axum 148,934 86.0%

    在 Apple M 系列芯片上测试,单线程,简单 JSON 响应。我们将很快发布完整的测试方法和代码。

    宏展开后就是你手写的普通异步函数。编译器看到的是展开后的普通 Rust 代码,并相应地进行优化。


    设计理念:为什么用宏?

    传统属性宏的问题

    在大多数 Rust Web 框架中,端点配置是这样的:

    #[get("/users/<id>")]
    #[middleware::auth]
    #[middleware::rate_limit(100)]
    async fn get_user(...) -> impl Responder {
        // 处理器代码
    }
    
    // 然后在别的地方,你还需要注册它:
    // router.register(get_user)
    

    配置分散在函数上方。注册在别的地方。要理解一个端点做什么——以及它是否真的生效——你需要检查多个地方。

    我们的方法

    一个代码块。一个地方。URL 、中间件、配置、处理器——以及自动注册

    核心特性

    1. 自动注册:定义端点的同时就注册了。不需要手动 router.register(),不会忘记注册,不用到处找路由在哪里组装的。这是大多数 Rust Web 框架缺少的功能。

    2. 一眼看清:端点的所有信息都在一个地方可见。

    3. 中间件组合:借鉴 Rust 的结构体更新语法——准确看到每个路由运行哪些中间件。

    4. 简化的中间件定义:没有 trait 样板代码。

    5. 多协议支持:相同 URL ,不同协议——语法一致。

    权衡与路线图

    我们承认当前的局限性:

    局限性 状态
    自定义语法有学习曲线 编译为标准异步 Rust
    rustfmt 支持有限 计划中:自定义格式化工具
    IDE 支持参差不齐 已针对 rust-analyzer 优化

    我们正在构建一个完整的工具链——包括我们自己的格式化工具和增强的 IDE 支持。Hotaru 正在从一个框架演变为一个带有完整工具的 Web 端点 DSL 。

    编译时保证

    所有宏都在编译时展开。零运行时开销——编译器在展开后看到的是普通 Rust 。


    当前状态

    我们目前是 v0.7 版本,还在起步阶段。API 正在趋于稳定但还没有冻结。文档在改进中但仍有缺口。与成熟框架相比,生态系统还很小。

    关于测试: 由于我们的数据库实现仍然非常抽象,目前的基准测试只能覆盖基础的 HTTP 响应场景。我们正在完善数据库层的设计,之后会进行更全面的测试。

    我们的定位是一个对语法有独特见解的框架。我们认为处理器代码应该读起来就像它做的事情一样直观。


    试试看

    use hotaru::prelude::*;
    use hotaru::http::*;
    
    pub static APP: SApp = Lazy::new(|| {
        App::new().binding("127.0.0.1:3000").build()
    });
    
    endpoint! {
        APP.url("/"),
        pub index <HTTP> {
            text_response("Hello from Hotaru")
        }
    }
    
    #[tokio::main]
    async fn main() {
        APP.clone().run().await;
    }
    

    仓库地址: https://github.com/Field-of-Dreams-Studio/hotaru

    文档: https://fds.rs/hotaru/tutorial/0.7.3/


    我们想听听你的看法

    • endpoint!/middleware! 语法感觉直观吗,还是隐藏了太多东西?
    • 新的 fn 风格语法(试验中)有帮助吗,还是引入了不必要的复杂性?
    • .. 中间件模式巧妙还是令人困惑?

    我们押注的是:用一点宏魔法换取更少的样板代码和自动注册是值得的。


    贡献

    我们是一个小团队,正在构建一些有野心的东西。如果你对以下方面感兴趣:

    • 语言工具(格式化工具、分析器)
    • 宏系统和 DSL 设计
    • Web 框架内部
    • 或者只是想和我们讨论语法设计

    我们很乐意听到你的声音。访问我们的 GitHub 或者提 issue 开始对话。

    目前尚无回复
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   1077 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 18:35 · PVG 02:35 · LAX 11:35 · JFK 14:35
    ♥ Do have faith in what you're doing.