Escribir un blog de microservicios - Parte 2 de API Gateway

En la primera parte de nuestra serie de artículos "Escribir un blog en microservicios" , describimos un enfoque general para resolver el problema.

Ahora es el turno de API Gateway o GW API.

En nuestra API c ptimofeev GW, implementamos las siguientes funciones:

  • Conversión de solicitudes REST a solicitudes gRPC y viceversa.
  • Solicitar registro.
  • Solicitar autenticación
  • Asignación de una ID de seguimiento a cada solicitud para su posterior transferencia entre microservicios a lo largo de toda la cadena de ejecución de la solicitud.

Entonces vamos ...

Para implementar la función de conversión REST / gRPC, utilizaremos la biblioteca gosh grpc-gateway .

Además, en el protofile de cada microservicio que queremos publicar en REST, debe agregar una descripción de la opción en la sección de descripción de las interfaces de servicio. En realidad, especifica la ruta y el método por el cual se realizará el acceso REST.

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

Según esta información, el script de generación de código (./bin/protogen.sh) creará el código del servidor gRPC (en el directorio de microservicios), el cliente gRPC (en el directorio api-gw) y generará la última documentación de la API (en el formato {{nombre de servicio}}. swagger.json)

A continuación, debemos escribir el código de Proxy HTTP, que por un lado será un servidor HTTP (para procesar solicitudes REST) ​​y, por otro lado, será un cliente gRPC para nuestros microservicios (servidores gRPC).

Colocaremos este código en el archivo ./services/api-gw/main.go.

Primero, en la sección de importación, conectamos las bibliotecas del cliente a nuestros microservicios
(protogen.sh los generó para nosotros):

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

A continuación, indicamos las direcciones y puertos en los que nuestros servicios gRPC se "cuelgan" (tomamos los valores de las variables de entorno):

 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")) ) 

Y finalmente, implementamos el Proxy HTTP en sí:

 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)) } 

Al configurar la conexión a microservicios, utilizamos la opción grpc.WithUnaryInterceptor (AccessLogInterceptor), en la que pasamos la función AccessLogInterceptor como parámetro. Esto no es más que una implementación de la capa de middleware, es decir la función AccessLogInterceptor se ejecutará con cada llamada gRPC del microservicio secundario.

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

A su vez, en la función AccessLogInterceptor, ya implementamos mecanismos de autenticación, registro y generación de TraceId.

Si el atributo de autorización se especificó en el Encabezado en la solicitud entrante (REST), lo analizamos y lo validamos en la función CheckGetJWTToken, que devuelve un error o, si tiene éxito, devuelve UserId y 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"]) } } 

A continuación, formamos TraceId y lo agrupamos con UserId y UserRole en el contexto de la llamada y llevamos a cabo la llamada gRPC de nuestro microservicio.

 // 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...) 

Y finalmente, escribimos un evento de llamada de servicio en el registro.

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

Otro procesador de middleware está "colgando" de las respuestas de métodos específicos (SignIn, SignUp) del servicio de usuario. Este controlador intercepta las respuestas de gRPC, recoge la respuesta de UserID y UserRole, la convierte en JWT Token y la entrega (JWT Token) en la respuesta REST como el encabezado de atributo "Autorización". El código de middleware descrito se implementa en el lado del cliente gRPC en el archivo ./api-gw/services/user/protobuf/functions.go.

Conectamos el manejador de respuestas.

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

Un ejemplo es el controlador de respuesta SignIn (el controlador SignUp es similar).

 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...) } 

Continuará ...

Sí, la demostración del proyecto se puede ver aquí , y el código fuente está aquí .

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


All Articles