Tonic es una implementación en Rust de gRPC, un marco de trabajo RPC general, de código abierto y de alto rendimiento que prioriza los dispositivos móviles y HTTP/2.
Es una librería que se creó para ofrecer soporte de primera clase para async/await y para servir como componente fundamental en sistemas de producción escritos en Rust.
Sus características principales son:
- Transmisión bidireccional E/S asíncrona de alto rendimiento.
- Interoperabilidad
TLS con soporte de
rustls. - Balanceo de carga.
- Metadatos personalizados.
- Autenticación.
- Verificación de estado.
Para conocer mejor esta librería haremos un proyecto cliente-servidor con Rust como lenguaje base.
Requisitos:
- Tener Rust instalado.
- Tener Cargo instalado.
Verifiquemos la versión de Rust:
$ rustc --version
Verifiquemos la versión de Cargo:
$ cargo version
Empezando con Tonic y Rust (con Cargo)
1. Crearemos un proyecto Rust con ayuda de Cargo, una vez creado nos ubicaremos en directorio:
$ cargo new demo-tonic-rust $ cd demo-tonic-rust
2. Modificaremos el archivo ``Cargo.toml`` para agregar las dependencias:
[package] name = "demo-tonic-rust" version = "0.1.0" edition = "2024" [dependencies] tokio = { version = "1", features = ["full"] } tonic = "0.11" prost = "0.12" [build-dependencies] tonic-build = "0.11"
3. Usaremos el archivo ``validar.proto`` del ejemplo de gRPC con Go del post anterior:
syntax = "proto3"; package validar; option go_package = "demo-grpc-go/proto;validar"; service Validar { rpc EsMayorQue100 (NumeroRequest) returns (NumeroResponse); } message NumeroRequest { int32 valor = 1; } message NumeroResponse { bool es_mayor = 1; }
4. Para la generación del código (con tonic-build) crearemos un archivo ``build.rs``:
fn main() { tonic_build::configure() .build_server(true) .build_client(true) .compile(&["proto/validar.proto"], &["proto"]) .unwrap(); }
Esto compilará el .proto y generará el código Rust en target.
5. Crearemos el programa ``servidor.rs``:
use tonic::{transport::Server, Request, Response, Status}; use validar::validar_server::{Validar, ValidarServer}; use validar::{NumeroRequest, NumeroResponse}; pub mod validar { tonic::include_proto!("validar"); } #[derive(Default)] pub struct MyValidar {} #[tonic::async_trait] impl Validar for MyValidar { async fn es_mayor_que100( &self, request: Request<NumeroRequest>, ) -> Result<Response<NumeroResponse>, Status> { let numero = request.into_inner().valor; let respuesta = NumeroResponse { es_mayor: numero > 100, }; Ok(Response::new(respuesta)) } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let addr = "[::1]:50051".parse()?; let validar = MyValidar::default(); println!("Servidor gRPC escuchando en {}", addr); Server::builder() .add_service(ValidarServer::new(validar)) .serve(addr) .await?; Ok(()) }
6. Crearemos el programa ``cliente.rs``:
use tonic::transport::Channel; use validar::validar_client::ValidarClient; use validar::NumeroRequest; pub mod validar { tonic::include_proto!("validar"); } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let mut client = ValidarClient::connect("http://[::1]:50051").await?; let numero = 150; let request = tonic::Request::new(NumeroRequest { valor: numero }); let response = client.es_mayor_que100(request).await?; println!("Número enviado: {}", numero); println!("¿Es mayor a 100? {}", response.into_inner().es_mayor); Ok(()) }
7. Editamos el archivo ``Cargo.toml`` para agregar los "binarios" del cliente y servidor:
[package] name = "demo-tonic-rust" version = "0.1.0" edition = "2024" [dependencies] tokio = { version = "1", features = ["full"] } tonic = "0.11" prost = "0.12" [build-dependencies] tonic-build = "0.11" [[bin]] name = "servidor" path = "src/servidor.rs" [[bin]] name = "cliente" path = "src/cliente.rs"
8. Compilamos el proyecto:
$ cargo build
9. Ejecutamos el servidor:
$ cargo run --bin servidor
10. En otra terminal ejecutamos el cliente:
$ cargo run --bin cliente
Si todo va bien, veremos esto en el servidor:
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.32s Running `target\debug\servidor.exe` Servidor gRPC escuchando en [::1]:50051
En el cliente:
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.86s Running `target\debug\cliente.exe` Número enviado: 150 ¿Es mayor a 100? true
Lo cual es correcto, pues le hemos enviado un número entero con valor de 150.
11. Modifiquemos el ``cliente.rs`` de tal manera pueda enviar cualquier número al servidor.
use tonic::transport::Channel; use validar::validar_client::ValidarClient; use validar::NumeroRequest; use std::env; pub mod validar { tonic::include_proto!("validar"); } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let args: Vec<String> = env::args().collect(); if args.len() < 2 { eprintln!("Uso: cargo run --bin client <numero>"); std::process::exit(1); } let numero: i32 = args[1].parse().expect("Debe ser un número entero"); let mut client = ValidarClient::connect("http://[::1]:50051").await?; let request = tonic::Request::new(NumeroRequest { valor: numero }); let response = client.es_mayor_que100(request).await?; println!("Número enviado: {}", numero); println!("¿Es mayor a 100? {}", response.into_inner().es_mayor); Ok(()) }
12. Ejecutamos el servidor:
$ cargo run --bin servidor
13. En otra terminal ejecutamos el cliente:
$ cargo run --bin cliente 1
Salida:
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.79s Running `target\debug\cliente.exe 1` Número enviado: 1 ¿Es mayor a 100? false
¡Hemos creado nuestra primera aplicación Tonic y Rust!
Continuaremos con esta serie en próximas entregas.
Enlaces:
https://crates.io/crates/tonichttps://github.com/hyperium/tonic
https://crates.io/
https://rust-lang.org/es/


Comentarios
Publicar un comentario