feat: support google.api.http in gateway (#2161)

master
Kevin Wan 2 years ago committed by GitHub
parent 4324ddc024
commit 0dd2768d09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -31,6 +31,8 @@ type (
Grpc zrpc.RpcClientConf Grpc zrpc.RpcClientConf
// ProtoSet is the file of proto set, like hello.pb // ProtoSet is the file of proto set, like hello.pb
ProtoSet string `json:",optional"` ProtoSet string `json:",optional"`
Mapping []mapping // Mapping is the mapping between gateway routes and upstream rpc methods.
// Keep it blank if annotations are added in rpc methods.
Mapping []mapping `json:",optional"`
} }
) )

@ -2,19 +2,29 @@ package internal
import ( import (
"fmt" "fmt"
"net/http"
"strings"
"github.com/fullstorydev/grpcurl" "github.com/fullstorydev/grpcurl"
"github.com/jhump/protoreflect/desc" "github.com/jhump/protoreflect/desc"
"google.golang.org/genproto/googleapis/api/annotations"
"google.golang.org/protobuf/proto"
) )
type Method struct {
HttpMethod string
HttpPath string
RpcPath string
}
// GetMethods returns all methods of the given grpcurl.DescriptorSource. // GetMethods returns all methods of the given grpcurl.DescriptorSource.
func GetMethods(source grpcurl.DescriptorSource) ([]string, error) { func GetMethods(source grpcurl.DescriptorSource) ([]Method, error) {
svcs, err := source.ListServices() svcs, err := source.ListServices()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var methods []string var methods []Method
for _, svc := range svcs { for _, svc := range svcs {
d, err := source.FindSymbol(svc) d, err := source.FindSymbol(svc)
if err != nil { if err != nil {
@ -25,10 +35,68 @@ func GetMethods(source grpcurl.DescriptorSource) ([]string, error) {
case *desc.ServiceDescriptor: case *desc.ServiceDescriptor:
svcMethods := val.GetMethods() svcMethods := val.GetMethods()
for _, method := range svcMethods { for _, method := range svcMethods {
methods = append(methods, fmt.Sprintf("%s/%s", svc, method.GetName())) rpcPath := fmt.Sprintf("%s/%s", svc, method.GetName())
ext := proto.GetExtension(method.GetMethodOptions(), annotations.E_Http)
if ext == nil {
methods = append(methods, Method{
RpcPath: rpcPath,
})
continue
}
httpExt, ok := ext.(*annotations.HttpRule)
if !ok {
methods = append(methods, Method{
RpcPath: rpcPath,
})
continue
}
switch rule := httpExt.GetPattern().(type) {
case *annotations.HttpRule_Get:
methods = append(methods, Method{
HttpMethod: http.MethodGet,
HttpPath: adjustHttpPath(rule.Get),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Post:
methods = append(methods, Method{
HttpMethod: http.MethodPost,
HttpPath: adjustHttpPath(rule.Post),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Put:
methods = append(methods, Method{
HttpMethod: http.MethodPut,
HttpPath: adjustHttpPath(rule.Put),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Delete:
methods = append(methods, Method{
HttpMethod: http.MethodDelete,
HttpPath: adjustHttpPath(rule.Delete),
RpcPath: rpcPath,
})
case *annotations.HttpRule_Patch:
methods = append(methods, Method{
HttpMethod: http.MethodPatch,
HttpPath: adjustHttpPath(rule.Patch),
RpcPath: rpcPath,
})
default:
methods = append(methods, Method{
RpcPath: rpcPath,
})
}
} }
} }
} }
return methods, nil return methods, nil
} }
func adjustHttpPath(path string) string {
path = strings.ReplaceAll(path, "{", ":")
path = strings.ReplaceAll(path, "}", "")
return path
}

@ -25,5 +25,9 @@ func TestGetMethods(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
methods, err := GetMethods(source) methods, err := GetMethods(source)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []string{"hello.Hello/Ping"}, methods) assert.EqualValues(t, []Method{
{
RpcPath: "hello.Hello/Ping",
},
}, methods)
} }

@ -66,11 +66,21 @@ func (s *Server) build() error {
return return
} }
resolver := grpcurl.AnyResolverFromDescriptorSource(source)
for _, m := range methods {
if len(m.HttpMethod) > 0 && len(m.HttpPath) > 0 {
writer.Write(rest.Route{
Method: m.HttpMethod,
Path: m.HttpPath,
Handler: s.buildHandler(source, resolver, cli, m.RpcPath),
})
}
}
methodSet := make(map[string]struct{}) methodSet := make(map[string]struct{})
for _, m := range methods { for _, m := range methods {
methodSet[m] = struct{}{} methodSet[m.RpcPath] = struct{}{}
} }
resolver := grpcurl.AnyResolverFromDescriptorSource(source)
for _, m := range up.Mapping { for _, m := range up.Mapping {
if _, ok := methodSet[m.RpcPath]; !ok { if _, ok := methodSet[m.RpcPath]; !ok {
cancel(fmt.Errorf("rpc method %s not found", m.RpcPath)) cancel(fmt.Errorf("rpc method %s not found", m.RpcPath))
@ -80,7 +90,7 @@ func (s *Server) build() error {
writer.Write(rest.Route{ writer.Write(rest.Route{
Method: strings.ToUpper(m.Method), Method: strings.ToUpper(m.Method),
Path: m.Path, Path: m.Path,
Handler: s.buildHandler(source, resolver, cli, m), Handler: s.buildHandler(source, resolver, cli, m.RpcPath),
}) })
} }
}, func(pipe <-chan interface{}, cancel func(error)) { }, func(pipe <-chan interface{}, cancel func(error)) {
@ -92,7 +102,7 @@ func (s *Server) build() error {
} }
func (s *Server) buildHandler(source grpcurl.DescriptorSource, resolver jsonpb.AnyResolver, func (s *Server) buildHandler(source grpcurl.DescriptorSource, resolver jsonpb.AnyResolver,
cli zrpc.Client, m mapping) func(http.ResponseWriter, *http.Request) { cli zrpc.Client, rpcPath string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
handler := &grpcurl.DefaultEventHandler{ handler := &grpcurl.DefaultEventHandler{
Out: w, Out: w,
@ -110,7 +120,7 @@ func (s *Server) buildHandler(source grpcurl.DescriptorSource, resolver jsonpb.A
defer can() defer can()
w.Header().Set(httpx.ContentType, httpx.JsonContentType) w.Header().Set(httpx.ContentType, httpx.JsonContentType)
if err := grpcurl.InvokeRPC(ctx, source, cli.Conn(), m.RpcPath, internal.BuildHeaders(r.Header), if err := grpcurl.InvokeRPC(ctx, source, cli.Conn(), rpcPath, internal.BuildHeaders(r.Header),
handler, parser.Next); err != nil { handler, parser.Next); err != nil {
httpx.Error(w, err) httpx.Error(w, err)
} }

Loading…
Cancel
Save