1
0
mirror of https://github.com/go-micro/go-micro.git synced 2025-07-12 22:41:07 +02:00

Add grpc plugin (#2758)

* Add grpc plugin

* remove stale readmes
This commit is contained in:
Asim Aslam
2025-05-14 21:04:42 +01:00
committed by GitHub
parent 01b8394c81
commit 230505bf5a
40 changed files with 5527 additions and 0 deletions

186
server/grpc/codec.go Normal file
View File

@ -0,0 +1,186 @@
package grpc
import (
"encoding/json"
"strings"
b "bytes"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"go-micro.dev/v5/codec"
"go-micro.dev/v5/codec/bytes"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
"google.golang.org/grpc/metadata"
)
type jsonCodec struct{}
type bytesCodec struct{}
type protoCodec struct{}
type wrapCodec struct{ encoding.Codec }
var jsonpbMarshaler = &jsonpb.Marshaler{
EnumsAsInts: false,
EmitDefaults: false,
OrigName: true,
}
var (
defaultGRPCCodecs = map[string]encoding.Codec{
"application/json": jsonCodec{},
"application/proto": protoCodec{},
"application/protobuf": protoCodec{},
"application/octet-stream": protoCodec{},
"application/grpc": protoCodec{},
"application/grpc+json": jsonCodec{},
"application/grpc+proto": protoCodec{},
"application/grpc+bytes": bytesCodec{},
}
)
func (w wrapCodec) String() string {
return w.Codec.Name()
}
func (w wrapCodec) Marshal(v interface{}) ([]byte, error) {
b, ok := v.(*bytes.Frame)
if ok {
return b.Data, nil
}
return w.Codec.Marshal(v)
}
func (w wrapCodec) Unmarshal(data []byte, v interface{}) error {
b, ok := v.(*bytes.Frame)
if ok {
b.Data = data
return nil
}
if v == nil {
return nil
}
return w.Codec.Unmarshal(data, v)
}
func (protoCodec) Marshal(v interface{}) ([]byte, error) {
m, ok := v.(proto.Message)
if !ok {
return nil, codec.ErrInvalidMessage
}
return proto.Marshal(m)
}
func (protoCodec) Unmarshal(data []byte, v interface{}) error {
m, ok := v.(proto.Message)
if !ok {
return codec.ErrInvalidMessage
}
return proto.Unmarshal(data, m)
}
func (protoCodec) Name() string {
return "proto"
}
func (jsonCodec) Marshal(v interface{}) ([]byte, error) {
if pb, ok := v.(proto.Message); ok {
s, err := jsonpbMarshaler.MarshalToString(pb)
return []byte(s), err
}
return json.Marshal(v)
}
func (jsonCodec) Unmarshal(data []byte, v interface{}) error {
if len(data) == 0 {
return nil
}
if pb, ok := v.(proto.Message); ok {
return jsonpb.Unmarshal(b.NewReader(data), pb)
}
return json.Unmarshal(data, v)
}
func (jsonCodec) Name() string {
return "json"
}
func (bytesCodec) Marshal(v interface{}) ([]byte, error) {
b, ok := v.(*[]byte)
if !ok {
return nil, codec.ErrInvalidMessage
}
return *b, nil
}
func (bytesCodec) Unmarshal(data []byte, v interface{}) error {
b, ok := v.(*[]byte)
if !ok {
return codec.ErrInvalidMessage
}
*b = data
return nil
}
func (bytesCodec) Name() string {
return "bytes"
}
type grpcCodec struct {
// headers
id string
target string
method string
endpoint string
s grpc.ServerStream
c encoding.Codec
}
func (g *grpcCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
md, _ := metadata.FromIncomingContext(g.s.Context())
if m == nil {
m = new(codec.Message)
}
if m.Header == nil {
m.Header = make(map[string]string, len(md))
}
for k, v := range md {
m.Header[k] = strings.Join(v, ",")
}
m.Id = g.id
m.Target = g.target
m.Method = g.method
m.Endpoint = g.endpoint
return nil
}
func (g *grpcCodec) ReadBody(v interface{}) error {
// caller has requested a frame
if f, ok := v.(*bytes.Frame); ok {
return g.s.RecvMsg(f)
}
return g.s.RecvMsg(v)
}
func (g *grpcCodec) Write(m *codec.Message, v interface{}) error {
// if we don't have a body
if v != nil {
b, err := g.c.Marshal(v)
if err != nil {
return err
}
m.Body = b
}
// write the body using the framing codec
return g.s.SendMsg(&bytes.Frame{Data: m.Body})
}
func (g *grpcCodec) Close() error {
return nil
}
func (g *grpcCodec) String() string {
return "grpc"
}

16
server/grpc/context.go Normal file
View File

@ -0,0 +1,16 @@
package grpc
import (
"context"
"go-micro.dev/v5/server"
)
func setServerOption(k, v interface{}) server.Option {
return func(o *server.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

42
server/grpc/error.go Normal file
View File

@ -0,0 +1,42 @@
package grpc
import (
"net/http"
"go-micro.dev/v5/errors"
"google.golang.org/grpc/codes"
)
func microError(err *errors.Error) codes.Code {
switch err {
case nil:
return codes.OK
}
switch err.Code {
case http.StatusOK:
return codes.OK
case http.StatusBadRequest:
return codes.InvalidArgument
case http.StatusRequestTimeout:
return codes.DeadlineExceeded
case http.StatusNotFound:
return codes.NotFound
case http.StatusConflict:
return codes.AlreadyExists
case http.StatusForbidden:
return codes.PermissionDenied
case http.StatusUnauthorized:
return codes.Unauthenticated
case http.StatusPreconditionFailed:
return codes.FailedPrecondition
case http.StatusNotImplemented:
return codes.Unimplemented
case http.StatusInternalServerError:
return codes.Internal
case http.StatusServiceUnavailable:
return codes.Unavailable
}
return codes.Unknown
}

125
server/grpc/extractor.go Normal file
View File

@ -0,0 +1,125 @@
package grpc
import (
"fmt"
"reflect"
"strings"
"go-micro.dev/v5/registry"
)
func extractValue(v reflect.Type, d int) *registry.Value {
if d == 3 {
return nil
}
if v == nil {
return nil
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
arg := &registry.Value{
Name: v.Name(),
Type: v.Name(),
}
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
if f.PkgPath != "" {
continue
}
val := extractValue(f.Type, d+1)
if val == nil {
continue
}
// if we can find a json tag use it
if tags := f.Tag.Get("json"); len(tags) > 0 {
parts := strings.Split(tags, ",")
if parts[0] == "-" || parts[0] == "omitempty" {
continue
}
val.Name = parts[0]
}
// if there's no name default it
if len(val.Name) == 0 {
val.Name = v.Field(i).Name
}
arg.Values = append(arg.Values, val)
}
case reflect.Slice:
p := v.Elem()
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
arg.Type = "[]" + p.Name()
}
return arg
}
func extractEndpoint(method reflect.Method) *registry.Endpoint {
if method.PkgPath != "" {
return nil
}
var rspType, reqType reflect.Type
var stream bool
mt := method.Type
switch mt.NumIn() {
case 3:
reqType = mt.In(1)
rspType = mt.In(2)
case 4:
reqType = mt.In(2)
rspType = mt.In(3)
default:
return nil
}
// are we dealing with a stream?
switch rspType.Kind() {
case reflect.Func, reflect.Interface:
stream = true
}
request := extractValue(reqType, 0)
response := extractValue(rspType, 0)
ep := &registry.Endpoint{
Name: method.Name,
Request: request,
Response: response,
Metadata: make(map[string]string),
}
if stream {
ep.Metadata = map[string]string{
"stream": fmt.Sprintf("%v", stream),
}
}
return ep
}
func extractSubValue(typ reflect.Type) *registry.Value {
var reqType reflect.Type
switch typ.NumIn() {
case 1:
reqType = typ.In(0)
case 2:
reqType = typ.In(1)
case 3:
reqType = typ.In(2)
default:
return nil
}
return extractValue(reqType, 0)
}

View File

@ -0,0 +1,64 @@
package grpc
import (
"context"
"reflect"
"testing"
"go-micro.dev/v5/registry"
)
type testHandler struct{}
type testRequest struct{}
type testResponse struct{}
func (t *testHandler) Test(ctx context.Context, req *testRequest, rsp *testResponse) error {
return nil
}
func TestExtractEndpoint(t *testing.T) {
handler := &testHandler{}
typ := reflect.TypeOf(handler)
var endpoints []*registry.Endpoint
for m := 0; m < typ.NumMethod(); m++ {
if e := extractEndpoint(typ.Method(m)); e != nil {
endpoints = append(endpoints, e)
}
}
if i := len(endpoints); i != 1 {
t.Errorf("Expected 1 endpoint, have %d", i)
}
if endpoints[0].Name != "Test" {
t.Errorf("Expected handler Test, got %s", endpoints[0].Name)
}
if endpoints[0].Request == nil {
t.Error("Expected non nil request")
}
if endpoints[0].Response == nil {
t.Error("Expected non nil request")
}
if endpoints[0].Request.Name != "testRequest" {
t.Errorf("Expected testRequest got %s", endpoints[0].Request.Name)
}
if endpoints[0].Response.Name != "testResponse" {
t.Errorf("Expected testResponse got %s", endpoints[0].Response.Name)
}
if endpoints[0].Request.Type != "testRequest" {
t.Errorf("Expected testRequest type got %s", endpoints[0].Request.Type)
}
if endpoints[0].Response.Type != "testResponse" {
t.Errorf("Expected testResponse type got %s", endpoints[0].Response.Type)
}
}

36
server/grpc/go.mod Normal file
View File

@ -0,0 +1,36 @@
module go-micro.dev/v5/server/grpc
go 1.24
toolchain go1.24.1
require (
github.com/golang/protobuf v1.5.4
go-micro.dev/v5 v5.5.1-0.20250507183911-01b8394c8119
golang.org/x/net v0.23.0
google.golang.org/grpc v1.53.0
)
require (
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli/v2 v2.25.7 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)

99
server/grpc/go.sum Normal file
View File

@ -0,0 +1,99 @@
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go-micro.dev/v5 v5.5.1-0.20250507183911-01b8394c8119 h1:hKzFHTSwaYvPS0sTrWpUcS2HFFbJte//n91NMWFFk4k=
go-micro.dev/v5 v5.5.1-0.20250507183911-01b8394c8119/go.mod h1:pplSGFQk5So43OrzGAg1gH434XydWybOTyFhv/nR0pA=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

1039
server/grpc/grpc.go Normal file

File diff suppressed because it is too large Load Diff

66
server/grpc/handler.go Normal file
View File

@ -0,0 +1,66 @@
package grpc
import (
"reflect"
"go-micro.dev/v5/registry"
"go-micro.dev/v5/server"
)
type rpcHandler struct {
name string
handler interface{}
endpoints []*registry.Endpoint
opts server.HandlerOptions
}
func newRpcHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
options := server.HandlerOptions{
Metadata: make(map[string]map[string]string),
}
for _, o := range opts {
o(&options)
}
typ := reflect.TypeOf(handler)
hdlr := reflect.ValueOf(handler)
name := reflect.Indirect(hdlr).Type().Name()
var endpoints []*registry.Endpoint
for m := 0; m < typ.NumMethod(); m++ {
if e := extractEndpoint(typ.Method(m)); e != nil {
e.Name = name + "." + e.Name
for k, v := range options.Metadata[e.Name] {
e.Metadata[k] = v
}
endpoints = append(endpoints, e)
}
}
return &rpcHandler{
name: name,
handler: handler,
endpoints: endpoints,
opts: options,
}
}
func (r *rpcHandler) Name() string {
return r.name
}
func (r *rpcHandler) Handler() interface{} {
return r.handler
}
func (r *rpcHandler) Endpoints() []*registry.Endpoint {
return r.endpoints
}
func (r *rpcHandler) Options() server.HandlerOptions {
return r.opts
}

95
server/grpc/options.go Normal file
View File

@ -0,0 +1,95 @@
package grpc
import (
"context"
"crypto/tls"
"net"
"go-micro.dev/v5/broker"
"go-micro.dev/v5/codec"
"go-micro.dev/v5/logger"
"go-micro.dev/v5/registry"
"go-micro.dev/v5/server"
"go-micro.dev/v5/transport"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
)
type codecsKey struct{}
type grpcOptions struct{}
type netListener struct{}
type maxMsgSizeKey struct{}
type maxConnKey struct{}
type tlsAuth struct{}
type grpcServerKey struct{}
// gRPC Codec to be used to encode/decode requests for a given content type.
func Codec(contentType string, c encoding.Codec) server.Option {
return func(o *server.Options) {
codecs := make(map[string]encoding.Codec)
if o.Context == nil {
o.Context = context.Background()
}
if v, ok := o.Context.Value(codecsKey{}).(map[string]encoding.Codec); ok && v != nil {
codecs = v
}
codecs[contentType] = c
o.Context = context.WithValue(o.Context, codecsKey{}, codecs)
}
}
// AuthTLS should be used to setup a secure authentication using TLS.
func AuthTLS(t *tls.Config) server.Option {
return setServerOption(tlsAuth{}, t)
}
// MaxConn specifies maximum number of max simultaneous connections to server.
func MaxConn(n int) server.Option {
return setServerOption(maxConnKey{}, n)
}
// Listener specifies the net.Listener to use instead of the default.
func Listener(l net.Listener) server.Option {
return setServerOption(netListener{}, l)
}
// Server specifies a *grpc.Server to use instead of the default
// This is for rare use case where user need to expose grpc.Server for
// customization. Please NOTE however user injected grpcServer doesn't support
// server Handler abstraction.
func Server(srv *grpc.Server) server.Option {
return setServerOption(grpcServerKey{}, srv)
}
// Options to be used to configure gRPC options.
func Options(opts ...grpc.ServerOption) server.Option {
return setServerOption(grpcOptions{}, opts)
}
// MaxMsgSize set the maximum message in bytes the server can receive and
// send. Default maximum message size is 4 MB.
func MaxMsgSize(s int) server.Option {
return setServerOption(maxMsgSizeKey{}, s)
}
func newOptions(opt ...server.Option) server.Options {
opts := server.Options{
Codecs: make(map[string]codec.NewCodec),
Metadata: map[string]string{},
Broker: broker.DefaultBroker,
Registry: registry.DefaultRegistry,
RegisterCheck: server.DefaultRegisterCheck,
Transport: transport.DefaultTransport,
Address: server.DefaultAddress,
Name: server.DefaultName,
Id: server.DefaultId,
Version: server.DefaultVersion,
Logger: logger.DefaultLogger,
}
for _, o := range opt {
o(&opts)
}
return opts
}

90
server/grpc/request.go Normal file
View File

@ -0,0 +1,90 @@
package grpc
import (
"go-micro.dev/v5/codec"
"go-micro.dev/v5/codec/bytes"
)
type rpcRequest struct {
service string
method string
contentType string
codec codec.Codec
header map[string]string
body []byte
stream bool
payload interface{}
}
type rpcMessage struct {
topic string
contentType string
payload interface{}
header map[string]string
body []byte
codec codec.Codec
}
func (r *rpcRequest) ContentType() string {
return r.contentType
}
func (r *rpcRequest) Service() string {
return r.service
}
func (r *rpcRequest) Method() string {
return r.method
}
func (r *rpcRequest) Endpoint() string {
return r.method
}
func (r *rpcRequest) Codec() codec.Reader {
return r.codec
}
func (r *rpcRequest) Header() map[string]string {
return r.header
}
func (r *rpcRequest) Read() ([]byte, error) {
f := &bytes.Frame{}
if err := r.codec.ReadBody(f); err != nil {
return nil, err
}
return f.Data, nil
}
func (r *rpcRequest) Stream() bool {
return r.stream
}
func (r *rpcRequest) Body() interface{} {
return r.payload
}
func (r *rpcMessage) ContentType() string {
return r.contentType
}
func (r *rpcMessage) Topic() string {
return r.topic
}
func (r *rpcMessage) Payload() interface{} {
return r.payload
}
func (r *rpcMessage) Header() map[string]string {
return r.header
}
func (r *rpcMessage) Body() []byte {
return r.body
}
func (r *rpcMessage) Codec() codec.Reader {
return r.codec
}

27
server/grpc/response.go Normal file
View File

@ -0,0 +1,27 @@
package grpc
import (
"go-micro.dev/v5/codec"
)
type rpcResponse struct {
header map[string]string
codec codec.Codec
}
func (r *rpcResponse) Codec() codec.Writer {
return r.codec
}
func (r *rpcResponse) WriteHeader(hdr map[string]string) {
for k, v := range hdr {
r.header[k] = v
}
}
func (r *rpcResponse) Write(b []byte) error {
return r.codec.Write(&codec.Message{
Header: r.header,
Body: b,
}, nil)
}

182
server/grpc/server.go Normal file
View File

@ -0,0 +1,182 @@
package grpc
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// Meh, we need to get rid of this shit
import (
"context"
"errors"
"reflect"
"sync"
"unicode"
"unicode/utf8"
"go-micro.dev/v5/logger"
"go-micro.dev/v5/server"
)
var (
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
)
type methodType struct {
method reflect.Method
ArgType reflect.Type
ReplyType reflect.Type
ContextType reflect.Type
stream bool
}
type service struct {
name string // name of service
rcvr reflect.Value // receiver of methods for the service
typ reflect.Type // type of the receiver
method map[string]*methodType // registered methods
}
// server represents an RPC Server.
type rServer struct {
mu sync.Mutex // protects the serviceMap
serviceMap map[string]*service
logger logger.Logger
}
// Is this an exported - upper case - name?
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
// Is this type exported or a builtin?
func isExportedOrBuiltinType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}
// prepareEndpoint() returns a methodType for the provided method or nil
// in case if the method was unsuitable.
func prepareEndpoint(method reflect.Method, log logger.Logger) *methodType {
mtype := method.Type
mname := method.Name
var replyType, argType, contextType reflect.Type
var stream bool
// Endpoint() must be exported.
if method.PkgPath != "" {
return nil
}
switch mtype.NumIn() {
case 3:
// assuming streaming
argType = mtype.In(2)
contextType = mtype.In(1)
stream = true
case 4:
// method that takes a context
argType = mtype.In(2)
replyType = mtype.In(3)
contextType = mtype.In(1)
default:
log.Logf(logger.ErrorLevel, "method %v of %v has wrong number of ins: %v", mname, mtype, mtype.NumIn())
return nil
}
if stream {
// check stream type
streamType := reflect.TypeOf((*server.Stream)(nil)).Elem()
if !argType.Implements(streamType) {
log.Logf(logger.ErrorLevel, "%v argument does not implement Streamer interface: %v", mname, argType)
return nil
}
} else {
// if not stream check the replyType
// First arg need not be a pointer.
if !isExportedOrBuiltinType(argType) {
log.Logf(logger.ErrorLevel, "%v argument type not exported: %v", mname, argType)
return nil
}
if replyType.Kind() != reflect.Ptr {
log.Logf(logger.ErrorLevel, "method %v reply type not a pointer: %v", mname, replyType)
return nil
}
// Reply type must be exported.
if !isExportedOrBuiltinType(replyType) {
log.Logf(logger.ErrorLevel, "method %v reply type not exported: %v", mname, replyType)
return nil
}
}
// Endpoint() needs one out.
if mtype.NumOut() != 1 {
log.Logf(logger.ErrorLevel, "method %v has wrong number of outs: %v", mname, mtype.NumOut())
return nil
}
// The return type of the method must be error.
if returnType := mtype.Out(0); returnType != typeOfError {
log.Logf(logger.ErrorLevel, "method %v returns %v not error", mname, returnType.String())
return nil
}
return &methodType{method: method, ArgType: argType, ReplyType: replyType, ContextType: contextType, stream: stream}
}
func (server *rServer) register(rcvr interface{}) error {
server.mu.Lock()
defer server.mu.Unlock()
log := server.logger
if server.serviceMap == nil {
server.serviceMap = make(map[string]*service)
}
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
sname := reflect.Indirect(s.rcvr).Type().Name()
if sname == "" {
logger.Fatalf("rpc: no service name for type %v", s.typ.String())
}
if !isExported(sname) {
s := "rpc Register: type " + sname + " is not exported"
log.Log(logger.ErrorLevel, s)
return errors.New(s)
}
if _, present := server.serviceMap[sname]; present {
return errors.New("rpc: service already defined: " + sname)
}
s.name = sname
s.method = make(map[string]*methodType)
// Install the methods
for m := 0; m < s.typ.NumMethod(); m++ {
method := s.typ.Method(m)
if mt := prepareEndpoint(method, log); mt != nil {
s.method[method.Name] = mt
}
}
if len(s.method) == 0 {
s := "rpc Register: type " + sname + " has no exported methods of suitable type"
log.Log(logger.ErrorLevel, s)
return errors.New(s)
}
server.serviceMap[s.name] = s
return nil
}
func (m *methodType) prepareContext(ctx context.Context) reflect.Value {
if contextv := reflect.ValueOf(ctx); contextv.IsValid() {
return contextv
}
return reflect.Zero(m.ContextType)
}

38
server/grpc/stream.go Normal file
View File

@ -0,0 +1,38 @@
package grpc
import (
"context"
"go-micro.dev/v5/server"
"google.golang.org/grpc"
)
// rpcStream implements a server side Stream.
type rpcStream struct {
s grpc.ServerStream
request server.Request
}
func (r *rpcStream) Close() error {
return nil
}
func (r *rpcStream) Error() error {
return nil
}
func (r *rpcStream) Request() server.Request {
return r.request
}
func (r *rpcStream) Context() context.Context {
return r.s.Context()
}
func (r *rpcStream) Send(m interface{}) error {
return r.s.SendMsg(m)
}
func (r *rpcStream) Recv(m interface{}) error {
return r.s.RecvMsg(m)
}

291
server/grpc/subscriber.go Normal file
View File

@ -0,0 +1,291 @@
package grpc
import (
"context"
"fmt"
"reflect"
"runtime/debug"
"strings"
"go-micro.dev/v5/broker"
"go-micro.dev/v5/errors"
"go-micro.dev/v5/logger"
"go-micro.dev/v5/metadata"
"go-micro.dev/v5/registry"
"go-micro.dev/v5/server"
)
const (
subSig = "func(context.Context, interface{}) error"
)
type handler struct {
method reflect.Value
reqType reflect.Type
ctxType reflect.Type
}
type subscriber struct {
topic string
rcvr reflect.Value
typ reflect.Type
subscriber interface{}
handlers []*handler
endpoints []*registry.Endpoint
opts server.SubscriberOptions
}
func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOption) server.Subscriber {
options := server.SubscriberOptions{
AutoAck: true,
}
for _, o := range opts {
o(&options)
}
var endpoints []*registry.Endpoint
var handlers []*handler
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
h := &handler{
method: reflect.ValueOf(sub),
}
switch typ.NumIn() {
case 1:
h.reqType = typ.In(0)
case 2:
h.ctxType = typ.In(0)
h.reqType = typ.In(1)
}
handlers = append(handlers, h)
endpoints = append(endpoints, &registry.Endpoint{
Name: "Func",
Request: extractSubValue(typ),
Metadata: map[string]string{
"topic": topic,
"subscriber": "true",
},
})
} else {
hdlr := reflect.ValueOf(sub)
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
h := &handler{
method: method.Func,
}
switch method.Type.NumIn() {
case 2:
h.reqType = method.Type.In(1)
case 3:
h.ctxType = method.Type.In(1)
h.reqType = method.Type.In(2)
}
handlers = append(handlers, h)
endpoints = append(endpoints, &registry.Endpoint{
Name: name + "." + method.Name,
Request: extractSubValue(method.Type),
Metadata: map[string]string{
"topic": topic,
"subscriber": "true",
},
})
}
}
return &subscriber{
rcvr: reflect.ValueOf(sub),
typ: reflect.TypeOf(sub),
topic: topic,
subscriber: sub,
handlers: handlers,
endpoints: endpoints,
opts: options,
}
}
func validateSubscriber(sub server.Subscriber) error {
typ := reflect.TypeOf(sub.Subscriber())
var argType reflect.Type
if typ.Kind() == reflect.Func {
name := "Func"
switch typ.NumIn() {
case 2:
argType = typ.In(1)
default:
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
}
if typ.NumOut() != 1 {
return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
name, typ.NumOut(), subSig)
}
if returnType := typ.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
}
} else {
hdlr := reflect.ValueOf(sub.Subscriber())
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
switch method.Type.NumIn() {
case 3:
argType = method.Type.In(2)
default:
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
name, method.Name, method.Type.NumIn(), subSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("%v argument type not exported: %v", name, argType)
}
if method.Type.NumOut() != 1 {
return fmt.Errorf(
"subscriber %v.%v has wrong number of outs: %v require signature %s",
name, method.Name, method.Type.NumOut(), subSig)
}
if returnType := method.Type.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
}
}
}
return nil
}
func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broker.Handler {
return func(p broker.Event) (err error) {
defer func() {
if r := recover(); r != nil {
g.opts.Logger.Log(logger.ErrorLevel, "panic recovered: ", r)
g.opts.Logger.Log(logger.ErrorLevel, string(debug.Stack()))
err = errors.InternalServerError("go.micro.server", "panic recovered: %v", r)
}
}()
msg := p.Message()
// if we don't have headers, create empty map
if msg.Header == nil {
msg.Header = make(map[string]string)
}
ct := msg.Header["Content-Type"]
if len(ct) == 0 {
msg.Header["Content-Type"] = defaultContentType
ct = defaultContentType
}
cf, err := g.newGRPCCodec(ct)
if err != nil {
return err
}
hdr := make(map[string]string, len(msg.Header))
for k, v := range msg.Header {
hdr[k] = v
}
delete(hdr, "Content-Type")
ctx := metadata.NewContext(context.Background(), hdr)
results := make(chan error, len(sb.handlers))
for i := 0; i < len(sb.handlers); i++ {
handler := sb.handlers[i]
var isVal bool
var req reflect.Value
if handler.reqType.Kind() == reflect.Ptr {
req = reflect.New(handler.reqType.Elem())
} else {
req = reflect.New(handler.reqType)
isVal = true
}
if isVal {
req = req.Elem()
}
if err = cf.Unmarshal(msg.Body, req.Interface()); err != nil {
return err
}
fn := func(ctx context.Context, msg server.Message) error {
var vals []reflect.Value
if sb.typ.Kind() != reflect.Func {
vals = append(vals, sb.rcvr)
}
if handler.ctxType != nil {
vals = append(vals, reflect.ValueOf(ctx))
}
vals = append(vals, reflect.ValueOf(msg.Payload()))
returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil {
return rerr.(error)
}
return nil
}
for i := len(opts.SubWrappers); i > 0; i-- {
fn = opts.SubWrappers[i-1](fn)
}
if g.wg != nil {
g.wg.Add(1)
}
go func() {
if g.wg != nil {
defer g.wg.Done()
}
err := fn(ctx, &rpcMessage{
topic: sb.topic,
contentType: ct,
payload: req.Interface(),
header: msg.Header,
body: msg.Body,
})
results <- err
}()
}
var errors []string
for i := 0; i < len(sb.handlers); i++ {
if rerr := <-results; rerr != nil {
errors = append(errors, rerr.Error())
}
}
if len(errors) > 0 {
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
}
return err
}
}
func (s *subscriber) Topic() string {
return s.topic
}
func (s *subscriber) Subscriber() interface{} {
return s.subscriber
}
func (s *subscriber) Endpoints() []*registry.Endpoint {
return s.endpoints
}
func (s *subscriber) Options() server.SubscriberOptions {
return s.opts
}

49
server/grpc/util.go Normal file
View File

@ -0,0 +1,49 @@
package grpc
import (
"context"
"io"
"os"
"sync"
"google.golang.org/grpc/codes"
)
// convertCode converts a standard Go error into its canonical code. Note that
// this is only used to translate the error returned by the server applications.
func convertCode(err error) codes.Code {
switch err {
case nil:
return codes.OK
case io.EOF:
return codes.OutOfRange
case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF:
return codes.FailedPrecondition
case os.ErrInvalid:
return codes.InvalidArgument
case context.Canceled:
return codes.Canceled
case context.DeadlineExceeded:
return codes.DeadlineExceeded
}
switch {
case os.IsExist(err):
return codes.AlreadyExists
case os.IsNotExist(err):
return codes.NotFound
case os.IsPermission(err):
return codes.PermissionDenied
}
return codes.Unknown
}
func wait(ctx context.Context) *sync.WaitGroup {
if ctx == nil {
return nil
}
wg, ok := ctx.Value("wait").(*sync.WaitGroup)
if !ok {
return nil
}
return wg
}