Tonic для Rust: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое Tonic и зачем он нужен?

Tonic — это современная, высокопроизводительная библиотека для реализации gRPC-серверов и клиентов на языке Rust. Она построена на базе асинхронного рантайма Tokio и HTTP/2-фреймворка Hyper, что обеспечивает максимальную производительность и полную совместимость со стандартом gRPC.

Основные преимущества Tonic:

  • Нативная асинхронность — полностью async/await, без блокировок
  • Типобезопасность — генерация кода из proto-файлов на этапе компиляции
  • Поддержка всех gRPC-паттернов — unary, server streaming, client streaming, bidirectional streaming
  • Интеграция с экосистемой Rust — работает с Tokio, Tower, Hyper и другими популярными крейтами
  • Производительность — один из самых быстрых gRPC-фреймворков

Библиотека используется в продакшене такими компаниями, как Discord, Microsoft, Amazon и многими другими. Tonic идеально подходит для создания микросервисов, API-шлюзов и любых распределенных систем, где требуется эффективная коммуникация между сервисами.

Установка Tonic

Для работы с Tonic вам потребуется установить несколько зависимостей. Добавьте их в ваш Cargo.toml:

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

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

Пояснение зависимостей:

  • tonic — основная библиотека для работы с gRPC
  • prost — генератор Protocol Buffers кода для Rust
  • tokio — асинхронный рантайм
  • tonic-build — используется на этапе сборки для компиляции .proto файлов

Также вам потребуется установить protoc — компилятор Protocol Buffers. На Ubuntu/Debian:

sudo apt install protobuf-compiler

На macOS:

brew install protobuf

На Windows скачайте бинарник с официального репозитория и добавьте его в PATH.

Быстрый старт: Hello World на gRPC

Создадим простой сервис, который принимает имя и возвращает приветствие.

Шаг 1. Создайте файл proto/helloworld.proto:

syntax = "proto3";

package helloworld;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

Шаг 2. Создайте build.rs в корне проекта:

fn main() -> Result<(), Box> {
    tonic_build::compile_protos("proto/helloworld.proto")?;
    Ok(())
}

Шаг 3. Реализуйте сервер (src/server.rs):

use tonic::{transport::Server, Request, Response, Status};
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloRequest, HelloReply};

pub mod hello_world {
    tonic::include_proto!("helloworld");
}

#[derive(Default)]
pub struct MyGreeter;

#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(
        &self,
        request: Request,
    ) -> Result, Status> {
        let name = request.into_inner().name;
        let reply = HelloReply {
            message: format!("Hello, {}!", name),
        };
        Ok(Response::new(reply))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box> {
    let addr = "[::1]:50051".parse()?;
    let greeter = MyGreeter::default();

    Server::builder()
        .add_service(GreeterServer::new(greeter))
        .serve(addr)
        .await?;

    Ok(())
}

Шаг 4. Создайте клиент (src/client.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> {
    let mut client = GreeterClient::connect("http://[::1]:50051").await?;

    let request = tonic::Request::new(HelloRequest {
        name: "World".into(),
    });

    let response = client.say_hello(request).await?;
    println!("RESPONSE={:?}", response.into_inner().message);

    Ok(())
}

Запустите сервер и клиент в разных терминалах:

cargo run --bin server
cargo run --bin client

Основные методы и функции Tonic

1. tonic::transport::Server::builder()

Описание: Создает новый билдер для конфигурации gRPC-сервера. Позволяет настраивать различные параметры сервера.

Параметры: Нет входных параметров. Возвращает ServerBuilder.

Пример:

use tonic::transport::Server;

let server = Server::builder()
    .add_service(my_service)
    .serve(addr)
    .await?;

2. ServerBuilder::add_service()

Описание: Добавляет gRPC-сервис к серверу. Можно вызывать несколько раз для добавления нескольких сервисов.

Параметры: svc: impl Service — реализация сервиса, сгенерированная из proto-файла.

Пример:

Server::builder()
    .add_service(GreeterServer::new(greeter))
    .add_service(AnotherServiceServer::new(another_service))
    .serve(addr)
    .await?;

3. ServerBuilder::serve()

Описание: Запускает сервер на указанном адресе. Блокирует текущий поток до завершения работы сервера.

Параметры: addr: SocketAddr — адрес и порт для прослушивания.

Пример:

let addr = "0.0.0.0:50051".parse()?;
Server::builder()
    .add_service(MyService::new())
    .serve(addr)
    .await?;

4. tonic::transport::Channel::from_static()

Описание: Создает gRPC-канал для подключения к серверу из статической строки. Используется для создания клиентов.

Параметры: endpoint: &'static str — URL сервера в формате http://host:port.

Пример:

use tonic::transport::Channel;

let channel = Channel::from_static("http://localhost:50051");
let mut client = MyServiceClient::new(channel);

5. tonic::Request::new()

Описание: Создает новый gRPC-запрос с указанным сообщением. Позволяет также добавлять метаданные.

Параметры: message: T — сообщение типа, сгенерированного из proto.

Пример:

let request = tonic::Request::new(HelloRequest {
    name: "Alice".to_string(),
});

// С метаданными
let mut request = tonic::Request::new(HelloRequest::default());
request.metadata_mut().insert(
    "authorization",
    "Bearer token123".parse().unwrap(),
);

6. tonic::Response::new()

Описание: Создает новый gRPC-ответ с указанным сообщением. Используется на стороне сервера для возврата данных.

Параметры: message: T — сообщение-ответ.

Пример:

let reply = HelloReply {
    message: "Hello, World!".to_string(),
};
Ok(Response::new(reply))

7. tonic::Status::new()

Описание: Создает gRPC-статус ошибки с указанным кодом и сообщением. Используется для возврата ошибок.

Параметры: code: Code — код ошибки gRPC, message: &str — описание ошибки.

Пример:

use tonic::{Code, Status};

return Err(Status::new(
    Code::NotFound,
    "User not found"
));

// Или используя готовые методы:
return Err(Status::not_found("User not found"));

8. tonic::include_proto!()

Описание: Макрос для включения сгенерированного из proto-файлов кода. Должен вызываться внутри модуля.

Параметры: package_name: literal — имя пакета из proto-файла.

Пример:

pub mod my_service {
    tonic::include_proto!("my_service_package");
}

9. tonic::transport::Endpoint::new()

Описание: Создает endpoint для подключения с более тонкой настройкой, чем Channel::from_static. Позволяет настроить таймауты, TLS и другие параметры.

Параметры: uri: Uri — URI сервера.

Пример:

use tonic::transport::Endpoint;

let endpoint = Endpoint::new("http://localhost:50051")?
    .timeout(Duration::from_secs(5))
    .connect_timeout(Duration::from_secs(3));

let channel = endpoint.connect().await?;
let mut client = MyServiceClient::new(channel);

10. Client::connect()

Описание: Упрощенный метод для подключения к gRPC-серверу. Создает канал и возвращает готового клиента.

Параметры: endpoint: &str — URL сервера.

Пример:

let mut client = GreeterClient::connect("http://localhost:50051").await?;
let response = client.say_hello(request).await?;

Реальный практический пример: сервис управления пользователями

Создадим полноценный CRUD-сервис для управления пользователями.

proto/user_service.proto:

syntax = "proto3";

package userservice;

service UserService {
    rpc CreateUser (CreateUserRequest) returns (User);
    rpc GetUser (GetUserRequest) returns (User);
    rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
    rpc UpdateUser (UpdateUserRequest) returns (User);
    rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse);
}

message User {
    int32 id = 1;
    string name = 2;
    string email = 3;
    int32 age = 4;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
    int32 age = 3;
}

message GetUserRequest {
    int32 id = 1;
}

message ListUsersRequest {
    int32 page = 1;
    int32 page_size = 2;
}

message ListUsersResponse {
    repeated User users = 1;
    int32 total_count = 2;
}

message UpdateUserRequest {
    int32 id = 1;
    string name = 2;
    string email = 3                    

Рекомендаций