2个不稳定版本
0.2.0 | 2023年1月3日 |
---|---|
0.1.0 | 2018年2月13日 |
#4 在 #http-post
每月下载 73次
41KB
608 行
Prost Twirp
Prost Twirp是一个代码生成器和一组工具,用于在Rust中使用prost和hyper调用和提供Twirp服务。
Twirp是一个简单的跨语言框架/协议,用于RPC,服务由Protobuf定义并通过HTTP POST传输。
使用方法
Prost Twirp支持调用和提供Twirp服务。Prost Twirp可以使用以下三种方式之一,以下各节将解释每种方式。
由于动态生成的代码与复杂的Hyper类型的交互,理解API的最佳方式是阅读并实验examples/
中的代码,特别是examples/service-gen
,这是最简单的。
- 作为客户端和/或服务器代码生成器以及支持运行时库。这是一种最简单的方法,强烈推荐。
- 作为一组实用程序库,帮助进行更多手动Twirp客户端/服务器调用。
代码生成
请参阅examples/service-gen
以获取完整的工作示例。
大部分代码生成依赖于prost。prost
代码生成器接受一个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库和在构建时添加服务生成支持。它还添加了在运行时使用服务所需的hyper、futures和tokio。之前,在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