1. Introduction
Dans cet atelier de programmation, vous allez utiliser gRPC-Rust pour créer un client et un serveur qui constituent la base d'une application de mappage d'itinéraires écrite en Rust.
À la fin de ce tutoriel, vous disposerez d'un client qui se connecte à un serveur distant à l'aide de l'implémentation Rust officielle du protocole gRPC pour récupérer le nom ou l'adresse postale d'un lieu à des coordonnées spécifiques sur une carte. Une application complète peut utiliser cette conception client-serveur pour énumérer ou résumer les points d'intérêt le long d'un itinéraire.
Le service est défini dans un fichier Protocol Buffers, qui sera utilisé pour générer du code récurrent pour le client et le serveur afin qu'ils puissent communiquer entre eux, ce qui vous fera gagner du temps et des efforts lors de l'implémentation de cette fonctionnalité.
Ce code généré gère non seulement les complexités de la communication entre le serveur et le client, mais aussi la sérialisation et la désérialisation des données.
Points abordés
- Comment utiliser Protocol Buffers pour définir une API de service.
- Comment créer un client basé sur gRPC à partir d'une définition Protocol Buffers à l'aide de la génération de code automatisée.
- Comprendre la communication client-serveur avec gRPC.
Cet atelier de programmation s'adresse aux développeurs Rust qui découvrent gRPC ou qui souhaitent se familiariser avec gRPC, ou à toute autre personne intéressée par la création de systèmes distribués. Aucune expérience préalable avec gRPC n'est requise.
2. Avant de commencer
Prérequis
Assurez-vous d'avoir installé les éléments suivants :
- GCC. Suivez les instructions ici.
- Git : instructions d'installation ici.
- Rust, version 1.88.0. Suivez les instructions d'installation ici.
Obtenir le code
Pour que vous n'ayez pas à partir de zéro, cet atelier de programmation fournit une structure du code source de l'application que vous pouvez compléter. Les étapes suivantes vous montreront comment terminer l'application, y compris comment utiliser les plug-ins du compilateur de tampon de protocole pour générer le code gRPC passe-partout.
Commencez par créer le répertoire de travail de l'atelier de programmation et accédez-y :
mkdir grpc-rust-getting-started && cd grpc-rust-getting-started
Téléchargez et extrayez l'atelier de programmation :
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/2026.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-2026/codelabs/grpc-rust-getting-started/start_here
Vous pouvez également télécharger le fichier .zip contenant uniquement le répertoire de l'atelier de programmation et le décompresser manuellement.
Le code source complet est disponible sur GitHub si vous ne souhaitez pas saisir d'implémentation.
3. Définir le service
La première étape consiste à définir le service gRPC de l'application, sa méthode RPC et ses types de messages de requête et de réponse à l'aide de Protocol Buffers. Votre service fournira les éléments suivants :
- Une méthode RPC appelée
GetFeatureque le serveur implémente et que le client appelle. - Les types de messages
PointetFeaturequi sont des structures de données échangées entre le client et le serveur lors de l'utilisation de la méthodeGetFeature. Le client fournit des coordonnées cartographiques sous la forme d'unPointdans sa requêteGetFeatureau serveur, et le serveur répond avec uneFeaturecorrespondante qui décrit ce qui se trouve à ces coordonnées.
Cette méthode RPC et ses types de messages seront tous définis dans le fichier proto/routeguide.proto du code source fourni.
Les Protocol Buffers sont communément appelés protobufs. Pour en savoir plus sur la terminologie gRPC, consultez Concepts de base, architecture et cycle de vie de gRPC.
Méthode de service
Commençons par définir nos méthodes de service, puis définissons nos types de messages Point et Feature. Le fichier proto/routeguide.proto comporte une structure service nommée RouteGuide qui définit une ou plusieurs méthodes fournies par le service de l'application.
Ajoutez la méthode rpc GetFeature dans la définition RouteGuide. Comme expliqué précédemment, cette méthode recherchera le nom ou l'adresse d'un lieu à partir d'un ensemble de coordonnées donné. Par conséquent, faites en sorte que GetFeature renvoie une Feature pour un Point donné :
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Il s'agit d'une méthode RPC unaire : un RPC simple dans lequel le client envoie une requête au serveur et attend une réponse, comme un appel de fonction local.
Types de messages
Dans le fichier proto/routeguide.proto du code source, commencez par définir le type de message Point. Un Point représente une paire de coordonnées de latitude et de longitude sur une carte. Pour cet atelier de programmation, utilisez des entiers pour les coordonnées :
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Les nombres 1 et 2 sont des numéros d'ID uniques pour chacun des champs de la structure message.
Ensuite, définissez le type de message Feature. Une Feature utilise un champ string pour le nom ou l'adresse postale d'un élément à un emplacement spécifié par un Point :
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
4. Générer le code client et serveur
Nous vous avons déjà fourni le code généré à partir du fichier .proto dans le répertoire generated/, y compris tous les ajouts que vous avez effectués ci-dessus. Cependant, nous aimerions prendre un moment pour expliquer comment fonctionne la génération de code.
Notre fichier .proto décrit toutes les structures et fonctions utilisées par un client ou un serveur. Nous utilisons un script de compilation Cargo (build.rs) ainsi que le crate grpc-protobuf-build pour générer automatiquement ce code.
Dans Cargo.toml, nous ajoutons grpc-protobuf-build sous [build-dependencies].
Dans build.rs, nous configurons grpc_protobuf_build::CodeGen pour compiler proto/routeguide.proto dans le répertoire generated/. Voici les lignes clés :
grpc_protobuf_build::CodeGen::new()
.include("proto")
.input("routeguide.proto")
.output_dir("generated")
.compile()
.unwrap();
Cela appelle la génération de code du crate grpc_protobuf_build, en lui transmettant le routeguide.proto. Nous avons encapsulé cela dans du code pour qu'il ne s'exécute que lorsqu'un flag de fonctionnalité est transmis, afin qu'il ne soit régénéré que lorsque vous le souhaitez. Vous n'avez pas besoin de l'exécuter maintenant, car nous avons déjà généré le code pour vous.
cargo build --bin routeguide-server --features regenerate_proto
Lorsque vous exécutez cargo build, build.rs compile les définitions de tampon de protocole dans le répertoire generate/, y compris les éléments suivants :
- Définitions de structure pour les types de messages
PointetFeature. - Un trait de service Tonic que nous devrons implémenter pour le serveur :
route_guide_server::RouteGuide. - Un type de client gRPC-Rust que nous utiliserons pour appeler le serveur :
route_guide_client::RouteGuideClient<T>.
Pour en savoir plus, consultez le guide protoc-gen-rust-grpc.
Ensuite, nous allons implémenter les méthodes de service sur le serveur.
5. Implémenter le service
Dans src/server/server.rs, nous pouvons mettre le code généré dans le champ d'application via la macro include_generated_proto! de gRPC et importer le trait RouteGuide et Point.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature,
};
Nous pouvons commencer par définir une structure pour représenter notre service. Pour l'instant, nous pouvons le faire sur src/server/server.rs :
#[derive(Debug)]
pub struct RouteGuideService {
features: Vec<Feature>,
}
Nous devons maintenant implémenter le trait route_guide_server::RouteGuide à partir de notre code généré.
RPC unaire simple
Le RouteGuideService implémente toutes nos méthodes de service. La fonction get_feature côté serveur est l'endroit où le travail principal est effectué : elle prend un message Point du client et renvoie dans un message Feature les informations de localisation correspondantes à partir d'une liste de lieux connus. Voici l'implémentation de la fonction dans src/server/server.rs :
#[tonic::async_trait]
impl RouteGuide for RouteGuideService {
async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
println!("GetFeature = {:?}", request);
let requested_point = request.get_ref();
for feature in self.features.iter() {
if feature.location().latitude() == requested_point.latitude() {
if feature.location().longitude() == requested_point.longitude(){
return Ok(Response::new(feature.clone()))
};
};
}
Ok(Response::new(Feature::default()))
}
}
Une fois cette méthode implémentée, nous devons également démarrer un serveur gRPC afin que les clients puissent réellement utiliser notre service. Remplacez main() par le code suivant.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:10000".parse().unwrap();
println!("RouteGuideServer listening on: {addr}");
let route_guide = RouteGuideService {
features: load(),
};
let svc = RouteGuideServer::new(route_guide);
Server::builder().add_service(svc).serve(addr).await?;
Ok(())
}
Voici ce qui se passe dans main(), étape par étape :
- Spécifiez le port que nous voulons utiliser pour écouter les requêtes client.
- Créez un
RouteGuideServiceavec des fonctionnalités chargées en appelant la fonction d'assistanceload(). - Créez une instance du serveur gRPC à l'aide de
RouteGuideServer::new()à l'aide du service que nous avons créé. - Enregistrez notre implémentation de service auprès du serveur gRPC.
- Appelez
serve()sur le serveur avec les détails de notre port pour effectuer une attente bloquante jusqu'à ce que le processus soit arrêté.
6. Créer le client
Dans cette section, nous allons créer un client Rust pour notre service RouteGuide dans src/client/client.rs.
Comme nous l'avons fait dans src/server/server.rs, nous pouvons mettre le code généré dans le champ d'application via la macro include_generated_proto! de gRPC et importer le type RouteGuideClient.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_client::RouteGuideClient,
Point,
};
Appeler des méthodes de service
Dans gRPC-Rust, les RPC sont asynchrones et non bloquantes, et utilisent la syntaxe async/await de Rust pour attendre les réponses du serveur.
Pour appeler des méthodes de service, nous créons d'abord un Channel à l'aide de Channel::builder(), en spécifiant l'adresse du serveur (dns:///[::1]:10000) et les identifiants de connexion (LocalChannelCredentials). Nous transmettons ensuite le canal à RouteGuideClient::new() pour instancier notre client :
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new gRPC channel
let channel = Channel::builder(
"dns:///[::1]:10000",
Arc::new(LocalChannelCredentials::new()),
)
.build();
// Create a new client
let client = RouteGuideClient::new(channel);
}
Dans cette fonction, RouteGuideClient::new() lie le canal générique au stub client généré qui implémente les méthodes définies dans notre définition de service .proto.
RPC simple
L'appel du RPC simple GetFeature est aussi simple que l'appel d'une méthode locale. Ajoutez ceci dans main() :
println!("*** SIMPLE RPC ***");
let point = proto!(Point {
latitude: 409_146_138,
longitude: -746_188_906,
});
let response = client
.get_feature(point)
.await
.expect("RPC error");
Dans gRPC-Rust, nous transmettons le message protobuf Point directement à client.get_feature(point). L'attente du futur renvoyé génère directement la réponse Feature, sans nécessiter d'autres appels de méthode.
Ensuite, imprimez les champs de la réponse :
println!(
"Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude()
);
Dans l'ensemble, la fonction main() du client doit se présenter comme suit :
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Create a new gRPC channel
let channel = Channel::builder(
"dns:///[::1]:10000",
Arc::new(LocalChannelCredentials::new()),
)
.build();
// 2. Instantiate the generated RouteGuideClient
let client = RouteGuideClient::new(channel);
println!("*** SIMPLE RPC ***");
let point = proto!(Point {
latitude: 409_146_138,
longitude: -746_188_906,
});
let response = client
.get_feature(point)
.await
.expect("RPC error");
println!(
"Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude()
);
Ok(())
}
7. Essayer
Pour exécuter votre client et votre serveur, vérifiez d'abord que les deux cibles binaires sont définies dans Cargo.toml :
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
Exécutez ensuite les commandes suivantes à partir de notre répertoire de travail :
- Exécutez le serveur dans un terminal :
cargo run --bin routeguide-server
- Exécutez le client à partir d'un autre terminal :
cargo run --bin routeguide-client
Le résultat doit ressembler à ceci, avec les codes temporels omis pour plus de clarté :
*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906
8. Étape suivante
- Passez à l'atelier de programmation Premiers pas avec gRPC-Rust (streaming).
- Explorez le dépôt gRPC-Rust officiel.
- Découvrez l'architecture gRPC dans Concepts de base.
- Consultez la documentation gRPC-Rust sur gRPC.io.
9. Contributeurs à cet atelier de programmation
- Cathy Zhao
- Lucio Franco
- Arvind Bright
- Nathaniel Ford