golang project component grpc-gateway-yaml defines http rules and customizes gateway routing

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