2021-12-01に更新

tonicを使ってgRPCサーバーを立ててみる

gRPCサーバー+grpc-webをバックエンドにしてWiki的なのをつくろうという気持ちがあるので、そのための実験をしています。今回はgRPCサーバーをRustでつくれるかの調査です。

ライブラリ選定

Rustで、Hello Worldを卒業した?次は、gRPCだよね! | by FUJITA Tomonori | nttlabs | Medium というRust教な記事があり、ここで tonic が推されています。

私は別にRust教信者ではないですが、Rustだけで完結していると環境構築が楽そうなので、とりあえずtonicを試してみることにしました。‘grpc’ search // Lib.rs でもtonicが一番人気っぽいですし、まあたぶんよくできているのでしょう。

Hello world

tonic/helloworld-tutorial.md at master · hyperium/tonicに従ってHelloWorldします。

環境準備

Rust 2021 Editionに依存しているのでRust 1.56以降が必要というのはなかなか面白いですね。

エディタにはVSCode、補完にはrust-analyzerを使います。ここで注意点なのですが、READMEには

If you're using rust-analyzer we recommend you set "rust-analyzer.cargo.loadOutDirsFromCheck": true to correctly load the generated code.

とあるものの、この通りにやると "unknown configuration setting" とVSCodeに言われてしまいます。rust-analyzerのIssueによると、この rust-analyzer.cargo.loadOutDirsFromCheck 設定は rust-analyzer.cargo.runBuildScripts に改名された上に既定でオンになるようになったらしいです。つまり、特に何の設定もしなくてもよいということですね。

proto/sinanoki.proto

protocol bufferでAPI定義を書きます。なお、このsinanokiというのは単なる(私が作ろうとしているものの)プロジェクト名なので意味は無いです。

syntax = "proto3";
package sinanoki;

service PageManager {
    rpc GetPage(GetPageRequest) returns (GetPageResponce);
}

message GetPageRequest {
    string path = 1;
}

message GetPageResponce {
    string markdown = 1;
}

cargo.toml

[package]
name = "sinanoki-backend"
version = "0.1.0"
authors = ["suzusime <***@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tonic = "0.6"
prost = "0.9"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }

[build-dependencies]
tonic-build = "0.6"

言われる通りにdependenciesの設定を追加します。今回はサーバーだけ(複数の実行ファイルはいらない)なので、binまわりの設定は飛ばしてserver.rsの代わりにmain.rsに書くことにします。

build.rs

ビルドに対して特殊な処理が必要になるので、ルートフォルダにbuild.rsを置きます。

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("./proto/sinanoki.proto")?;
    Ok(())
}

src/main.rs

今までの設定をしてcargo buildすると、protocol bufferに書いたものに対応したサーバー/クライアントの雛型となるRustのコードがtarget以下に生成されるようになります。これを利用してサーバーを実装します。

例えば私の場合は target/debug/build/sinanoki-backend-1664b6d9540c1c9f/out/sinanoki.rs に雛型コードが吐かれました。この雛型コードの中に

    #[async_trait]
    pub trait PageManager: Send + Sync + 'static {
        async fn get_page(
            &self,
            request: tonic::Request<super::GetPageRequest>,
        ) -> Result<tonic::Response<super::GetPageResponce>, tonic::Status>;
    }

というのがあるので、これをコピペしたものを使い、以下のようなコードをsrc/main.rsに書きます。

use sinanoki::page_manager_server::{PageManager, PageManagerServer};
use sinanoki::{GetPageRequest, GetPageResponce};
use tonic::{transport::Server, Request, Response, Status};

pub mod sinanoki {
    tonic::include_proto!("sinanoki"); // The string specified here must match the proto package name
}

#[derive(Debug, Default)]
pub struct MyPageManager {}

#[tonic::async_trait]
impl PageManager for MyPageManager {
    async fn get_page(
        &self,
        request: Request<GetPageRequest>,
    ) -> Result<Response<GetPageResponce>, Status> {
        println!("Got a request: {:?}", request);

        let reply = GetPageResponce {
            markdown: format!("you requested a file of {}", request.into_inner().path).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private
        };

        Ok(Response::new(reply))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse()?;
    let manager = MyPageManager::default();

    Server::builder()
        .add_service(PageManagerServer::new(manager))
        .serve(addr)
        .await?;

    Ok(())
}

実行

あとは

$ cargo run

して、

$ grpcurl -plaintext -import-path ./proto -proto ./proto/sinanoki.proto -d '{"path": "/index.md"}' localhost:50051 sinanoki.PageManager/GetPage
{
  "markdown": "you requested a file of /index.md"
}

で動作が確認できました。

所感

どうせ何かハマるだろうと思っていましたが、すんなり動きました。

困ったところを挙げるならば、(tonicに限らず)Rustの補完があまり気持ちよく動いてくれないことでしょうか。単にバグっぽいところ(implしようとしたときに勝手に出してくれるコードが明らかにおかしい)を除いても適切な補完候補がなかなか出てくれなくて、少し厳しいです。おそらく私の設定が悪いんだろうなと思っているのですが……。

ツイッターでシェア
みんなに共有、忘れないようにメモ

すずしめ

巫女見習いです。

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント