كتابة مدونة Microservice - الجزء 2 من بوابة API

في الجزء الأول من سلسلة مقالاتنا "كتابة مدونة على Microservices" ، وصفنا مقاربة عامة لحل المشكلة.

الآن جاء دور API Gateway أو GW API.

في واجهة برمجة تطبيقات c ptimofeev GW الخاصة بنا ، نقوم بتنفيذ الوظائف التالية:

  • تحويل طلبات REST إلى طلبات gRPC والعكس.
  • طلب تسجيل.
  • طلب المصادقة
  • تعيين معرف تتبع لكل طلب لنقله بين خدمات micros على امتداد سلسلة تنفيذ الطلب بالكامل.

لذلك دعونا نذهب ...

لتنفيذ وظيفة تحويل REST / gRPC ، سنستخدم بوابة gpc الخاصة بمكتبة gosh.

علاوة على ذلك ، في الملف الأولي لكل خدمة ميكروية نريد نشرها على REST ، تحتاج إلى إضافة وصف خيار في قسم الوصف بواجهات الخدمة. وهي تحدد بالفعل المسار والأسلوب الذي سيتم به الوصول إلى REST.

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

استنادًا إلى هذه المعلومات ، سينشئ البرنامج النصي لإنشاء الشفرة (./bin/protogen.sh) رمز خادم gRPC (في دليل الخدمة المصغرة) ، عميل gRPC (في دليل api-gw) وإنشاء أحدث وثائق API (في {{اسم الخدمة}}). swagger.json)

بعد ذلك ، نحتاج إلى كتابة رمز HTTP Proxy ، والذي سيكون من ناحية خادم HTTP (لمعالجة طلبات REST) ​​، ومن ناحية أخرى ، سيكون عميل gRPC لخدماتنا المصغرة (خوادم gRPC).

سنضع هذا الرمز في الملف ./services/api-gw/main.go.

أولاً ، في قسم الاستيراد ، نقوم بتوصيل مكتبات العملاء إلى خدماتنا المصغرة
(protogen.sh إنشاؤها لهم بالنسبة لنا):

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

بعد ذلك ، نشير إلى العناوين والمنافذ التي "تعلق" خدمات gRPC الخاصة بنا (نأخذ القيم من متغيرات البيئة):

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

وأخيرًا ، ننفذ وكيل HTTP نفسه:

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

في إعداد الاتصال بخدمات microservices ، نستخدم خيار grpc.WithUnaryInterceptor (AccessLogInterceptor) ، والذي نمر فيه إلى وظيفة AccessLogInterceptor كمعلمة. هذا ليس أكثر من مجرد تطبيق لطبقة الوسيطة ، أي سيتم تنفيذ وظيفة AccessLogInterceptor مع كل مكالمة gRPC لجهاز microservice الفرعي.

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

بدوره ، في وظيفة AccessLogInterceptor ، نطبق بالفعل آليات إنشاء المصادقة وتسجيل الدخول وإنشاء TraceId.

إذا تم تحديد سمة التخويل في رأس الصفحة في الطلب الوارد (REST) ​​، فسنقوم بتحليلها والتحقق منها في وظيفة CheckGetJWTToken ، التي تُرجع خطأً أو تُرجع UserId و 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"]) } } 

بعد ذلك ، نقوم بتكوين TraceId ولفه مع UserId و UserRole في سياق المكالمة وننفذ استدعاء gRPC الخاص بالخدمة المصغرة الخاصة بنا.

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

وأخيراً ، نكتب حدث مكالمة خدمة إلى السجل.

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

هناك معالج وسيط آخر "معلق" على إجابات طرق محددة (تسجيل الدخول ، تسجيل الدخول) لخدمة المستخدم. يعترض هذا المعالج استجابات gRPC ، ويستلم استجابة UserID و UserRole ، ويحولها إلى JWT Token ويعطيها (JWT Token) في استجابة REST كرمز للسمة "Authorization". يتم تطبيق رمز الوسيطة الموصوفة على جانب عميل gRPC في الملف ./api-gw/services/user/protobuf/functions.go.

نحن ربط معالج الاستجابة.

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

مثال على ذلك هو معالج استجابة SignIn (يشبه معالج SignUp).

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

أن تستمر ...

نعم ، يمكن الاطلاع على العرض التوضيحي للمشروع هنا ، والكود المصدري موجود هنا .

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


All Articles