logo

【Rust】Tonicでgrpcの接続テストをするまで【初学者】

投稿日2024-02-17

更新日2024-05-03

Picture of the logo
目次(タップして移動)

VscodeでrustのDev Containerを作り、Tonicを使ったgrpcサーバとクライアントを作り、接続テストをしてみる。接続の結果、お馴染みのHello worldが表示される。

公式GitHub

この記事のコードをまとめたもの

前提条件

  • Vscodeがインストールされている
  • dockerがインストールされている
  • rustのチュートリアル程度の知識はある

DevContainerをつくる

  1. まず、基本のworkspaceとなるフォルダを作り、.devcontainerフォルダを作成する。
  2. その中に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が開く。初回は少し時間がかかるかも。 rust-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(())
}

最終的にはこのようなディレクトリ構造になっていると思います。 tree

テストしてみる

まず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 }

というようなレスポンスがターミナルに表示される。以上で、開通テストは終わりです。

今後

ストリーミング通信を実装したり、セキュリティ対策を施したりとしていきたいと思います。






このサイトをシェアする