1. Introduction
Dans cet atelier de programmation, vous allez utiliser gRPC-Go pour créer un client et un serveur qui constituent la base d'une application de cartographie d'itinéraire écrite en Go.
À la fin de ce tutoriel, vous disposerez d'un client qui se connecte à un serveur distant à l'aide de gRPC pour obtenir le nom ou l'adresse postale d'un lieu situé à 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 passe-partout pour le client et le serveur afin qu'ils puissent communiquer entre eux. Vous gagnerez ainsi du temps et des efforts pour implémenter 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
- Utiliser Protocol Buffers pour définir une API de service.
- Comment créer un client et un serveur basés 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 Go qui débutent avec gRPC ou qui souhaitent se rafraîchir la mémoire sur gRPC, ou à toute 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 :
- La version 1.24.5 ou ultérieure de la chaîne d'outils Go. Pour obtenir des instructions d'installation, consultez la page Getting started (Premiers pas) de Go.
- Le compilateur de tampons de protocole
protoc
, version 3.27.1 ou ultérieure. Pour obtenir des instructions d'installation, consultez le guide d'installation du compilateur. - Plug-ins du compilateur de tampon de protocole pour Go et gRPC. Pour installer ces plug-ins, exécutez les commandes suivantes :
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Mettez à jour votre variable PATH
afin que le compilateur de tampon de protocole puisse trouver les plug-ins :
export PATH="$PATH:$(go env GOPATH)/bin"
Obtenir le code
Pour que vous n'ayez pas à partir de zéro, cet atelier de programmation fournit un échafaudage du code source de l'application que vous devez compléter. Les étapes suivantes vous montreront comment finaliser l'application, y compris en utilisant les plug-ins du compilateur de tampon de protocole pour générer le code gRPC standard.
Téléchargez ce code source sous forme d'archive .ZIP depuis GitHub, puis décompressez son contenu.
Si vous ne souhaitez pas saisir d'implémentation, vous pouvez également accéder au code source complet sur GitHub.
3. Définir le service
La première étape consiste à définir le service gRPC de l'application, sa méthode RPC, ainsi que ses types de messages de requête et de réponse à l'aide de Protocol Buffers. Votre service fournira :
- Méthode RPC appelée
GetFeature
que le serveur implémente et que le client appelle. - Les types de messages
Point
etFeature
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'unPoint
dans sa requêteGetFeature
au serveur, et le serveur répond avec unFeature
correspondant 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 routeguide/route_guide.proto
du code source fourni.
Les tampons de protocole sont communément appelés "protobufs". Pour en savoir plus sur la terminologie gRPC, consultez Concepts fondamentaux, architecture et cycle de vie de gRPC.
Types de messages
Dans le fichier routeguide/route_guide.proto
du code source, définissez d'abord le type de message Point
. Un Point
représente une paire de coordonnées (latitude et longitude) sur une carte. Pour cet atelier de programmation, utilisez des nombres entiers pour les coordonnées :
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Les nombres 1
et 2
sont des ID uniques pour chacun des champs de la structure message
.
Ensuite, définissez le type de message Feature
. Un 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;
}
Méthode de service
Le fichier route_guide.proto
possède 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 recherche 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 un 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 où le client envoie une requête au serveur et attend une réponse, comme pour un appel de fonction local.
4. Générer le code client et serveur
Ensuite, générez le code gRPC standard pour le client et le serveur à partir du fichier .proto
à l'aide du compilateur de tampons de protocole. Dans le répertoire routeguide
, exécutez la commande suivante :
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
Cette commande génère les fichiers suivants :
route_guide.pb.go
, qui contient des fonctions permettant de créer les types de messages de l'application et d'accéder à leurs données.route_guide_grpc.pb.go
, qui contient les fonctions utilisées par le client pour appeler la méthode gRPC à distance du service, ainsi que les fonctions utilisées par le serveur pour fournir ce service à distance.
Ensuite, nous allons implémenter la méthode GetFeature
côté serveur, afin que lorsque le client envoie une requête, le serveur puisse y répondre.
5. Implémenter le service
La fonction GetFeature
côté serveur est l'endroit où le gros du travail 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 server/server.go
:
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
for _, feature := range s.savedFeatures {
if proto.Equal(feature.Location, point) {
return feature, nil
}
}
// No feature was found, return an unnamed feature
return &pb.Feature{Location: point}, nil
}
Lorsque cette méthode est appelée à la suite d'une requête d'un client distant, la fonction reçoit un objet Context
décrivant l'appel RPC et un objet de tampon de protocole Point
provenant de cette requête client. La fonction renvoie un objet de tampon de protocole Feature
pour le lieu recherché et un error
si nécessaire.
Dans la méthode, remplissez un objet Feature
avec les informations appropriées pour le Point
donné, puis return
-le avec une erreur nil
pour indiquer à gRPC que vous avez terminé de traiter le RPC et que l'objet Feature
peut être renvoyé au client.
La méthode GetFeature
nécessite la création et l'enregistrement d'un objet routeGuideServer
afin que les requêtes des clients pour les recherches de localisation puissent être acheminées vers cette fonction. Pour ce faire, utilisez main()
:
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
s := &routeGuideServer{}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)
}
Voici ce qui se passe dans main()
, étape par étape :
- Spécifiez le port TCP à utiliser pour écouter les requêtes des clients distants à l'aide de
lis, err := net.Listen(...)
. Par défaut, l'application utilise le port TCP50051
, comme spécifié par la variableport
ou en transmettant le commutateur--port
sur la ligne de commande lors de l'exécution du serveur. Si le port TCP ne peut pas être ouvert, l'application se termine avec une erreur fatale. - Créez une instance du serveur gRPC à l'aide de
grpc.NewServer(...)
, en nommant cette instancegrpcServer
. - Créez un pointeur vers
routeGuideServer
, une structure représentant le service d'API de l'application, en nommant le pointeurs.
. - Utilisez
s.loadFeatures()
pour remplir le tableaus.savedFeatures
avec les lieux qui peuvent être recherchés viaGetFeature
. - Enregistrez le service d'API auprès du serveur gRPC afin que les appels RPC à
GetFeature
soient routés vers la fonction appropriée. - Appelez
Serve()
sur le serveur avec les détails de notre port pour effectuer une attente bloquante des requêtes client. Cette opération se poursuit jusqu'à ce que le processus soit arrêté ou queStop()
soit appelé.
La fonction loadFeatures()
obtient ses mappages de coordonnées vers des lieux à partir de server/testdata.go
.
6. Créer le client
Modifiez maintenant client/client.go
, qui est l'endroit où vous implémenterez le code client.
Pour appeler les méthodes du service à distance, nous devons d'abord créer un canal gRPC pour communiquer avec le serveur. Pour ce faire, nous transmettons la chaîne URI cible du serveur (qui, dans ce cas, est simplement l'adresse et le numéro de port) à grpc.NewClient()
dans la fonction main()
du client, comme suit :
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
L'adresse du serveur, définie par la variable serverAddr
, est localhost:50051
par défaut. Elle peut être remplacée par le commutateur --addr
sur la ligne de commande lors de l'exécution du client.
Si le client doit se connecter à un service qui nécessite des identifiants d'authentification, tels que des identifiants TLS ou JWT, il peut transmettre un objet DialOptions
en tant que paramètre à grpc.NewClient
contenant les identifiants requis. Le service RouteGuide
ne nécessite aucun identifiant.
Une fois le canal gRPC configuré, nous avons besoin d'un stub client pour effectuer des RPC via des appels de fonction Go. Nous obtenons ce stub à l'aide de la méthode NewRouteGuideClient
fournie par le fichier route_guide_grpc.pb.go
généré à partir du fichier .proto
de l'application.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
Appeler les méthodes de service
Dans gRPC-Go, les RPC fonctionnent en mode bloquant/synchrone, ce qui signifie que l'appel RPC attend la réponse du serveur et renvoie une réponse ou une erreur.
RPC simple
L'appel du RPC simple GetFeature
est presque aussi simple que l'appel d'une méthode locale, en l'occurrence client.GetFeature
:
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
Le client appelle la méthode sur le stub créé précédemment. Pour les paramètres de la méthode, le client crée et remplit un objet de tampon de protocole de requête Point
. Vous transmettez également un objet context.Context
qui nous permet de modifier le comportement de notre RPC si nécessaire, par exemple en définissant un délai pour l'appel ou en annulant un RPC en cours. Si l'appel ne renvoie pas d'erreur, le client peut lire les informations de réponse du serveur à partir de la première valeur renvoyée :
log.Println(feature)
Au total, la fonction main()
du client devrait se présenter comme suit :
func main() {
flag.Parse()
// Set up a connection to the gRPC server.
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
// Create a new RouteGuide stub.
client := pb.NewRouteGuideClient(conn)
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
log.Println(feature)
}
7. Essayer
Vérifiez que le serveur et le client fonctionnent correctement ensemble en exécutant les commandes suivantes dans le répertoire de travail de l'application :
- Exécutez le serveur dans un terminal :
cd server go run .
- Exécutez le client à partir d'un autre terminal :
cd client go run .
Un résultat semblable à celui-ci s'affiche (les codes temporels sont omis pour plus de clarté) :
Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>
8. Étape suivante
- Découvrez comment fonctionne gRPC dans Présentation de gRPC et Concepts de base.
- Suivez le tutoriel sur les principes de base.
- Consultez la documentation de référence sur l'API.