Menulis Blog Microservice - Bagian 2 dari API Gateway

Pada bagian pertama dari seri artikel kami "Menulis Blog di Layanan Mikro," kami menggambarkan pendekatan umum untuk memecahkan masalah.

Sekarang giliran API Gateway atau GW API.

Dalam API GW c ptimofeev kami, kami menerapkan fungsi-fungsi berikut:

  • Mengonversi permintaan REST ke permintaan gRPC dan sebaliknya.
  • Meminta Logging.
  • Minta Otentikasi
  • Penugasan ID Jejak untuk setiap permintaan untuk transfer lebih lanjut antara layanan microser sepanjang seluruh rantai eksekusi permintaan.

Jadi ayo pergi ...

Untuk mengimplementasikan fungsi konversi REST / gRPC, kami akan menggunakan grosh-gateway perpustakaan gosh.

Lebih lanjut, dalam protofile dari setiap layanan Microsoft yang ingin kami terbitkan di REST, Anda perlu menambahkan deskripsi opsi di bagian deskripsi antarmuka layanan. Sebenarnya menentukan jalur dan metode yang digunakan akses REST akan dilakukan.

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

Berdasarkan informasi ini, skrip pembuatan kode (./bin/protogen.sh) akan membuat kode server gRPC (dalam direktori microservice), klien gRPC (dalam direktori api-gw) dan menghasilkan dokumentasi API terbaru (dalam format {{nama layanan}}. swagger.json)

Selanjutnya, kita perlu menulis kode Proxy HTTP, yang di satu sisi akan menjadi server HTTP (untuk memproses permintaan REST), dan di sisi lain itu akan menjadi klien gRPC untuk layanan Microsoft kami (server gRPC).

Kami akan menempatkan kode ini di file ./services/api-gw/main.go.

Pertama, di bagian impor, kami menghubungkan perpustakaan klien ke layanan microser kami
(protogen.sh membuatkannya untuk kami):

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

Selanjutnya, kami menunjukkan alamat dan port tempat layanan gRPC kami “hang” (kami mengambil nilai dari variabel lingkungan):

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

Dan akhirnya, kami menerapkan HTTP Proxy itu sendiri:

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

Dalam mengatur koneksi ke layanan-layanan microser, kami menggunakan opsi grpc.DenganUnaryInterceptor (AccessLogInterceptor), di mana kami melewati fungsi AccessLogInterceptor sebagai parameter. Ini tidak lebih dari implementasi lapisan middleware, yaitu fungsi AccessLogInterceptor akan dieksekusi dengan setiap panggilan gRPC dari microservice anak.

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

Pada gilirannya, dalam fungsi AccessLogInterceptor, kami sudah menerapkan mekanisme otentikasi, pencatatan, dan pembuatan TraceId.

Jika atribut otorisasi ditentukan dalam Header dalam permintaan masuk (REST), maka kami mengurai dan memvalidasinya dalam fungsi CheckGetJWTToken, yang mengembalikan kesalahan atau, jika berhasil, mengembalikan UserId dan 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"]) } } 

Selanjutnya, kita membentuk TraceId dan membungkusnya dengan UserId dan UserRole dalam konteks panggilan dan melakukan panggilan gRPC dari layanan Microsoft kami.

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

Dan akhirnya, kami menulis acara panggilan layanan ke log.

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

Prosesor middleware lain adalah "menggantung" pada jawaban metode tertentu (Masuk, Daftar) dari layanan Pengguna. Handler ini mencegat respons gRPC, mengambil respons UserID dan UserRole, mengubahnya menjadi JWT Token dan memberikannya (JWT Token) dalam respons REST sebagai Header atribut "Otorisasi". Kode middleware yang dijelaskan diimplementasikan pada sisi klien gRPC dalam file ./api-gw/services/user/protobuf/functions.go.

Kami menghubungkan penangan respons.

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

Contohnya adalah penangan respons SignIn (handler Signup serupa).

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

Dilanjutkan ...

Ya, demo proyek dapat dilihat di sini , dan kode sumbernya ada di sini .

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


All Articles