Rédaction d'un blog sur les microservices - Partie 2 de la passerelle API

Dans la première partie de notre série d'articles «Rédaction d'un blog sur les microservices», nous avons décrit une approche générale pour résoudre le problème.

C'est maintenant au tour de l'API Gateway ou de l'API GW.

Dans notre API c ptimofeev GW, nous implémentons les fonctions suivantes:

  • Conversion des demandes REST en demandes gRPC et vice versa.
  • Journalisation des demandes.
  • Demande d'authentification
  • Affectation d'un identifiant de trace à chaque demande pour son transfert ultérieur entre les microservices tout au long de la chaîne d'exécution de la demande.

Alors allons-y ...

Pour implémenter la fonction de conversion REST / gRPC, nous utiliserons la bibliothèque gosh grpc-gateway .

De plus, dans le profil de chaque microservice que nous voulons publier sur REST, vous devez ajouter une description d'option dans la section description des interfaces de service. Il spécifie en fait le chemin et la méthode par lesquels l'accès REST sera effectué.

//  Category service CategoryService { //  rpc Create (CreateCategoryRequest) returns (CreateCategoryResponse) { option (google.api.http) = { post: "/api/v1/category" }; } } 

Sur la base de ces informations, le script de génération de code (./bin/protogen.sh) créera le code du serveur gRPC (dans le répertoire du microservice), le client gRPC (dans le répertoire api-gw) et générera la dernière documentation API (au format {{nom du service}}). swagger.json)

Ensuite, nous devons écrire du code proxy HTTP, qui d'une part sera un serveur HTTP (pour le traitement des demandes REST), et d'autre part ce sera un client gRPC pour nos microservices (serveurs gRPC).

Nous placerons ce code dans le fichier ./services/api-gw/main.go.

Tout d'abord, dans la section importation, nous connectons les bibliothèques clientes à nos microservices
(protogen.sh les a générés pour nous):

 import ( … userService "./services/user/protobuf" postService "./services/post/protobuf" commentService "./services/comment/protobuf" categoryService "./services/category/protobuf" 

Ensuite, nous indiquons les adresses et les ports sur lesquels nos services gRPC «se bloquent» (nous prenons les valeurs des variables d'environnement):

 var ( // gRPC services userServerAdress=fmt.Sprintf("%s:%s",os.Getenv("USER_HOST"),os.Getenv("USER_PORT")) postServerAdress=fmt.Sprintf("%s:%s",os.Getenv("POST_HOST"),os.Getenv("POST_PORT")) commentServerAdress=fmt.Sprintf("%s:%s",os.Getenv("COMMENT_HOST"),os.Getenv("COMMENT_PORT")) categoryServerAdress=fmt.Sprintf("%s:%s",os.Getenv("CATEGORY_HOST"),os.Getenv("CATEGORY_PORT")) ) 

Et enfin, nous implémentons le proxy HTTP lui-même:

 func HTTPProxy(proxyAddr string){ grpcGwMux:=runtime.NewServeMux() //---------------------------------------------------------------- //     gRPC //---------------------------------------------------------------- //   User grpcUserConn, err:=grpc.Dial( userServerAdress, grpc.WithInsecure(), ) if err!=nil{ log.Fatalln("Failed to connect to User service", err) } defer grpcUserConn.Close() err = userService.RegisterUserServiceHandler( context.Background(), grpcGwMux, grpcUserConn, ) if err!=nil{ log.Fatalln("Failed to start HTTP server", err) } //---------------------------------------------------------------- //   Post grpcPostConn, err:=grpc.Dial( postServerAdress, grpc.WithUnaryInterceptor(AccessLogInterceptor), grpc.WithInsecure(), ) if err!=nil{ log.Fatalln("Failed to connect to Post service", err) } defer grpcPostConn.Close() err = postService.RegisterPostServiceHandler( context.Background(), grpcGwMux, grpcPostConn, ) if err!=nil{ log.Fatalln("Failed to start HTTP server", err) } //---------------------------------------------------------------- //   Comment grpcCommentConn, err:=grpc.Dial( commentServerAdress, grpc.WithInsecure(), ) if err!=nil{ log.Fatalln("Failed to connect to Comment service", err) } defer grpcCommentConn.Close() err = commentService.RegisterCommentServiceHandler( context.Background(), grpcGwMux, grpcCommentConn, ) if err!=nil{ log.Fatalln("Failed to start HTTP server", err) } //---------------------------------------------------------------- //   Category grpcCategoryConn, err:=grpc.Dial( categoryServerAdress, grpc.WithInsecure(), ) if err!=nil{ log.Fatalln("Failed to connect to Category service", err) } defer grpcCategoryConn.Close() err = categoryService.RegisterCategoryServiceHandler( context.Background(), grpcGwMux, grpcCategoryConn, ) if err!=nil{ log.Fatalln("Failed to start HTTP server", err) } //---------------------------------------------------------------- //     REST //---------------------------------------------------------------- mux:=http.NewServeMux() mux.Handle("/api/v1/",grpcGwMux) mux.HandleFunc("/",helloworld) fmt.Println("starting HTTP server at "+proxyAddr) log.Fatal(http.ListenAndServe(proxyAddr,mux)) } 

Pour configurer la connexion aux microservices, nous utilisons l'option grpc.WithUnaryInterceptor (AccessLogInterceptor), dans laquelle nous transmettons la fonction AccessLogInterceptor comme paramètre. Ce n'est rien de plus qu'une implémentation de la couche middleware, c'est-à-dire la fonction AccessLogInterceptor sera exécutée avec chaque appel gRPC du microservice enfant.

 //---------------------------------------------------------------- //   Post grpcPostConn, err:=grpc.Dial( … grpc.WithUnaryInterceptor(AccessLogInterceptor), … ) 

À son tour, dans la fonction AccessLogInterceptor, nous implémentons déjà des mécanismes d'authentification, de journalisation et de génération TraceId.

Si l'attribut d'autorisation a été spécifié dans l'en-tête de la demande entrante (REST), nous l'analysons et le validons dans la fonction CheckGetJWTToken, qui renvoie une erreur ou, en cas de succès, renvoie UserId et UserRole.

 var traceId,userId,userRole string if len(md["authorization"])>0{ tokenString:= md["authorization"][0] if tokenString!=""{ err,token:=userService.CheckGetJWTToken(tokenString) if err!=nil{ return err } userId=fmt.Sprintf("%s",token["UserID"]) userRole=fmt.Sprintf("%s",token["UserRole"]) } } 

Ensuite, nous formons TraceId et l'enveloppons avec UserId et UserRole dans le contexte de l'appel et effectuons l'appel gRPC de notre microservice.

 // ID  traceId=fmt.Sprintf("%d",time.Now().UTC().UnixNano()) callContext:=context.Background() mdOut:=metadata.Pairs( "trace-id",traceId, "user-id",userId, "user-role",userRole, ) callContext=metadata.NewOutgoingContext(callContext,mdOut) err:=invoker(callContext,method,req,reply,cc, opts...) 

Et enfin, nous écrivons un événement d'appel de service dans le journal.

 msg:=fmt.Sprintf("Call:%v, traceId: %v, userId: %v, userRole: %v, time: %v", method,traceId,userId,userRole,time.Since(start)) app.AccesLog(msg) 

Un autre processeur middleware «accroche» aux réponses de méthodes spécifiques (SignIn, SignUp) du service Utilisateur. Ce gestionnaire intercepte les réponses gRPC, récupère la réponse UserID et UserRole, la convertit en jeton JWT et la donne (jeton JWT) dans la réponse REST en tant qu'en-tête d'attribut «Autorisation». Le code du middleware décrit est implémenté côté client gRPC dans le fichier ./api-gw/services/user/protobuf/functions.go.

Nous connectons le gestionnaire de réponse.

 func init() { //     SignIn forward_UserService_SignIn_0 = forwardSignIn //     SignUp forward_UserService_SignUp_0 = forwardSignUp } 

Un exemple est le gestionnaire de réponses SignIn (le gestionnaire SignUp est similaire).

 func forwardSignIn(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) { // proto.Message  SignInResponse signInResponse:=&SignInResponse{} signInResponse.XXX_Merge(resp) token,err:=GetJWTToken(signInResponse.Slug,signInResponse.Role) if err!=nil{ http.Error(w, fmt.Sprintf("%v",err), http.StatusUnauthorized) return } w.Header().Set("authorization", token) runtime.ForwardResponseMessage(ctx, mux, marshaler, w, req, resp, opts...) } 

À suivre ...

Oui, la démo du projet peut être consultée ici , et le code source est ici .

Source: https://habr.com/ru/post/fr473516/


All Articles