【每周一库】- Tonic 基于Rust的gRPC实现
Tonic
gRPC的rust实现,高性能,开源,为移动设备与HTTP/2准备的通用RPC框架
tonic
是基于HTTP/2的gRPC实现,专注于高性能,互通性和灵活性。创建该库的目的是为了对async/await具有一流的支持,并充当用Rust编写的生产系统的核心构建块。
特性
- 双向流传输
- 高性能异步io
- 互通性
- 通过
rustls
进行TLS加密支持 - 负载均衡
- 自定义元数据
- 身份认证
- 健康检查
入门
本教程作为Tonic的入门指导,前提是你对Rust有基础了解并且使用过protocol buffers。
先决条件
运行此教程中的代码,唯一需要安装的是Rust,如果你还没有安装rustup
,可以尝试使用它快速方便的安装Rust.
项目设置
首先,我们需要使用Cargo创建一个新的Rust项目:
$ cargo new helloworld-tonic
$ cd helloworld-tonic
tonic
需要rust1.39
及以上版本,因为它需要async_await
特性的支持。
$ rustup update
$ rustup component add rustfmt
定义HelloWorld服务
我们第一步需要做的是使用protocol buffers定义gRPC服务以及请求和相应的类型。我们将这个定义文件.proto
放进crate根目录下的一个子目录中。需要注意的是,Tonic对.proto
定义文件的位置并没有严格的要求。
$ mkdir proto
$ touch proto/helloworld.proto
然后你需要在服务定义中定义RPC方法,并且指定它们的请求与相应类型。gRPC允许你定义4种不同的服务方式,Tonic都可支持。在这个入门指导中,我们只使用简易的RPC
首先,我们需要定义包的名称,当在你的客户端——服务器应用中引用proto文件时,Tonic会通过这个包名称来搜索。这里,我们叫它helloworld
syntax = "proto3";
package helloworld;
下一步,我们来定义服务。这个服务中将包含我们的应用中会使用到的RPC调用。每个RPC包含一个标识符,一个请求类型,并返回一个响应类型。这是我们的Greeter服务,它提供SayHello RPC方法。
service Greeter {
// SayHello rpc 接受 HelloRequests 并返回 HelloReplies
rpc SayHello (HelloRequest) returns (HelloReply);
}
最后,我们需要定义上面SayHello
RPC方法中的请求与返回的类型。RPC类型定义为包含类型化字段的消息,如下:
message HelloRequest {
// 请求消息中包含要问候的名称
string name = 1;
}
message HelloReply {
// 回复包含问候语
string message = 1;
}
最终,我们入门指导中的.proto
文件完整的样子:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
应用设置
现在,在完成了protobuf的定义之后,我们可以用Tonic来完成我们应用的逻辑。首先,我们需要在Cargo.toml
中加入必要的依赖项。
[package]
name = "helloworld-tonic"
version = "0.1.0"
edition = "2018"
[[bin]] # 用来运行 HelloWorld gRPC 服务器的可执行文件
name = "helloworld-server"
path = "src/server.rs"
[[bin]] # 用来运行 HelloWorld gRPC 客户端的可执行文件
name = "helloworld-client"
path = "src/client.rs"
[dependencies]
tonic = "0.3"
prost = "0.6"
tokio = { version = "0.2", features = ["macros"] }
[build-dependencies]
tonic-build = "0.3"
我们加进了tonic-build
是为了可以使用它在构建过程中很方便的生成gRPC的客户端和服务器端代码。以下是设置这个构建过程的方式:
生成服务器端以及客户端代码
在crate的根目录下,创建一个build.rs
文件,然后添加以下代码:
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/helloworld.proto")?;
Ok(())
}
这个方程的目的是在你构建Rust项目时利用tonic-build
来编译protobuf文件。配置这个构建过程的方式不止此一种,不过其他方式我们就不再这里赘述了。
编写服务器端代码
至此,我们的构建过程已经完备,依赖项也已经配置完成。我们可以开始编写有意思的部分了。我们需要引用服务器需要的库,包括protobuf。从在/src
目录下创建一个叫server.rs
的文件并加入以下代码开始:
use tonic::{transport::Server, Request, Response, Status};
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
pub mod hello_world {
tonic::include_proto!("helloworld"); // 这里指定的字符串必须与proto的包名称一致
}
接下来,我们可以在代码中实现之前在.proto
中定义的Greeter服务:
#[derive(Debug, Default)]
pub struct MyGreeter {}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>, // 接收以HelloRequest为类型的请求
) -> Result<Response<HelloReply>, Status> { // 返回以HelloReply为类型的示例作为响应
println!("Got a request: {:?}", request);
let reply = hello_world::HelloReply {
message: format!("Hello {}!", request.into_inner().name).into(), // 由于gRPC请求和响应中的字段都是私有的,所以需要使用 .into_inner()
};
Ok(Response::new(reply)) // 发回格式化的问候语
}
}
最后,我们需要使用Tokio运行时来跑我们的服务器。这里我们需要加入Tokio这个库作为依赖。
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
Ok(())
}
最后完成的代码:
use tonic::{transport::Server, Request, Response, Status};
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
pub mod hello_world {
tonic::include_proto!("helloworld");
}
#[derive(Debug, Default)]
pub struct MyGreeter {}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
println!("Got a request: {:?}", request);
let reply = hello_world::HelloReply {
message: format!("Hello {}!", request.into_inner().name).into(),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
Ok(())
}
现在,你应该可以通过命令:cargo run --bin helloworld-server
来运行你的HelloWorld gRPC服务器。这里使用到了之前我们在Cargo.toml
中定义的[[bin]]来运行服务器。
你可以使用类似Bloom RPC的图形化gRPC客户端发送请求用来测试是否能正常得到服务器返回的问候语。
你也可以用命令行工具比如grpcurl来发送请求进行测试:
$ grpcurl -plaintext -import-path ./proto -proto helloworld.proto -d '{"name": "Tonic"}' [::]:50051 helloworld.Greeter/SayHello
得到的服务器返回的相应应该是这样的:
{
"message": "Hello Tonic!"
}
编写客户端代码
我们现在有一个可以运行的gRPC服务器,但如何让我们的程序和它进行通信呢?我们需要编写一个客户端。Tonic支持客户端与服务器端的实现,我们从在/src
目录下创建一个叫client.rs
的文件并引用所有需要的库开始:
use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;
pub mod hello_world {
tonic::include_proto!("helloworld");
}
客户端相比服务器端来说,实现更加简洁,因为我们不需要在客户端中定义服务方法,只需要发送请求。这里我们使用Tokio运行时来发送我们的请求,并将返回的响应消息打印到终端中:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
let request = tonic::Request::new(HelloRequest {
name: "Tonic".into(),
});
let response = client.say_hello(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
完整的客户端源代码:
use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;
pub mod hello_world {
tonic::include_proto!("helloworld");
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
let request = tonic::Request::new(HelloRequest {
name: "Tonic".into(),
});
let response = client.say_hello(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
总结
目前我们编写了protobuf文件,一个构建文件来编译我们的protobuf文件,一个实现SayHello服务的服务器端程序,和一个用来向服务器发送请求的客户端。你应该在项目根目录下有以下文件: proto/helloworld.proto
, build.rs
,src/server.rs
,src/client.rs
运行服务器:cargo run --bin helloworld-server
。然后在另一个终端窗口中运行客户端:cargo run --bin helloworld-client
如果一切正常,你应该可以看到运行服务器端程序的终端窗口中的日志会打印出请求信息,而运行客户端的终端窗口中则会打印出响应信息。
恭喜你完成了Tonic的入门指导!希望这个入门指导能帮助你理解Tonic的基础,并帮助你开始使用gRPC在Rust中编写高性能,有互通性和灵活性的服务器。
- 祭奠那些年,我弃坑的开源轮子
- 这些奇技浮巧,助你优化前端应用性能
- Stepping.js——两步完成前后端分离架构设计
- 我的职业是前端工程师【十】客户端存储艺术:数据存储与模型
- 【开源】2md:将复制的内容、网页转成 markdown
- React Native 持续部署实践— push 代码构建出新版的 Growth
- 技巧 - 如何好一个 Git 提交信息及几种不同的规范
- React、Vue、Ember 及其他前端开发者,请暂缓更新到 Chrome 59 浏览器
- 微软开源全新的文档生成工具DocFX
- 使用 MimeKit 和 MailKit 发送邮件
- 使用 React Native 重写大型 Ionic 应用后,我们想分享一下这八个经验
- 基于OWin的Web服务器Katana发布版本3
- 【工具推荐】图像界的魔术师 ImageMagick
- 使用Metrics.NET 构建 ASP.NET MVC 应用程序的性能指标
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【Rust日报】2020-09-06 Evil_DLL 用来测试注入方法的DLL
- 【Rust日报】2020-09-05 微软在c++静态分析工具实现了一些rust的安全规则
- 一起来玩玩WebGL
- 自研网关:多项目的swagger聚合功能
- Prometheus 如何做到“活学活用”,大牛总结的避坑指南
- 文档驱动 —— 表单组件(一):表单元素组件 优点缺点选择文本类的Inputcheck 多选value的类型问题
- Vue3.0源码结构分析
- 【每周一库】- cached - 缓存结构型、辅助函数记忆化
- meta生成器 —— 表单元素组件 meta表单代码meta的模板data变幻
- 不用写代码也能做表单 —— 加载meta即可 菜单表单加载json运行效果。ModelAbout
- 从0到1,手把手教你入门 etcd
- 数据结构:手把手带你了解 ”图“ 所有知识!(含DFS、BFS)
- 设计模式之单例模式
- AndroidStudio创建JNI 工程与调用
- Java 版植物大战僵尸思路和源码分享!