YAML defines http rules and custom implementation of gateway routing
Defining http rules in proto is always troublesome, because the proto file still defines messages, and the grpc interface is better. There is a better way to configure http rules. We can use yaml files to define http rules for the interface. At the same time, some interfaces are not as simple as just letting the gateway forward. Sometimes you need to define the gateway interface handler yourself.
yaml defines http rules
type: google.api.Service config_version: 3 http: rules: # {package}.{message}.{method} - selector: user.User.Get get: "/user/{id}" - selector: user.User.AddOrUpdate post: "/user" body: "*" additional_bindings: - put: "/user" body: "*" - patch: "/user" body: "addr" - selector: user.User.Delete delete: "/user/{id}"
proto file
syntax = "proto3"; package user; option go_package = "user/proto"; message Member{ int64 id = 1; string userName = 2[json_name = "user_name"]; int32 age = 3; string phone = 4; Addr addr = 5; } message Addr { string province = 1; string city = 2; string county = 3; } message UploadRequest { int64 size = 1; bytes content = 2; } message UploadResponse { string filePath= 1[json_name = "file_path"]; } service User{ rpc Get(Member) returns (Member) {} rpc AddOrUpdate(Member) returns (Member) { } rpc Delete(Member) returns (Member) {} rpc Upload(stream UploadRequest) returns (UploadResponse){} }
Generate message,grpc,gateway
# Generate message protoc --proto_path=proto --go_out=proto --go_opt=paths=source_relative proto/user.proto # Generate grpc service protoc --proto_path=proto --go-grpc_out=proto --go-grpc_opt=paths=source_relative proto/user.proto #Generate gateway protoc --proto_path=proto --grpc-gateway_out=proto --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative --grpc-gateway_opt grpc_api_configuration=proto/user.yaml proto/user.proto
Refer to the startup code in grpc-gateway entry to call the corresponding interface.
Customized implementation of gateway routing
After generating the gateway, we reserved a file upload grpc interface in the previous proto file. Then in yaml we do not define the corresponding http rules. Therefore, it is necessary to customize the corresponding gateway routing to deal with complex business situations.
gateway.go
Add custom processing routes and corresponding handler functions through mux.HandlePath
package gateway import ( "context" "flag" "google.golang.org/grpc/health/grpc_health_v1" "net/http" "user/user-server/gateway/middleware" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" gw "user/proto" ) var ( grpcServerEndpoint = flag.String("grpc-server-endpoint", "localhost:50051", "gRPC server endpoint") ) func Run() error {<!-- --> ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() inComingOpt := runtime.WithIncomingHeaderMatcher(func(s string) (string, bool) {<!-- --> switch s {<!-- --> case "Service-Authorization": return "service-authorization", true default: return "", false } return "", false }) //Create a connection for health check conn, err := grpc.Dial(*grpcServerEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil {<!-- --> return err } mux := runtime.NewServeMux(inComingOpt, runtime.WithHealthzEndpoint(grpc_health_v1.NewHealthClient(conn))) //Add custom processing function mux.HandlePath("POST", "/upload", uploadHandler) handler := middleware.Cors(mux) opts := []grpc.DialOption{<!-- -->grpc.WithTransportCredentials(insecure.NewCredentials())} err = gw.RegisterUserHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts) if err != nil {<!-- --> return err } return http.ListenAndServe(":8081", handler) }
upload.go
Write the handler that the corresponding gateway needs to register
package gateway import ( "context" "fmt" "github.com/golang/protobuf/jsonpb" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" "io" "net/http" "user/proto" ) func uploadHandler(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {<!-- --> serviceAuthorization := r.Header.Get("Service-Authorization") fmt.Println(serviceAuthorization) err := r.ParseForm() if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) return } f, header, err := r.FormFile("attachment") if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) return } defer f.Close() conn, err := grpc.Dial(*grpcServerEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) return } defer conn.Close() c := proto.NewUserClient(conn) ctx := context.Background() ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{<!-- -->"file_name": header.Filename, "service-authorization": serviceAuthorization})) stream, err := c.Upload(ctx) if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) return } buf := make([]byte, 100) for {<!-- --> n, err := f.Read(buf) if err != nil & amp; & amp; err != io.EOF {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) return } if n == 0 {<!-- --> break } stream.Send( & amp;proto.UploadRequest{<!-- --> Content: buf[:n], Size: int64(n), }) } res, err := stream.CloseAndRecv() if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) return } m := jsonpb.Marshaler{<!-- -->} str, err := m.MarshalToString(res) if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") fmt.Fprint(w, str) }
Just restart