#hyper #rpc #hyper-http #protobuf #http #async #http-post

prost-twirp

使用prost和hyper在Rust中调用/提供Twirp服务的代码生成器和库

2个不稳定版本

0.2.0 2023年1月3日
0.1.0 2018年2月13日

#4#http-post

Download history 106/week @ 2024-03-14 27/week @ 2024-03-21 15/week @ 2024-03-28 5/week @ 2024-04-04

每月下载 73次

MIT 许可证

41KB
608

Prost Twirp

Prost Twirp是一个代码生成器和一组工具,用于在Rust中使用prost和hyper调用和提供Twirp服务。

Twirp是一个简单的跨语言框架/协议,用于RPC,服务由Protobuf定义并通过HTTP POST传输。

请参阅下面的使用说明、API文档示例

使用方法

Prost Twirp支持调用和提供Twirp服务。Prost Twirp可以使用以下三种方式之一,以下各节将解释每种方式。

由于动态生成的代码与复杂的Hyper类型的交互,理解API的最佳方式是阅读并实验examples/中的代码,特别是examples/service-gen,这是最简单的。

  • 作为客户端和/或服务器代码生成器以及支持运行时库。这是一种最简单的方法,强烈推荐。
  • 作为一组实用程序库,帮助进行更多手动Twirp客户端/服务器调用。

代码生成

请参阅examples/service-gen以获取完整的工作示例。

大部分代码生成依赖于prostprost代码生成器接受一个prost_build::ServiceGenerator。Prost Twirp提供此生成器。

本指南将使用Twirp的example service.proto,它也用于Prost Twirp的示例。

根据prost-build文档设置项目以生成代码。此外,将以下内容添加到Cargo.toml的依赖关系和构建依赖关系中

[dependencies]
bytes = "1.2"
futures = "0.3"
prost = "0.11"
prost-derive = "0.11"
prost-twirp = "0.2"

[dependencies.hyper]
version = "0.14"
features = ["client", "server", "http1", "http2", "tcp"]

[dependencies.tokio]
version = "1.2"
features = ["macros", "net", "rt", "rt-multi-thread", "sync", "time"]

[build-dependencies]
prost-build = "0.11"
prost-twirp = { features = ["service-gen"] }

这将在运行时添加支持的Prost Twirp库和在构建时添加服务生成支持。它还添加了在运行时使用服务所需的hyperfuturestokio。之前,在build.rs中的构建脚本代码可能如下所示:

fn main() {
    prost_build::compile_protos(&["src/service.proto"], &["src/"]).unwrap();
}

这将仅生成protobuf结构体,但不生成服务。现在将其更改为利用Prost Twirp服务生成器

fn main() {
    let mut conf = prost_build::Config::new();
    conf.service_generator(Box::new(prost_twirp::TwirpServiceGenerator::new()));
    conf.compile_protos(&["src/service.proto"], &["src/"]).unwrap();
}

现在包含的文件包含一个服务客户端和服务器。如prost-build文档中所述,它可以在main.rs中包含

mod service {
    include!(concat!(env!("OUT_DIR"), "/twitch.twirp.example.rs"));
}

生成的Trait

每个protobuf服务映射到一个单一代码生成的Rust特质,其中一个方法对应于proto服务接口中的每个方法。

这个特质由一个自动生成的客户端存根类型实现,它将方法调用转换为对远程Twirp服务器的HTTP请求。

如果你编写了一个服务器,那么你的服务器也将提供相同服务特质的实现,当调用方法时将执行方法的业务逻辑:例如,制作一顶帽子。

示例服务.proto包含以下服务

// A Haberdasher makes hats for clients.
service Haberdasher {
  // MakeHat produces a hat of mysterious, randomly-selected color!
  rpc MakeHat(Size) returns (Hat);
}

这将在target/..../twitch.twirp.example.rs中生成以下特质

pub trait Haberdasher: Send + Sync + 'static {
    /// MakeHat produces a hat of mysterious, randomly-selected color!
    fn make_hat(
        &self,
        request: ::prost_twirp::ServiceRequest<Size>,
    ) -> ::prost_twirp::PTRes<Hat>;
}

impl dyn Haberdasher {
    pub fn new_client(
        client: ::hyper::Client<::hyper::client::HttpConnector, ::hyper::Body>,
        root_url: &str,
    ) -> Box<dyn Haberdasher> {
        /* ... */
    }

    pub fn new_server<T: Haberdasher>(
        v: T,
    ) -> Box<
        dyn (::hyper::service::Service<
            ::hyper::Request<::hyper::Body>,
            Response = ::hyper::Response<::hyper::Body>,
            Error = ::hyper::Error,
            Future = ::std::pin::Pin<
                Box<
                    dyn (::futures::Future<
                        Output = Result<::hyper::Response<::hyper::Body>, ::hyper::Error>,
                    >) + Send,
                >,
            >,
        >) + Send + Sync,
    > {
        /* ... */
    }
}

[PTRes]是boxed未来服务响应,由客户端和服务器双方使用。

使用客户端

创建Prost Twirp客户端是在创建hyper::Client之后的额外步骤。只需调用生成的服务特质中的new_client静态方法,传入hyper客户端和根URL即可

let hyper_client = Client::new();
let service_client =
    <dyn service::Haberdasher>::new_client(hyper_client, "https://127.0.0.1:8080");

这创建并返回一个Haberdasher特质的boxed实现。然后可以这样调用

let res = service_client
    .make_hat(service::Size { inches: 12 }.into())
    .await
    .unwrap();
println!("Made {:?}", res.output);

注意into,它将一个prost protobuf对象转换为Prost Twirp[ServiceRequest]。结果是包含序列化结果的[ServiceResponse]boxed未来,其output字段将包含序列化结果(在这种情况下,service::Hat)。

调用过程中可能发生的任何错误将导致包含[ProstTwirpError]错误的已错误未来。

使用服务器

客户端使用的相同特质也必须作为服务器实现。以下是一个示例实现

pub struct HaberdasherService;
impl service::Haberdasher for HaberdasherService {
    fn make_hat(
        &self,
        req: service::ServiceRequest<service::Size>,
    ) -> service::PTRes<service::Hat> {
        Box::pin(future::ok(
            service::Hat {
                size: req.input.inches,
                color: "blue".to_string(),
                name: "fedora".to_string(),
            }
            .into(),
        ))
    }
}

像其他Hyper服务一样,这返回一个boxed未来,包含protobuf值。在这种情况下,它每次都生成一个Hat实例。

要启动服务,生成的特质有一个接受特质实现的new_server方法,并返回一个::hyper::service::Service

let addr = "0.0.0.0:8080".parse().unwrap();
let server = Server::bind(&addr)
    .serve(make_service_fn(|_conn| async {
        Ok::<_, Infallible>(<dyn service::Haberdasher>::new_server(HaberdasherService))
    }));
server.await.unwrap();

注意,由于一些tokio服务限制,服务实现必须具有'static生命周期。

返回错误

可以返回以[ProstTwirpError]形式存在的错误。可以发送回与Twirp序列化错误格式更直接对应的[TwirpError]。

(请参阅examples/errors以获取完整的示例。)

以下是一个示例,说明不接受某些边界之外的任何大小

pub struct HaberdasherService;
impl service::Haberdasher for HaberdasherService {
    fn make_hat(&self, i: service::ServiceRequest<service::Size>) -> service::PTRes<service::Hat> {
        Box::pin(if i.input.inches < 1 {
            future::err(
                TwirpError::new_meta(
                    StatusCode::BAD_REQUEST,
                    "too_small",
                    "Size too small",
                    serde_json::to_value(MinMaxSize { min: 1, max: 10 }).ok(),
                )
                .into(),
            )
        } else if i.input.inches > 10 {
            future::err(
                TwirpError::new_meta(
                    StatusCode::BAD_REQUEST,
                    "too_large",
                    "Size too large",
                    serde_json::to_value(MinMaxSize { min: 1, max: 10 }).ok(),
                )
                .into(),
            )
        } else {
            future::ok(
                service::Hat {
                    size: i.input.inches,
                    color: "blue".to_string(),
                    name: "fedora".to_string(),
                }
                .into(),
            )
        })
    }
}

可以将以serde_json::Value形式存在的元数据提供给[TwirpError]。

手动客户端和服务器

Prost Twirp的一些功能可以使用手动方式,而不是代码生成。请参阅examples/no-service-gen。在此模式下,应用程序代码负责URL路由和确定正确的请求和响应类型,而prost_twirp将反序列化和序列化请求和响应。

对于客户端,可以使用根URL和hyper客户端创建一个新的[HyperClient]。然后,对于由prost构建的消息,可以使用路径和[ServiceRequest]调用go。响应是一个包含[ServiceResponse]的boxed future,它必须使用预期的prost构建的输出类型进行类型化。示例

常见问题解答

为什么没有JSON支持?

这可能会很快实现,可能使用pbjson

为什么我的服务器服务实现必须为'static

这是由于需要在静态future中引用服务。请参阅此问题。欢迎任何更好的解决方案。

支持哪种Twirp格式?

此crate当前实现Twirp 5。可以添加Twirp 7。

依赖关系

~8–23MB
~300K SLoC