RustでgRPCに入門する windows編
gRPCの概要
gRPCはGoogleが開発したオープンソースのRPC(Remote Procedure Call)フレームワークです。2015年に公開され、マイクロサービス間の通信やモバイルアプリとバックエンドサーバー間の通信で広く利用されています。
マイクロサービスとは、機能ごとに独立したサービスとして設計されたソフトウェアアーキテクチャです。例えば、決済機能や検索機能などが個別のマイクロサービスとして提供され、それらを組み合わせて一つの大きなサービスを形成します。
RustのgRPCライブラリtonicとは?
tonicは高性能、相互運用性、柔軟性に焦点を当てたHTTP/2上のgRPC実装です。このライブラリは、Rustで書かれた本番システムの基盤として、async/awaitの第一級サポートを提供するために作成されました。
tonicは主に三つの主要コンポーネントで構成されています
汎用gRPC実装、高性能HTTP/2実装、およびprostによって駆動されるコード生成(codegen)の三つです。
一連の汎用トレイトを介して任意のHTTP/2実装および任意のエンコーディングをサポートできます。HTTP/2実装は、堅牢なtokioスタックの上に構築された高速なHTTP/1.1およびHTTP/2クライアントとサーバであるhyperに基づいています。
コード生成(codegen)には、protobuf定義からクライアントとサーバを構築するためのツールが含まれています。
今回は公式のチュートリアルを参考に入門してみましょう。
https://github.com/hyperium/tonic/blob/master/examples/helloworld-tutorial.md
①Protocol Buffersを導入する windows編
windowsの場合は下記のGithubからダウンロードしてbinファイルにpathを通す作業をします。
今回私のマシンはWINDOWS 64BITなのでprotoc-27.2-win64.zipをダウンロードして、圧縮ファイルを解凍し、以下のフォルダに配置しました。
C:\Program Files\Protoc
そして環境変数のPathにC:\Program Files\Protoc\protoc-27.2-win64\binを設定します。
②新しいプロジェクトを追加します。
//PowerShell
$ cargo new helloworld-tonic
$ cd helloworld-tonic
③.protoファイルをプロジェクトのルートディレクトリに保持します。
//PowerShell
New-Item -ItemType Directory -Path .\proto
New-Item -ItemType File -Path .\proto\helloworld.proto
④.protoファイルの中身を変更します。
// helloworld.proto
syntax = "proto3";
package helloworld;
// Greeterサービスは、クライアントがHelloRequestを送信し、
// サーバーがHelloReplyを返すメソッドを提供します。
service Greeter {
// SayHelloは、クライアントから受け取ったHelloRequestに基づいて、
// サーバーが挨拶のメッセージを含むHelloReplyを返すRPCです。
rpc SayHello (HelloRequest) returns (HelloReply);
}
// HelloRequestは、挨拶を要求する際にクライアントが送信するメッセージです。
message HelloRequest {
string name = 1;
}
// HelloReplyは、サーバーがクライアントに返信する挨拶のメッセージです。
message HelloReply {
string message = 1;
}
⑤Cargo.tomlを変更します。
[package]
name = "helloworld-tonic"
version = "0.1.0"
edition = "2021"
[[bin]] # Bin to run the HelloWorld gRPC server
name = "helloworld-server"
path = "src/server.rs"
[[bin]] # Bin to run the HelloWorld gRPC client
name = "helloworld-client"
path = "src/client.rs"
[dependencies]
tonic = "0.11"
prost = "0.12"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
[build-dependencies]
tonic-build = "0.11"
⑥build.rsファイルを作成する
srcディレクトリと同じ階層にbuild.rsファイルを作成する
//PowerShell
New-Item -ItemType File -Path .\build.rs
作成したbuild.rsにコードを追加して、cargo build コマンドでビルドします。
// build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Tonicで利用するために、Protobufファイルをコンパイルする
// ここでは"proto/helloworld.proto"ファイルを指定している
tonic_build::compile_protos("proto/helloworld.proto")?;
// 正常に完了したらOkを返す
Ok(())
}
⑦サーバーのコードを作成するserver.rs
/src ディレクトリに server.rs というファイルを作成し、次のコードを追加します
//server.rs
use tonic::{transport::Server, Request, Response, Status};
// hello_worldモジュールを宣言し、greeter_serverとHelloReply、HelloRequestをインポート
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
// Protoファイルから生成されたモジュールをインクルード
pub mod hello_world {
tonic::include_proto!("helloworld");
}
// MyGreeter構造体を定義し、デフォルトの実装を提供
#[derive(Debug, Default)]
pub struct MyGreeter {}
// 非同期トレイトのGreeterを実装
#[tonic::async_trait]
impl Greeter for MyGreeter {
// say_helloメソッドを非同期で実装
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
// リクエストを標準出力に表示
println!("Got a request: {:?}", request);
// リクエストから名前を取得し、HelloReplyのメッセージを作成
let reply = HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};
// レスポンスを生成して返す
Ok(Response::new(reply))
}
}
// 非同期のmain関数を定義
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// サーバーのアドレスをパース
let addr = "[::1]:50051".parse()?;
// MyGreeterのデフォルトインスタンスを作成
let greeter = MyGreeter::default();
// tonicのServerビルダーを使用してサービスを追加し、指定したアドレスでサーバーを開始
Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
Ok(())
}
⑧クライアントのコードを作成する
Tonicはクライアントとサーバーの両方の実装をサポートしています。サーバーと同様に、まず/srcディレクトリにclient.rsというファイルを作成し、必要なものをすべてインポートします。
//client.rs
// GreeterClientとHelloRequestをモジュールからインポート
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>> {
// GreeterClientのインスタンスを作成し、サーバーに接続
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
// サーバーに送信するリクエストを作成
let request = tonic::Request::new(HelloRequest {
name: "Tonic".into(), // リクエストのnameフィールドに"Tonic"を設定
});
// サーバーにリクエストを送信し、レスポンスを待機
let response = client.say_hello(request).await?;
// レスポンスの内容を出力
println!("RESPONSE={:?}", response);
// 正常終了
Ok(())
}
⑨サーバーを起動する
サーバーを実行するには、cargo run --bin helloworld-server を実行します
cargo run --bin helloworld-server
⑩クライアントを実行する
別のターミナル ウィンドウで cargo run --bin helloworld-client を実行します。
cargo run --bin helloworld-client
サーバーのターミナル ウィンドウにサーバーによってログアウトされたリクエストと、クライアントによってログアウトされた応答が表示されます。
//helloworld-server
Got a request: Request { metadata: MetadataMap { headers: {"te": "trailers", "content-type": "application/grpc", "user-agent": "tonic/0.11.0"} }, message: HelloRequest { name2: "Tonic Rust" }, extensions: Extensions }
//helloworld-client
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Sat, 29 Jun 2024 09:49:59 GMT", "grpc-status": "0"} }, message: HelloReply {
message2: "Hello Tonic Rust!" }, extensions: Extensions }
まとめ
gRPC を使用すると、.proto ファイルでサービスを 1 回定義し、gRPC でサポートされている言語のいずれかでクライアントとサーバーを実装できます。これにより、サーバーから独自のタブレットまで、さまざまな環境で実行できます。
さらに深堀したい人はこちらを参照してみてください。https://github.com/hyperium/tonic/blob/master/examples/routeguide-tutorial.md
Rsutをさらに学習したい人におすすめ
この記事が気に入ったらサポートをしてみませんか?