Tổng quan về Restful API và gRPC
Restful API: Khi sử dụng các API REST, phản hồi từ dữ liệu back-end được chuyển đến các máy khách (client) thông qua định dạng nhắn tin JSON hoặc XML . Công nghệ này sử dụng giao thức HTTP
gRPC: gRPC(Google Remote Process Call) client cần server thực hiện tính toán hoặc trả về một thông tin cụ thể nào đó. Bản chất giống y như ta đang gọi hàm, chỉ là hàm đó ở máy chủ khác hoặc service khác. Công nghệ này sử dụng giao thức HTTP 2.
Vấn đề
Một hạn chế với gRPC là không phải nền tảng nào cũng có thể sử dụng nó. Các trình duyệt không hỗ trợ đầy đủ HTTP 2, làm cho REST và JSON trở thành cách chính để tải dữ liệu vào các ứng dụng trình duyệt.
Ngay cả với những lợi ích mà gRPC mang lại, REST và JSON vẫn có một vị trí quan trọng trong các ứng dụng hiện nay. Vì vậy việc xây dựng 1 service cung cấp cả giao thức grpc HTTP 2 và Restful API HTTP là điều hoàn toàn có thể xảy ra.
Ở đây mình có tham khảo một repo GIT có hơn 11k start. Theo mình hiểu thì khi gRPC cung cấp giao thức HTTP 2 ra thì chúng ta cần thêm 1 gRPC-Gateway để convert cái giao thức HTTP 2 đấy về HTTP rồi cung cấp các URL như làm Restful API thôi. Các contributor đã đưa cách làm này chạy mượt mà từ năm 2018. Nên chắc không cần lo lắng về tính đúng đắn của nó đâu!
- Mô hình phát triển
Triển khai
- Tạo go mudule
go mod init github.com/your-git/grpc-gateway-demo
- Copy thư mục google từ về folder dự án của bạn
- Tạo thư mục proto và thêm file
service.proto
chứa những định nghĩa request, response và các service bạn muốn implement.
syntax = "proto3"; package grpc_gateway_demo; option go_package = "/.;proto"; import "google/api/annotations.proto"; message StringMessage { string value = 1;
} message SumRequest { int32 num1 = 1; int32 num2 = 2;
} message SumResponse { int32 result = 1;
} service MyService { rpc Echo(StringMessage) returns (StringMessage) { option (google.api.http) = { post: "/v1/echo" body: "*" }; }; rpc Sum(SumRequest) returns (SumResponse) { option (google.api.http) = { get: "/v1/sum" }; }
}
- Tiếp theo chúng ta cần chạy protoc để gen gRPC stubs và reverse-proxy
- Trước tiên tạo file tools.go
//go:build tools
// +build tools package tools import ( _ "github.com/golang/protobuf/protoc-gen-go" _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway"
)
- Run terminal
go mod tidy
- Install tools
$ go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger go install github.com/golang/protobuf/protoc-gen-go
- Run protoc để gen
service.pb.go
protoc --go_out=. --go_opt plugins=grpc --go_opt paths=source_relative .\proto\service.proto
service.pb.gw.go
protoc --grpc-gateway_out=. --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative .\proto\service.proto
- Tạo file main.go để implement các service trong file proto vừa tạo và chạy server
package main import ( "context" "fmt" "log" "net" "github.com/dainh-2247/grpc-gateway-demo/proto" "google.golang.org/grpc"
) type server struct{} // Echo
func (*server) Echo(ctx context.Context, req *proto.StringMessage) (*proto.StringMessage, error) { log.Printf("receive message %s\n", req.GetValue()) return req, nil
} // Sum
func (*server) Sum(ctx context.Context, req *proto.SumRequest) (*proto.SumResponse, error) { log.Printf("Sum is called...") resp := &proto.SumResponse{ Result: req.GetNum1() + req.GetNum2(), } return resp, nil
} func main() { lis, err := net.Listen("tcp", "0.0.0.0:3000") if err != nil { log.Fatalf("err while create listen %v", err) } s := grpc.NewServer() proto.RegisterMyServiceServer(s, &server{}) fmt.Printf("server is running...") err = s.Serve(lis) if err != nil { log.Fatalf("err while serve %v", err) }
}
-
2 func Echo và Sum là mk implement từ file proto
-
Func Echo "kiểu" POST thì mk sẽ lấy value từ request body rồi trả về đúng cái value đấy
-
Func Sum "kiểu" GET thì mk sẽ tính tổng của 2 số từ param
-
Server chạy trên localhost:3000
-
Tạo thư mục proxy chưa file proxy.go
package main import ( "context" "flag" "log" "net/http" "github.com/golang/glog" "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" gw "github.com/dainh-2247/grpc-gateway-demo/proto" // Update
) var ( // command-line options: // gRPC server endpoint grpcServerEndpoint = flag.String("grpc-server-endpoint", "localhost:3000", "gRPC server endpoint")
) func run() error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() // Register gRPC server endpoint // Note: Make sure the gRPC server is running properly and accessible mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} err := gw.RegisterMyServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts) if err != nil { return err } // Start HTTP server (and proxy calls to gRPC server endpoint) return http.ListenAndServe(":8081", mux)
} func main() { flag.Parse() defer glog.Flush() log.Printf("proxy is running...") if err := run(); err != nil { glog.Fatal(err) }
}
- Lưu ý ở khai báo
grpcServerEndpoint = flag.String("grpc-server-endpoint", "localhost:3000", "gRPC server endpoint")
parameter thứ 2 là url mà server cung cấp. Ở đây là localhost:3000 - Port để chạy gateway là 8081.
Lúc này thì thư mục sẽ như thế này
- Run server và proxy
go run .\main.go
go run .\proxy\proxy.go
-
Dùng postman để test
-
POST
- GET
- Chạy khá là ok đấy nhỉ :v
Kết Luận
- Thực ra cách cung cấp cả Restful API HTTP và gRPC HTTP 2 trên GIT các contributor đang triển khai 1 version mới. Version mà mình triển khai là v1 version cũ.
- Nhưng mình nghĩ hiểu từ cái căn bản nhất rồi tới những phần nâng cao hơn, mới hơn cũng tốt phải không
- Ở lần sau mk sẽ triển khai theo version mới để phục vụ bạn đọc.
- Link source code của mk Cảm ơn các bạn đã theo dõi.