見出し画像

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をさらに学習したい人におすすめ

この記事が気に入ったらサポートをしてみませんか?