【Rust】Tonicでgrpcの接続テストをするまで【初学者】
投稿日2024-02-17
更新日2024-05-03
目次(タップして移動)
VscodeでrustのDev Containerを作り、Tonicを使ったgrpcサーバとクライアントを作り、接続テストをしてみる。接続の結果、お馴染みのHello worldが表示される。
前提条件
- Vscodeがインストールされている
- dockerがインストールされている
- rustのチュートリアル程度の知識はある
DevContainerをつくる
- まず、基本のworkspaceとなるフォルダを作り、
.devcontainer
フォルダを作成する。 - その中にdevcontainer.jsonとdocker-compose.ymlを作る
[devcontainer.json]
{ "name": "Rust Development", "dockerComposeFile": ["docker-compose.yml"], "service": "rust_devcontainer", "workspaceFolder": "/workspace", "settings": { "terminal.integrated.shell.linux": "/bin/bash", "workbench.colorTheme": "One Dark Pro", "editor.defaultFormatter": "rustfmt", "files.associations": { "*.rs": "rust" }, "tasks": { "cargo build": { "type": "shell", "command": "cargo build", "group": "build", "label": "Build" }, "cargo run": { "type": "shell", "command": "cargo run", "group": "run", "label": "Run" }, "cargo test": { "type": "shell", "command": "cargo test", "group": "test", "label": "Test" }, "cargo fmt": { "type": "shell", "command": "cargo fmt", "group": "formatting", "label": "Format" } }, "extensions": [ "rust-lang.rust", "ms-vscode.rust-analyzer", "equinusocio.vsc-community-material-theme", "vscode-icons", "xaver.vscode-bookmarks" ], "remote.containers": { "defaultCommand": "cargo run" } } }
[docker-compose.yml]
services: rust_devcontainer: image: rust:1.76-buster restart: always tty: true volumes: - ../:/workspace working_dir: /workspace
ディレクトリ名とファイル名は必ずこの名前にして下さい。名前が違うと機能しません。ファイルの用意が出来たら画面左下の角の方にある><のようなアイコンをクリックするとメニューが出るので、コンテナを再度開くを選ぶとdevcontainerが開く。初回は少し時間がかかるかも。
DevContainerの中にプロジェクトを作る
コンテナが出来たらまずprotobuf-compilerをインストールしなければならない。インストール方法は公式githubに書いてある。使っているディストリビューションによって違う。
サーバーサイドの構築
公式githubのリポジトリでは一つのプロジェクトにサーバとクライアントがまとめて入っていたが、ここではサーバとクライアントで独立した別のプロジェクトを作成することにする。実際に使う際にもサーバとクライアントが同じプロジェクト内にあるのも変だし。という事で、
cargo new server cargo new client
として、別々のプロジェクトを作り、まずはcd server
でserverディレクトリに移動してサーバー側からコードを書いていく。
[Cargo.toml]
[package] name = "helloworld" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] # Bin to run the HelloWorld gRPC server name = "helloworld" path = "src/main.rs" [dependencies] prost = "0.12.3" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } tonic = "0.11.0" [build-dependencies] tonic-build = "0.11.0"
[build.rs] srcフォルダではなくCargo.tomlと同じ階層におくこと
fn main() -> Result<(), Box<dyn std::error::Error>> { tonic_build::compile_protos("proto/helloworld.proto")?; Ok(()) }
[main.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"); } #[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), }; Ok(Response::new(reply)) } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let addr = "127.0.0.1:8080".parse()?; let greeter = MyGreeter::default(); Server::builder() .add_service(GreeterServer::new(greeter)) .serve(addr) .await?; Ok(()) }
[proto/helloworld.proto]
syntax = "proto3"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
クライアントサイドの構築
ここではシンプルなテストなので、Cargo.toml、protoファイル、build.rsのファイルは使いまわしで構わない。同じフォルダ構造で同じ階層にファイルを配置してください。main.rsだけ新規に用意します。
[main.rs]
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://127.0.0.1:8080").await?; let request = tonic::Request::new(HelloRequest { name: "Tonic".into(), }); let response = client.say_hello(request).await?; println!("RESPONSE={:?}", response); Ok(()) }
最終的にはこのようなディレクトリ構造になっていると思います。
テストしてみる
まずserver側でcargo build
cargo run
してみる。立ち上がったらclient側でもcargo build
cargo run
してみる。上手く行けば、サーバーサイドには
Got a request: Request { metadata: MetadataMap { headers: {"te": "trailers", "content-type": "application/grpc", "user-agent": "tonic/0.11.0"} }, message: HelloRequest { name: "Tonic" }, extensions: Extensions }
クライアントサイドには
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Sat, 17 Feb 2024 12:54:32 GMT", "grpc-status": "0"} }, message: HelloReply { message: "Hello Tonic!" }, extensions: Extensions }
というようなレスポンスがターミナルに表示される。以上で、開通テストは終わりです。
今後
ストリーミング通信を実装したり、セキュリティ対策を施したりとしていきたいと思います。
サイト管理者
がずしげ
- アプリ開発
- 株式投資、等
関連記事
最新記事
スポンサーリンク
このサイトをシェアする