#axum #rpc #protobuf #connect

axum-connect-build

为 axum-connect 连接 Web RPC 代码生成器

10 个版本

0.3.2 2024 年 1 月 11 日
0.3.1 2024 年 1 月 8 日
0.3.0 2023 年 12 月 28 日
0.2.0 2023 年 10 月 4 日
0.1.1 2023 年 3 月 3 日

#1414HTTP 服务器

Download history 76/week @ 2024-03-10 58/week @ 2024-03-17 40/week @ 2024-03-24 37/week @ 2024-03-31 31/week @ 2024-04-07 3/week @ 2024-04-14 28/week @ 2024-04-21 27/week @ 2024-04-28 26/week @ 2024-05-12 34/week @ 2024-05-19 34/week @ 2024-05-26 48/week @ 2024-06-02 9/week @ 2024-06-09 51/week @ 2024-06-16 111/week @ 2024-06-23

每月 221 次下载

MIT/Apache

21KB
210

Axum Connect-Web

通过 Rust 的惯用方法将基于 protobuf 的 Connect-Web RPC 框架 带入 Axum。

Axum 版本

  • axum-connect:0.3axum:0.7 兼容
  • axum-connect:0.2axum:0.6 兼容

特性 🔍

  • 无缝集成到现有的 Axum HTTP 应用程序中
  • 紧密映射 Axum 的 API
    • 提取状态和其他 impl RpcFromRequestPartsparts,就像使用 Axum 一样。
    • 返回任何 impl RpcIntoResponse 的类型,就像 Axum 一样。
  • 生成的类型和服务处理器是强类型的,...
  • 处理器强制执行语义上正确的 HTTP 'parts' 访问。
  • 允许用户像 Axum 一样推导 RpcIntoResponseRpcFromRequestParts
    • 注意:这些必须是 Axum 类型的派生,因为它们更严格;你不再处理任意的 HTTP,你正在通过 HTTP 传输 connect-web RPC。
  • 在惯用的 Axum/Rust 中包装 connect-web 错误处理。
  • 从单独的 crate 中的 *.proto 文件中进行代码生成。
  • 所有其他 Axum 的美妙好处,如社区、文档和性能!

注意 ⚠️

在生产环境中,我们使用axum-connect,但不知道有谁会用得更明智。它是用Rust编写的,显然提供了一些令人惊叹的编译器保证,但还没有经过良好的测试或实战检验。对这条信息随心所欲吧。

请告诉我你是否在使用axum-connect!如果你发现任何错误,请创建问题。

入门 🤓

假定您对Protobuf(包括IDL及其在RPC框架中的应用)和Axum有先前的了解。

依赖关系 👀

您需要2个axum-connect包,一个用于代码生成,一个用于运行时使用。由于prost的工作方式,您还需要将其添加到自己的项目中。显然,您还需要axumtokio

# Note: axum-connect-build will fetch `protoc` for you.
cargo add --build axum-connect-build
cargo add axum-connect prost axum
cargo add tokio --features full

Protobuf文件 🥱

首先创建必要的“hello world”proto服务定义。

proto/hello.proto

syntax = "proto3";

package hello;

message HelloRequest { string name = 1; }

message HelloResponse { string message = 1; }

service HelloWorldService {
  rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

代码生成 🤔

使用axum_connect_codegen包从proto IDL生成Rust代码。

目前所有代码生成都是在本地磁盘上的proto文件上完成的,并使用一个build.rs文件。有一天我希望支持更多像远程代码生成这样的Buf约定。

build.rs

use axum_connect_build::{axum_connect_codegen, AxumConnectGenSettings};

fn main() {
    // This helper will use `proto` as the import path, and globs all .proto
    // files in the `proto` directory.
    //
    // Note that you might need to re-save the `build.rs` file after updating
    // a proto file to get rust-analyzer to pickup the change. I haven't put
    // time into looking for a fix to that yet.
    let settings = AxumConnectGenSettings::from_directory_recursive("proto")
        .expect("failed to glob proto files");

    axum_connect_codegen(settings).unwrap();
}

有趣的部分 😁

无聊的部分处理完毕后,让我们使用Axum实现我们的服务!

use async_stream::stream;
use axum::{extract::Host, Router};
use axum_connect::{futures::Stream, prelude::*};
use proto::hello::*;

mod proto {
    pub mod hello {
        include!(concat!(env!("OUT_DIR"), "/hello.rs"));
    }
}

#[tokio::main]
async fn main() {
    // Build our application with a route. Note the `rpc` method which was added by `axum-connect`.
    // It expect a service method handler, wrapped in it's respective type. The handler (below) is
    // just a normal Rust function. Just like Axum, it also supports extractors!
    let app = Router::new()
        .rpc(HelloWorldService::say_hello(say_hello_success))
        .rpc(HelloWorldService::say_hello_stream(say_hello_stream));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3030")
        .await
        .unwrap();
    println!("listening on http://{:?}", listener.local_addr());
    axum::serve(listener, app).await.unwrap();
}

async fn say_hello_success(
  Host(host): Host,
  request: HelloRequest
) -> HelloResponse {
    HelloResponse {
        message: format!(
            "Hello {}! You're addressing the hostname: {}.",
            request.name, host
        ),
    }
}

发送它 🚀

为了测试它,尝试手动访问端点。

curl --location 'http://127.0.0.1:3030/hello.HelloWorldService/SayHello' \
--header 'Content-Type: application/json' \
--data '{ "name": "Alec" }'

从这里,您可以启动一个connect-web TypeScript/Go项目,使用端到端的类型化RPC调用您的API。

请求/响应部分 🙍‍♂️

请求和响应类型都是在axum-connect中派生的。一开始这可能看起来有些冗余。

让我们先谈谈简单的部分,RpcIntoResponse。连接RPC不是任意的HTML请求,也不能返回任意的HTML响应。例如,流响应必须返回HTTP 200状态码,无论错误状态如何。遵循Axum的(非常棒的)范式,这是通过类型系统强制执行的。RPC处理器不能返回任意的HTML,而必须返回axum-connect知道如何将其转换为有效连接响应的内容。

不那么直观的是,axum-connect派生了RpcFromRequestParts,它与Axum的FromRequestParts几乎相同。重要的是,FromRequestParts可以返回任意HTML响应的错误,这同样是一个问题。

Axum还允许FromRequest在处理器中占用最后一个参数,该处理器消耗了整个HTTP请求(包括正文)。由于axum-connect需要自己处理请求输入,因此没有为RPC处理器提供等效的选项。

路线图/未实现的目标 🛣️

  • 探索比RpcFromRequestParts更好的类型
    • 理想情况下,客户端只需提供一个RpcIntoError,但我还没有完全想通这个问题。我只知道,对于自定义类型,需要同时指定FromRequestPartsRpcFromRequestParts是一个很烦人的事情。
  • 重新设计错误响应以允许多个错误
  • 生成代码和运行时代码之间的版本检查
  • 向前兼容性的计划
  • 使一切与connect-web保持一致,并...
  • 全面的集成测试

更长远的目标 🌜

  • 我还希望能支持一个WASM兼容的客户库
  • 使用buf.build来支持远程代码生成和简化的proto处理
  • 支持gRPC调用
    • 我认为这不难,只是我没有个人用例
  • 可能也许有一天会支持通过WebRTC的双向流
    • 这需要 connect-web 支持相同的功能
    • WebRTC 流,因为它们是 DTLS/SRTP 并且具有弹性
  • 用自定义的简单方法替换 Prost

非目标 🙅

  • 支持 gRPC 的所有功能
    • 您已经在 Axum 中得到了很多这样的功能,但 gRPC 是一个怪物,我不希望复制。这种复杂性对 Google 有用,但对几乎所有其他人来说都是障碍。
  • 做一切事情并与一切集成
    • 我计划使 axum-connect 非常专注。擅长它所做的事情,其他什么都不做。
    • 这是 Rust 的惯例。做好一件事情,其余的留给其他 crate 处理。

Prost 和 Protobuf 📖

Protoc 版本

如果您需要或希望这样做,可以配置已安装的 protoc 版本为 None,这将完全禁用下载。

原因

Prost 停止提供 protoc 二进制文件(这是一个我不同意的决定),因此 axum-connect-build 内部使用 protoc-fetcher 来下载和解析 protoc 的副本。这比强制每个构建环境(通常是 Heroku 和/或 Docker)预先安装最新的 protoc 二进制文件要方便得多。如果不同意,或者需要遵守公司政策,或者构建环境离线,可以禁用此行为。

我希望有一天能用一个新的“轻量级”protoc 库来替换这一切,这个库具有内置解析器,仅支持最新的 proto3 语法以及标准 JSON 序列化格式,并且明确不支持许多很少使用的功能。但那一天不是今天。

许可证 🧾

Axum-Connect 是双许可(由您选择)

依赖项

~12–30MB
~450K SLoC