RustでgRPCサーバーを立ててみた
はじめに
はじめましてsweeepの五十嵐です。
sweeepではマイクロサービス構成の中のgRPCサーバーとしてGoを使っています。
個人的にRustに興味があったので、今回はgRPCサーバーをRustでつくってみようと思います。
Rustとは
- C/C++に匹敵するパフォーマンスやメモリ安全性を両立している
- GCの代わりに所有権モデルという独自のコンセプトを採用している(コンパイルが通る限りメモリ安全性が担保されている)
- 低レイヤ以外にWebアプリケーションのバックエンド開発にも使える
- モダンな言語機能が一通り入っている
低レイヤのイメージが強いRustですが、OSからWebアプリケーションまで幅広く実装できます。
メモリの安全性を言語仕様として保証してくれる上に、コンパイラのメッセージがとても親切なのが魅力的でした。
Hello world
gRPCのライブラリではtonicがデファクトスタンダードのようです。
それでは、実際にプロジェクトを作成して動作を確認していきます。
$ cargo new helloworld-tonic
$ cd helloworld-tonic
protocol bufferでAPIを定義します。
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Cargo.toml に依存関係を記載します。
[package]
name = "helloworld-tonic"
version = "0.1.0"
edition = "2021"
[dependencies]
tonic = "0.8"
prost = "0.11"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
[build-dependencies]
tonic-build = "0.8"
プロジェクトのルートで、build.rsファイルを作成し、次のコードを追加します。
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/helloworld.proto")?;
Ok(())
}
cargo build
でコードを自動生成します。
tonic-buildの中ではprostというcrateを介して、prost-buildにコード生成の処理を委譲しているようです。
そしてprost-buildにはprotocコマンドがバイナリとして埋め込まれているので、protocコマンドのインストールが不要です。
buildに成功するとtarget配下にコードが生成されます。
サーバーを実装します。
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
でサーバーを起動した後に
grpcurlを使用して以下のように送信リクエストを試します。
$ grpcurl -plaintext -import-path ./proto -proto helloworld.proto -d '{"name": "Tonic"}' '[::]:50051' helloworld.Greeter/SayHello
以下のようにレスポンスが返ってきたら成功です🦀
{
"message": "Hello Tonic!"
}
さいごに
途中ハマることなくすんなりgRPCサーバーを立てることができました。
個人的に低レイヤにも興味あるのでもっとRustを触っていきたいと思いました。
最後に、弊社では絶賛エンジニア募集中なので、気になる方は以下を覗いてみてください!
Discussion