From 1cd1449085ae451a810b1b5e2ccccb6dd314facd Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 8 Mar 2023 14:12:51 +0800 Subject: [PATCH] add package contrib/rpc/grpcx (#2169) --- .github/workflows/gf.yml | 10 +- cmd/gf/internal/cmd/cmd_gen_pb.go | 68 ++-- container/gmap/gmap_hash_any_any_map.go | 21 ++ container/gmap/gmap_hash_int_any_map.go | 22 ++ container/gmap/gmap_hash_int_int_map.go | 22 ++ container/gmap/gmap_hash_int_str_map.go | 22 ++ container/gmap/gmap_hash_str_any_map.go | 21 ++ container/gmap/gmap_hash_str_int_map.go | 22 ++ container/gmap/gmap_hash_str_str_map.go | 22 ++ .../gmap/gmap_z_unit_hash_any_any_test.go | 15 + .../gmap/gmap_z_unit_hash_int_any_test.go | 15 + .../gmap/gmap_z_unit_hash_int_int_test.go | 15 + .../gmap/gmap_z_unit_hash_int_str_test.go | 15 + .../gmap/gmap_z_unit_hash_str_any_test.go | 15 + .../gmap/gmap_z_unit_hash_str_int_test.go | 15 + .../gmap/gmap_z_unit_hash_str_str_test.go | 15 + contrib/drivers/dm/go.mod | 2 +- contrib/drivers/dm/go.sum | 4 +- contrib/registry/README.MD | 3 + contrib/registry/etcd/etcd.go | 37 +-- contrib/registry/etcd/etcd_discovery.go | 30 +- contrib/registry/etcd/etcd_registrar.go | 11 +- contrib/registry/etcd/etcd_service.go | 64 ++++ contrib/registry/etcd/etcd_watcher.go | 4 +- contrib/registry/etcd/etcd_z_test.go | 145 +++++++++ contrib/registry/file/README.MD | 77 +++++ contrib/registry/file/file.go | 42 +++ contrib/registry/file/file_discovery.go | 134 ++++++++ contrib/registry/file/file_registrar.go | 62 ++++ contrib/registry/file/file_service.go | 64 ++++ contrib/registry/file/file_watcher.go | 35 ++ contrib/registry/file/file_z_test.go | 150 +++++++++ contrib/registry/file/go.mod | 7 + contrib/registry/file/go.sum | 83 +++++ contrib/registry/polaris/polaris.go | 4 +- contrib/registry/polaris/polaris_discovery.go | 31 +- contrib/registry/zookeeper/zookeeper.go | 6 +- .../registry/zookeeper/zookeeper_discovery.go | 36 ++- .../registry/zookeeper/zookeeper_watcher.go | 9 +- contrib/rpc/grpcx/go.mod | 18 ++ contrib/rpc/grpcx/go.sum | 199 ++++++++++++ contrib/rpc/grpcx/grpcx.go | 40 +++ contrib/rpc/grpcx/grpcx_grpc_client.go | 77 +++++ contrib/rpc/grpcx/grpcx_grpc_server.go | 264 +++++++++++++++ contrib/rpc/grpcx/grpcx_grpc_server_config.go | 72 +++++ contrib/rpc/grpcx/grpcx_grpc_server_unary.go | 89 +++++ contrib/rpc/grpcx/grpcx_interceptor_client.go | 50 +++ contrib/rpc/grpcx/grpcx_interceptor_server.go | 91 ++++++ contrib/rpc/grpcx/grpcx_registry_file.go | 34 ++ contrib/rpc/grpcx/grpcx_unit_ctx_test.go | 63 ++++ .../rpc/grpcx/internal/balancer/balancer.go | 77 +++++ .../internal/balancer/balancer_builder.go | 52 +++ .../grpcx/internal/balancer/balancer_node.go | 33 ++ .../internal/balancer/balancer_picker.go | 64 ++++ contrib/rpc/grpcx/internal/grpcctx/grpcctx.go | 117 +++++++ .../rpc/grpcx/internal/resolver/resolver.go | 35 ++ .../internal/resolver/resolver_builder.go | 63 ++++ .../internal/resolver/resolver_manager.go | 20 ++ .../internal/resolver/resolver_resolver.go | 123 +++++++ contrib/rpc/grpcx/internal/tracing/tracing.go | 85 +++++ .../internal/tracing/tracing_interceptor.go | 287 ++++++++++++++++ .../tracing/tracing_interceptor_client.go | 137 ++++++++ .../tracing/tracing_interceptor_server.go | 127 ++++++++ contrib/rpc/grpcx/internal/utils/utils.go | 44 +++ example/go.mod | 11 +- example/go.sum | 23 +- example/registry/file/client/main.go | 27 ++ example/registry/file/server/main.go | 20 ++ example/rpc/grpcx/basic/client/config.yaml | 0 example/rpc/grpcx/basic/client/main.go | 32 ++ example/rpc/grpcx/basic/protobuf/echo.proto | 21 ++ example/rpc/grpcx/basic/protocol/echo.pb.go | 305 ++++++++++++++++++ example/rpc/grpcx/basic/server/config.yaml | 8 + example/rpc/grpcx/basic/server/main.go | 19 ++ .../rpc/grpcx/basic/service/service_echo.go | 27 ++ .../grpcx/basic_with_tag/client/config.yaml | 0 .../rpc/grpcx/basic_with_tag/client/main.go | 34 ++ .../grpcx/basic_with_tag/protobuf/echo.proto | 19 ++ .../grpcx/basic_with_tag/protocol/client.go | 31 ++ .../grpcx/basic_with_tag/protocol/echo.pb.go | 305 ++++++++++++++++++ .../grpcx/basic_with_tag/server/config.yaml | 8 + .../rpc/grpcx/basic_with_tag/server/main.go | 19 ++ .../basic_with_tag/service/service_echo.go | 27 ++ .../rpc/grpcx/rawgrpc/greeter_client/main.go | 47 +++ .../rpc/grpcx/rawgrpc/greeter_server/main.go | 63 ++++ .../grpcx/rawgrpc/helloworld/helloworld.pb.go | 234 ++++++++++++++ .../grpcx/rawgrpc/helloworld/helloworld.proto | 38 +++ .../rawgrpc/helloworld/helloworld_grpc.pb.go | 107 ++++++ .../grpc_with_db/protobuf/user/client.go | 5 +- .../grpc_with_db/protocol/user/user.proto | 4 +- example/trace/grpc_with_db/server/main.go | 10 +- example/trace/http_with_db/server/main.go | 6 +- net/gclient/gclient.go | 10 +- net/gclient/gclient_config.go | 12 + net/gclient/gclient_discovery.go | 26 +- net/ghttp/ghttp.go | 1 + net/ghttp/ghttp_server.go | 51 +-- net/ghttp/ghttp_server_config.go | 11 + net/ghttp/ghttp_server_registry.go | 60 ++-- net/gsel/gsel.go | 1 + net/gsel/gsel_builder_least_connection.go | 4 + net/gsel/gsel_builder_random.go | 4 + net/gsel/gsel_builder_round_robin.go | 4 + net/gsel/gsel_builder_weight.go | 4 + net/gsel/gsel_selector_least_connection.go | 2 - net/gsel/gsel_selector_random.go | 2 - net/gsel/gsel_selector_round_robin.go | 2 - net/gsel/gsel_selector_weight.go | 2 - net/gsvc/gsvc.go | 13 +- net/gsvc/gsvc_discovery.go | 38 ++- net/gsvc/gsvc_metadata.go | 2 +- os/gstructs/gstructs_z_bench_test.go | 7 + 112 files changed, 5130 insertions(+), 212 deletions(-) create mode 100644 contrib/registry/README.MD create mode 100644 contrib/registry/etcd/etcd_service.go create mode 100644 contrib/registry/etcd/etcd_z_test.go create mode 100644 contrib/registry/file/README.MD create mode 100644 contrib/registry/file/file.go create mode 100644 contrib/registry/file/file_discovery.go create mode 100644 contrib/registry/file/file_registrar.go create mode 100644 contrib/registry/file/file_service.go create mode 100644 contrib/registry/file/file_watcher.go create mode 100644 contrib/registry/file/file_z_test.go create mode 100644 contrib/registry/file/go.mod create mode 100644 contrib/registry/file/go.sum create mode 100644 contrib/rpc/grpcx/go.mod create mode 100644 contrib/rpc/grpcx/go.sum create mode 100644 contrib/rpc/grpcx/grpcx.go create mode 100644 contrib/rpc/grpcx/grpcx_grpc_client.go create mode 100644 contrib/rpc/grpcx/grpcx_grpc_server.go create mode 100644 contrib/rpc/grpcx/grpcx_grpc_server_config.go create mode 100644 contrib/rpc/grpcx/grpcx_grpc_server_unary.go create mode 100644 contrib/rpc/grpcx/grpcx_interceptor_client.go create mode 100644 contrib/rpc/grpcx/grpcx_interceptor_server.go create mode 100644 contrib/rpc/grpcx/grpcx_registry_file.go create mode 100644 contrib/rpc/grpcx/grpcx_unit_ctx_test.go create mode 100644 contrib/rpc/grpcx/internal/balancer/balancer.go create mode 100644 contrib/rpc/grpcx/internal/balancer/balancer_builder.go create mode 100644 contrib/rpc/grpcx/internal/balancer/balancer_node.go create mode 100644 contrib/rpc/grpcx/internal/balancer/balancer_picker.go create mode 100644 contrib/rpc/grpcx/internal/grpcctx/grpcctx.go create mode 100644 contrib/rpc/grpcx/internal/resolver/resolver.go create mode 100644 contrib/rpc/grpcx/internal/resolver/resolver_builder.go create mode 100644 contrib/rpc/grpcx/internal/resolver/resolver_manager.go create mode 100644 contrib/rpc/grpcx/internal/resolver/resolver_resolver.go create mode 100644 contrib/rpc/grpcx/internal/tracing/tracing.go create mode 100644 contrib/rpc/grpcx/internal/tracing/tracing_interceptor.go create mode 100644 contrib/rpc/grpcx/internal/tracing/tracing_interceptor_client.go create mode 100644 contrib/rpc/grpcx/internal/tracing/tracing_interceptor_server.go create mode 100644 contrib/rpc/grpcx/internal/utils/utils.go create mode 100644 example/registry/file/client/main.go create mode 100644 example/registry/file/server/main.go create mode 100644 example/rpc/grpcx/basic/client/config.yaml create mode 100644 example/rpc/grpcx/basic/client/main.go create mode 100644 example/rpc/grpcx/basic/protobuf/echo.proto create mode 100644 example/rpc/grpcx/basic/protocol/echo.pb.go create mode 100644 example/rpc/grpcx/basic/server/config.yaml create mode 100644 example/rpc/grpcx/basic/server/main.go create mode 100644 example/rpc/grpcx/basic/service/service_echo.go create mode 100644 example/rpc/grpcx/basic_with_tag/client/config.yaml create mode 100644 example/rpc/grpcx/basic_with_tag/client/main.go create mode 100644 example/rpc/grpcx/basic_with_tag/protobuf/echo.proto create mode 100644 example/rpc/grpcx/basic_with_tag/protocol/client.go create mode 100644 example/rpc/grpcx/basic_with_tag/protocol/echo.pb.go create mode 100644 example/rpc/grpcx/basic_with_tag/server/config.yaml create mode 100644 example/rpc/grpcx/basic_with_tag/server/main.go create mode 100644 example/rpc/grpcx/basic_with_tag/service/service_echo.go create mode 100644 example/rpc/grpcx/rawgrpc/greeter_client/main.go create mode 100644 example/rpc/grpcx/rawgrpc/greeter_server/main.go create mode 100644 example/rpc/grpcx/rawgrpc/helloworld/helloworld.pb.go create mode 100644 example/rpc/grpcx/rawgrpc/helloworld/helloworld.proto create mode 100644 example/rpc/grpcx/rawgrpc/helloworld/helloworld_grpc.pb.go diff --git a/.github/workflows/gf.yml b/.github/workflows/gf.yml index 9bc5bb248..32d743cd4 100644 --- a/.github/workflows/gf.yml +++ b/.github/workflows/gf.yml @@ -35,6 +35,15 @@ jobs: # Service containers to run with `code-test` services: + # Etcd service. + # docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes loads/etcd:3.4.24 + etcd: + image: loads/etcd:3.4.24 + env: + ALLOW_NONE_AUTHENTICATION: yes + ports: + - 2379:2379 + # Redis backend server. redis: image : loads/redis:7.0 @@ -54,7 +63,6 @@ jobs: MYSQL_DATABASE : test MYSQL_ROOT_PASSWORD: 12345678 ports: - # Maps tcp port 3306 on service container to the host - 3306:3306 # PostgreSQL backend server. diff --git a/cmd/gf/internal/cmd/cmd_gen_pb.go b/cmd/gf/internal/cmd/cmd_gen_pb.go index 0643cb3d0..729ad7936 100644 --- a/cmd/gf/internal/cmd/cmd_gen_pb.go +++ b/cmd/gf/internal/cmd/cmd_gen_pb.go @@ -2,12 +2,9 @@ package cmd import ( "context" - "fmt" "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog" - "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/os/genv" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/gproc" ) @@ -16,64 +13,53 @@ type ( cGenPb struct{} cGenPbInput struct { g.Meta `name:"pb" brief:"parse proto files and generate protobuf go files"` + Path string `name:"path" short:"p" dc:"protobuf file folder path" d:"manifest/protobuf"` + Output string `name:"output" short:"o" dc:"output folder path storing generated go files" d:"api"` } cGenPbOutput struct{} ) func (c cGenPb) Pb(ctx context.Context, in cGenPbInput) (out *cGenPbOutput, err error) { // Necessary check. - if gproc.SearchBinary("protoc") == "" { + protoc := gproc.SearchBinary("protoc") + if protoc == "" { mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first to proceed this command`) } // protocol fold checks. - protoFolder := "protocol" - if !gfile.Exists(protoFolder) { - mlog.Fatalf(`proto files folder "%s" does not exist`, protoFolder) + protoPath := gfile.RealPath(in.Path) + if protoPath == "" { + mlog.Fatalf(`proto files folder "%s" does not exist`, in.Path) } + // output path checks. + outputPath := gfile.RealPath(in.Output) + if outputPath == "" { + mlog.Fatalf(`output folder "%s" does not exist`, in.Output) + } + // folder scanning. - files, err := gfile.ScanDirFile(protoFolder, "*.proto", true) + files, err := gfile.ScanDirFile(protoPath, "*.proto", true) if err != nil { mlog.Fatal(err) } if len(files) == 0 { - mlog.Fatalf(`no proto files found in folder "%s"`, protoFolder) + mlog.Fatalf(`no proto files found in folder "%s"`, in.Path) + } + + if err = gfile.Chdir(protoPath); err != nil { + mlog.Fatal(err) } - dirSet := gset.NewStrSet() for _, file := range files { - dirSet.Add(gfile.Dir(file)) - } - var ( - servicePath = gfile.RealPath(".") - goPathSrc = gfile.RealPath(gfile.Join(genv.Get("GOPATH").String(), "src")) - ) - dirSet.Iterator(func(protoDirPath string) bool { - parsingCommand := fmt.Sprintf( - "protoc --gofast_out=plugins=grpc:. %s/*.proto -I%s", - protoDirPath, - servicePath, - ) - if goPathSrc != "" { - parsingCommand += " -I" + goPathSrc - } - mlog.Print(parsingCommand) - if output, err := gproc.ShellExec(ctx, parsingCommand); err != nil { - mlog.Print(output) + var command = gproc.NewProcess(protoc, nil) + command.Args = append(command.Args, "--proto_path="+gfile.Pwd()) + command.Args = append(command.Args, "--go_out=paths=source_relative:"+outputPath) + command.Args = append(command.Args, "--go-grpc_out=paths=source_relative:"+outputPath) + command.Args = append(command.Args, file) + mlog.Print(command.String()) + if err = command.Run(ctx); err != nil { mlog.Fatal(err) } - return true - }) - // Custom replacement. - //pbFolder := "protobuf" - //_, _ = gfile.ScanDirFileFunc(pbFolder, "*.go", true, func(path string) string { - // content := gfile.GetContents(path) - // content = gstr.ReplaceByArray(content, g.SliceStr{ - // `gtime "gtime"`, `gtime "github.com/gogf/gf/v2/os/gtime"`, - // }) - // _ = gfile.PutContents(path, content) - // utils.GoFmt(path) - // return path - //}) + } mlog.Print("done!") return } diff --git a/container/gmap/gmap_hash_any_any_map.go b/container/gmap/gmap_hash_any_any_map.go index 23ce00124..fdfe9aa63 100644 --- a/container/gmap/gmap_hash_any_any_map.go +++ b/container/gmap/gmap_hash_any_any_map.go @@ -514,3 +514,24 @@ func (m *AnyAnyMap) DeepCopy() interface{} { } return NewFrom(data, m.mu.IsSafe()) } + +// IsSubOf checks whether the current map is a sub-map of `other`. +func (m *AnyAnyMap) IsSubOf(other *AnyAnyMap) bool { + if m == other { + return true + } + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + for key, value := range m.data { + otherValue, ok := other.data[key] + if !ok { + return false + } + if otherValue != value { + return false + } + } + return true +} diff --git a/container/gmap/gmap_hash_int_any_map.go b/container/gmap/gmap_hash_int_any_map.go index 209cf19b3..1faed272e 100644 --- a/container/gmap/gmap_hash_int_any_map.go +++ b/container/gmap/gmap_hash_int_any_map.go @@ -16,6 +16,7 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +// IntAnyMap implements map[int]interface{} with RWMutex that has switch. type IntAnyMap struct { mu rwmutex.RWMutex data map[int]interface{} @@ -514,3 +515,24 @@ func (m *IntAnyMap) DeepCopy() interface{} { } return NewIntAnyMapFrom(data, m.mu.IsSafe()) } + +// IsSubOf checks whether the current map is a sub-map of `other`. +func (m *IntAnyMap) IsSubOf(other *IntAnyMap) bool { + if m == other { + return true + } + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + for key, value := range m.data { + otherValue, ok := other.data[key] + if !ok { + return false + } + if otherValue != value { + return false + } + } + return true +} diff --git a/container/gmap/gmap_hash_int_int_map.go b/container/gmap/gmap_hash_int_int_map.go index 9404b081f..f63c1f067 100644 --- a/container/gmap/gmap_hash_int_int_map.go +++ b/container/gmap/gmap_hash_int_int_map.go @@ -13,6 +13,7 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +// IntIntMap implements map[int]int with RWMutex that has switch. type IntIntMap struct { mu rwmutex.RWMutex data map[int]int @@ -484,3 +485,24 @@ func (m *IntIntMap) DeepCopy() interface{} { } return NewIntIntMapFrom(data, m.mu.IsSafe()) } + +// IsSubOf checks whether the current map is a sub-map of `other`. +func (m *IntIntMap) IsSubOf(other *IntIntMap) bool { + if m == other { + return true + } + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + for key, value := range m.data { + otherValue, ok := other.data[key] + if !ok { + return false + } + if otherValue != value { + return false + } + } + return true +} diff --git a/container/gmap/gmap_hash_int_str_map.go b/container/gmap/gmap_hash_int_str_map.go index e327a889d..943f6b79b 100644 --- a/container/gmap/gmap_hash_int_str_map.go +++ b/container/gmap/gmap_hash_int_str_map.go @@ -13,6 +13,7 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +// IntStrMap implements map[int]string with RWMutex that has switch. type IntStrMap struct { mu rwmutex.RWMutex data map[int]string @@ -484,3 +485,24 @@ func (m *IntStrMap) DeepCopy() interface{} { } return NewIntStrMapFrom(data, m.mu.IsSafe()) } + +// IsSubOf checks whether the current map is a sub-map of `other`. +func (m *IntStrMap) IsSubOf(other *IntStrMap) bool { + if m == other { + return true + } + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + for key, value := range m.data { + otherValue, ok := other.data[key] + if !ok { + return false + } + if otherValue != value { + return false + } + } + return true +} diff --git a/container/gmap/gmap_hash_str_any_map.go b/container/gmap/gmap_hash_str_any_map.go index 29bdc121d..b05be65a2 100644 --- a/container/gmap/gmap_hash_str_any_map.go +++ b/container/gmap/gmap_hash_str_any_map.go @@ -501,3 +501,24 @@ func (m *StrAnyMap) DeepCopy() interface{} { } return NewStrAnyMapFrom(data, m.mu.IsSafe()) } + +// IsSubOf checks whether the current map is a sub-map of `other`. +func (m *StrAnyMap) IsSubOf(other *StrAnyMap) bool { + if m == other { + return true + } + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + for key, value := range m.data { + otherValue, ok := other.data[key] + if !ok { + return false + } + if otherValue != value { + return false + } + } + return true +} diff --git a/container/gmap/gmap_hash_str_int_map.go b/container/gmap/gmap_hash_str_int_map.go index 13bb275d8..e0b330f22 100644 --- a/container/gmap/gmap_hash_str_int_map.go +++ b/container/gmap/gmap_hash_str_int_map.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +// StrIntMap implements map[string]int with RWMutex that has switch. type StrIntMap struct { mu rwmutex.RWMutex data map[string]int @@ -488,3 +489,24 @@ func (m *StrIntMap) DeepCopy() interface{} { } return NewStrIntMapFrom(data, m.mu.IsSafe()) } + +// IsSubOf checks whether the current map is a sub-map of `other`. +func (m *StrIntMap) IsSubOf(other *StrIntMap) bool { + if m == other { + return true + } + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + for key, value := range m.data { + otherValue, ok := other.data[key] + if !ok { + return false + } + if otherValue != value { + return false + } + } + return true +} diff --git a/container/gmap/gmap_hash_str_str_map.go b/container/gmap/gmap_hash_str_str_map.go index 0128fe385..e73628f87 100644 --- a/container/gmap/gmap_hash_str_str_map.go +++ b/container/gmap/gmap_hash_str_str_map.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +// StrStrMap implements map[string]string with RWMutex that has switch. type StrStrMap struct { mu rwmutex.RWMutex data map[string]string @@ -477,3 +478,24 @@ func (m *StrStrMap) DeepCopy() interface{} { } return NewStrStrMapFrom(data, m.mu.IsSafe()) } + +// IsSubOf checks whether the current map is a sub-map of `other`. +func (m *StrStrMap) IsSubOf(other *StrStrMap) bool { + if m == other { + return true + } + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + for key, value := range m.data { + otherValue, ok := other.data[key] + if !ok { + return false + } + if otherValue != value { + return false + } + } + return true +} diff --git a/container/gmap/gmap_z_unit_hash_any_any_test.go b/container/gmap/gmap_z_unit_hash_any_any_test.go index 170c4f8f7..3853218bf 100644 --- a/container/gmap/gmap_z_unit_hash_any_any_test.go +++ b/container/gmap/gmap_z_unit_hash_any_any_test.go @@ -391,3 +391,18 @@ func Test_AnyAnyMap_DeepCopy(t *testing.T) { t.AssertNE(m.Get("k1"), n.Get("k1")) }) } + +func Test_AnyAnyMap_IsSubOf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewAnyAnyMapFrom(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + }) + m2 := gmap.NewAnyAnyMapFrom(g.MapAnyAny{ + "k2": "v2", + }) + t.Assert(m1.IsSubOf(m2), false) + t.Assert(m2.IsSubOf(m1), true) + t.Assert(m2.IsSubOf(m2), true) + }) +} diff --git a/container/gmap/gmap_z_unit_hash_int_any_test.go b/container/gmap/gmap_z_unit_hash_int_any_test.go index 199e976fc..570348723 100644 --- a/container/gmap/gmap_z_unit_hash_int_any_test.go +++ b/container/gmap/gmap_z_unit_hash_int_any_test.go @@ -375,3 +375,18 @@ func Test_IntAnyMap_DeepCopy(t *testing.T) { t.AssertNE(m.Get(1), n.Get(1)) }) } + +func Test_IntAnyMap_IsSubOf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewIntAnyMapFrom(g.MapIntAny{ + 1: "v1", + 2: "v2", + }) + m2 := gmap.NewIntAnyMapFrom(g.MapIntAny{ + 2: "v2", + }) + t.Assert(m1.IsSubOf(m2), false) + t.Assert(m2.IsSubOf(m1), true) + t.Assert(m2.IsSubOf(m2), true) + }) +} diff --git a/container/gmap/gmap_z_unit_hash_int_int_test.go b/container/gmap/gmap_z_unit_hash_int_int_test.go index f565b5dcc..b2e7dd8f5 100644 --- a/container/gmap/gmap_z_unit_hash_int_int_test.go +++ b/container/gmap/gmap_z_unit_hash_int_int_test.go @@ -383,3 +383,18 @@ func Test_IntIntMap_DeepCopy(t *testing.T) { t.AssertNE(m.Get(1), n.Get(1)) }) } + +func Test_IntIntMap_IsSubOf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewIntAnyMapFrom(g.MapIntAny{ + 1: 1, + 2: 2, + }) + m2 := gmap.NewIntAnyMapFrom(g.MapIntAny{ + 2: 2, + }) + t.Assert(m1.IsSubOf(m2), false) + t.Assert(m2.IsSubOf(m1), true) + t.Assert(m2.IsSubOf(m2), true) + }) +} diff --git a/container/gmap/gmap_z_unit_hash_int_str_test.go b/container/gmap/gmap_z_unit_hash_int_str_test.go index 938d09bf6..7a2bcc5ab 100644 --- a/container/gmap/gmap_z_unit_hash_int_str_test.go +++ b/container/gmap/gmap_z_unit_hash_int_str_test.go @@ -447,3 +447,18 @@ func Test_IntStrMap_DeepCopy(t *testing.T) { t.AssertNE(m.Get(1), n.Get(1)) }) } + +func Test_IntStrMap_IsSubOf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewIntStrMapFrom(g.MapIntStr{ + 1: "v1", + 2: "v2", + }) + m2 := gmap.NewIntStrMapFrom(g.MapIntStr{ + 2: "v2", + }) + t.Assert(m1.IsSubOf(m2), false) + t.Assert(m2.IsSubOf(m1), true) + t.Assert(m2.IsSubOf(m2), true) + }) +} diff --git a/container/gmap/gmap_z_unit_hash_str_any_test.go b/container/gmap/gmap_z_unit_hash_str_any_test.go index 4853afc0c..83fc513f6 100644 --- a/container/gmap/gmap_z_unit_hash_str_any_test.go +++ b/container/gmap/gmap_z_unit_hash_str_any_test.go @@ -381,3 +381,18 @@ func Test_StrAnyMap_DeepCopy(t *testing.T) { t.AssertNE(m.Get("key1"), n.Get("key1")) }) } + +func Test_StrAnyMap_IsSubOf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewStrAnyMapFrom(g.MapStrAny{ + "k1": "v1", + "k2": "v2", + }) + m2 := gmap.NewStrAnyMapFrom(g.MapStrAny{ + "k2": "v2", + }) + t.Assert(m1.IsSubOf(m2), false) + t.Assert(m2.IsSubOf(m1), true) + t.Assert(m2.IsSubOf(m2), true) + }) +} diff --git a/container/gmap/gmap_z_unit_hash_str_int_test.go b/container/gmap/gmap_z_unit_hash_str_int_test.go index 2c11031bb..6616ebf90 100644 --- a/container/gmap/gmap_z_unit_hash_str_int_test.go +++ b/container/gmap/gmap_z_unit_hash_str_int_test.go @@ -389,3 +389,18 @@ func Test_StrIntMap_DeepCopy(t *testing.T) { t.AssertNE(m.Get("key1"), n.Get("key1")) }) } + +func Test_StrIntMap_IsSubOf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewStrIntMapFrom(g.MapStrInt{ + "k1": 1, + "k2": 2, + }) + m2 := gmap.NewStrIntMapFrom(g.MapStrInt{ + "k2": 2, + }) + t.Assert(m1.IsSubOf(m2), false) + t.Assert(m2.IsSubOf(m1), true) + t.Assert(m2.IsSubOf(m2), true) + }) +} diff --git a/container/gmap/gmap_z_unit_hash_str_str_test.go b/container/gmap/gmap_z_unit_hash_str_str_test.go index c129e3ea8..41c0966a1 100644 --- a/container/gmap/gmap_z_unit_hash_str_str_test.go +++ b/container/gmap/gmap_z_unit_hash_str_str_test.go @@ -388,3 +388,18 @@ func Test_StrStrMap_DeepCopy(t *testing.T) { t.AssertNE(m.Get("key1"), n.Get("key1")) }) } + +func Test_StrStrMap_IsSubOf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewStrStrMapFrom(g.MapStrStr{ + "k1": "v1", + "k2": "v2", + }) + m2 := gmap.NewStrStrMapFrom(g.MapStrStr{ + "k2": "v2", + }) + t.Assert(m1.IsSubOf(m2), false) + t.Assert(m2.IsSubOf(m1), true) + t.Assert(m2.IsSubOf(m2), true) + }) +} diff --git a/contrib/drivers/dm/go.mod b/contrib/drivers/dm/go.mod index dee9db964..d158a95db 100644 --- a/contrib/drivers/dm/go.mod +++ b/contrib/drivers/dm/go.mod @@ -5,6 +5,6 @@ go 1.15 replace github.com/gogf/gf/v2 => ../../../ require ( - gitee.com/chunanyong/dm v1.8.11 + gitee.com/chunanyong/dm v1.8.10 github.com/gogf/gf/v2 v2.0.0 ) diff --git a/contrib/drivers/dm/go.sum b/contrib/drivers/dm/go.sum index b7e652d7c..78465e222 100644 --- a/contrib/drivers/dm/go.sum +++ b/contrib/drivers/dm/go.sum @@ -1,5 +1,5 @@ -gitee.com/chunanyong/dm v1.8.11 h1:JPwiS1PqHObo4QFodruLR8WOhLP+7Y/EKGGu2BJ5SJI= -gitee.com/chunanyong/dm v1.8.11/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg= +gitee.com/chunanyong/dm v1.8.10 h1:9S1CKUggWHIea/GI7nr7S/DNMaxIilNFgfzdzKDx2+I= +gitee.com/chunanyong/dm v1.8.10/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= diff --git a/contrib/registry/README.MD b/contrib/registry/README.MD new file mode 100644 index 000000000..7f22edd5d --- /dev/null +++ b/contrib/registry/README.MD @@ -0,0 +1,3 @@ +# Service registrar and discovery + +Please refer to certain sub folder. \ No newline at end of file diff --git a/contrib/registry/etcd/etcd.go b/contrib/registry/etcd/etcd.go index 5201c355d..e1db2dbee 100644 --- a/contrib/registry/etcd/etcd.go +++ b/contrib/registry/etcd/etcd.go @@ -8,16 +8,16 @@ package etcd import ( - "reflect" "time" + etcd3 "go.etcd.io/etcd/client/v3" + "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/gsvc" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/text/gstr" - etcd3 "go.etcd.io/etcd/client/v3" ) var ( @@ -45,7 +45,7 @@ const ( ) // New creates and returns a new etcd registry. -func New(address string, option ...Option) *Registry { +func New(address string, option ...Option) gsvc.Registry { endpoints := gstr.SplitAndTrim(address, ",") if len(endpoints) == 0 { panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid etcd address "%s"`, address)) @@ -84,31 +84,22 @@ func extractResponseToServices(res *etcd3.GetResponse) ([]gsvc.Service, error) { return nil, nil } var ( - services []gsvc.Service - serviceKey string - serviceMap = make(map[string]*gsvc.LocalService) + services []gsvc.Service + servicePrefixMap = make(map[string]*Service) ) for _, kv := range res.Kvs { - service, err := gsvc.NewServiceWithKV(string(kv.Key), string(kv.Value)) + service, err := gsvc.NewServiceWithKV( + string(kv.Key), string(kv.Value), + ) if err != nil { return services, err } - localService, ok := service.(*gsvc.LocalService) - if !ok { - return nil, gerror.Newf( - `service from "gsvc.NewServiceWithKV" is not "*gsvc.LocalService", but "%s"`, - reflect.TypeOf(service), - ) - } - if localService != nil { - serviceKey = localService.GetPrefix() - var localServiceInMap *gsvc.LocalService - if localServiceInMap, ok = serviceMap[serviceKey]; ok { - localServiceInMap.Endpoints = append(localServiceInMap.Endpoints, localService.Endpoints...) - } else { - serviceMap[serviceKey] = localService - services = append(services, service) - } + s := NewService(service) + if v, ok := servicePrefixMap[service.GetPrefix()]; ok { + v.Endpoints = append(v.Endpoints, service.GetEndpoints()...) + } else { + servicePrefixMap[s.GetPrefix()] = s + services = append(services, s) } } return services, nil diff --git a/contrib/registry/etcd/etcd_discovery.go b/contrib/registry/etcd/etcd_discovery.go index 862515176..adb918cdf 100644 --- a/contrib/registry/etcd/etcd_discovery.go +++ b/contrib/registry/etcd/etcd_discovery.go @@ -9,11 +9,14 @@ package etcd import ( "context" - "github.com/gogf/gf/v2/net/gsvc" etcd3 "go.etcd.io/etcd/client/v3" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/text/gstr" ) -// Search is the etcd discovery search function. +// Search searches and returns services with specified condition. func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) ([]gsvc.Service, error) { if in.Prefix == "" && in.Name != "" { in.Prefix = gsvc.NewServiceWithName(in.Name).GetPrefix() @@ -29,20 +32,31 @@ func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) ([]gsvc.Serv } // Service filter. filteredServices := make([]gsvc.Service, 0) - for _, v := range services { - if in.Name != "" && in.Name != v.GetName() { + for _, service := range services { + if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) { continue } - if in.Version != "" && in.Version != v.GetVersion() { + if in.Name != "" && service.GetName() != in.Name { continue } - service := v - filteredServices = append(filteredServices, service) + if in.Version != "" && service.GetVersion() != in.Version { + continue + } + if len(in.Metadata) != 0 { + m1 := gmap.NewStrAnyMapFrom(in.Metadata) + m2 := gmap.NewStrAnyMapFrom(service.GetMetadata()) + if !m1.IsSubOf(m2) { + continue + } + } + resultItem := service + filteredServices = append(filteredServices, resultItem) } return filteredServices, nil } -// Watch is the etcd discovery watch function. +// Watch watches specified condition changes. +// The `key` is the prefix of service key. func (r *Registry) Watch(ctx context.Context, key string) (gsvc.Watcher, error) { return newWatcher(key, r.client) } diff --git a/contrib/registry/etcd/etcd_registrar.go b/contrib/registry/etcd/etcd_registrar.go index 62b3d310e..85b63aa52 100644 --- a/contrib/registry/etcd/etcd_registrar.go +++ b/contrib/registry/etcd/etcd_registrar.go @@ -9,13 +9,16 @@ package etcd import ( "context" + etcd3 "go.etcd.io/etcd/client/v3" + "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/net/gsvc" - etcd3 "go.etcd.io/etcd/client/v3" ) -// Register implements the gsvc.Register interface. +// Register registers `service` to Registry. +// Note that it returns a new Service if it changes the input Service with custom one. func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Service, error) { + service = NewService(service) r.lease = etcd3.NewLease(r.client) grant, err := r.lease.Grant(ctx, int64(r.keepaliveTTL.Seconds())) if err != nil { @@ -33,7 +36,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser key, value, grant.ID, ) } - r.logger.Infof( + r.logger.Debugf( ctx, `etcd put success with key "%s", value "%s", lease "%d"`, key, value, grant.ID, @@ -46,7 +49,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser return service, nil } -// Deregister implements the gsvc.Deregister interface. +// Deregister off-lines and removes `service` from the Registry. func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error { _, err := r.client.Delete(ctx, service.GetKey()) if r.lease != nil { diff --git a/contrib/registry/etcd/etcd_service.go b/contrib/registry/etcd/etcd_service.go new file mode 100644 index 000000000..ec0cb2581 --- /dev/null +++ b/contrib/registry/etcd/etcd_service.go @@ -0,0 +1,64 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package etcd + +import ( + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/net/gsvc" +) + +// Service wrapper. +type Service struct { + gsvc.Service + Endpoints gsvc.Endpoints + Metadata gsvc.Metadata +} + +// NewService creates and returns local Service from gsvc.Service interface object. +func NewService(service gsvc.Service) *Service { + s, ok := service.(*Service) + if ok { + if s.Endpoints == nil { + s.Endpoints = make(gsvc.Endpoints, 0) + } + if s.Metadata == nil { + s.Metadata = make(gsvc.Metadata) + } + return s + } + s = &Service{ + Service: service, + Endpoints: make(gsvc.Endpoints, 0), + Metadata: make(gsvc.Metadata), + } + if len(service.GetEndpoints()) > 0 { + s.Endpoints = service.GetEndpoints() + } + if len(service.GetMetadata()) > 0 { + s.Metadata = service.GetMetadata() + } + return s +} + +// GetMetadata returns the Metadata map of service. +// The Metadata is key-value pair map specifying extra attributes of a service. +func (s *Service) GetMetadata() gsvc.Metadata { + return s.Metadata +} + +// GetEndpoints returns the Endpoints of service. +// The Endpoints contain multiple host/port information of service. +func (s *Service) GetEndpoints() gsvc.Endpoints { + return s.Endpoints +} + +// GetValue formats and returns the value of the service. +// The result value is commonly used for key-value registrar server. +func (s *Service) GetValue() string { + b, _ := gjson.Marshal(s.Metadata) + return string(b) +} diff --git a/contrib/registry/etcd/etcd_watcher.go b/contrib/registry/etcd/etcd_watcher.go index fe401ba90..3c4da0d51 100644 --- a/contrib/registry/etcd/etcd_watcher.go +++ b/contrib/registry/etcd/etcd_watcher.go @@ -9,8 +9,9 @@ package etcd import ( "context" - "github.com/gogf/gf/v2/net/gsvc" etcd3 "go.etcd.io/etcd/client/v3" + + "github.com/gogf/gf/v2/net/gsvc" ) var ( @@ -46,6 +47,7 @@ func (w *watcher) Proceed() ([]gsvc.Service, error) { case <-w.ctx.Done(): return nil, w.ctx.Err() case <-w.watchChan: + // It retrieves, merges and returns all services by prefix if any changes. return w.getServicesByPrefix() } } diff --git a/contrib/registry/etcd/etcd_z_test.go b/contrib/registry/etcd/etcd_z_test.go new file mode 100644 index 000000000..06d1916a1 --- /dev/null +++ b/contrib/registry/etcd/etcd_z_test.go @@ -0,0 +1,145 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package etcd_test + +import ( + "testing" + + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/guid" +) + +func TestRegistry(t *testing.T) { + var ( + ctx = gctx.GetInitCtx() + registry = etcd.New(`127.0.0.1:2379`) + ) + svc := &gsvc.LocalService{ + Name: guid.S(), + Endpoints: gsvc.NewEndpoints("127.0.0.1:8888"), + Metadata: map[string]interface{}{ + "protocol": "https", + }, + } + gtest.C(t, func(t *gtest.T) { + registered, err := registry.Register(ctx, svc) + t.AssertNil(err) + t.Assert(registered.GetName(), svc.GetName()) + }) + + // Search by name. + gtest.C(t, func(t *gtest.T) { + result, err := registry.Search(ctx, gsvc.SearchInput{ + Name: svc.Name, + }) + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0].GetName(), svc.Name) + }) + + // Search by prefix. + gtest.C(t, func(t *gtest.T) { + result, err := registry.Search(ctx, gsvc.SearchInput{ + Prefix: svc.GetPrefix(), + }) + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0].GetName(), svc.Name) + }) + + // Search by metadata. + gtest.C(t, func(t *gtest.T) { + result, err := registry.Search(ctx, gsvc.SearchInput{ + Name: svc.GetName(), + Metadata: map[string]interface{}{ + "protocol": "https", + }, + }) + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0].GetName(), svc.Name) + }) + gtest.C(t, func(t *gtest.T) { + result, err := registry.Search(ctx, gsvc.SearchInput{ + Name: svc.GetName(), + Metadata: map[string]interface{}{ + "protocol": "grpc", + }, + }) + t.AssertNil(err) + t.Assert(len(result), 0) + }) + + gtest.C(t, func(t *gtest.T) { + err := registry.Deregister(ctx, svc) + t.AssertNil(err) + }) +} + +func TestWatch(t *testing.T) { + var ( + ctx = gctx.GetInitCtx() + registry = etcd.New(`127.0.0.1:2379`) + ) + + svc1 := &gsvc.LocalService{ + Name: guid.S(), + Endpoints: gsvc.NewEndpoints("127.0.0.1:8888"), + Metadata: map[string]interface{}{ + "protocol": "https", + }, + } + gtest.C(t, func(t *gtest.T) { + registered, err := registry.Register(ctx, svc1) + t.AssertNil(err) + t.Assert(registered.GetName(), svc1.GetName()) + }) + + gtest.C(t, func(t *gtest.T) { + watcher, err := registry.Watch(ctx, svc1.GetPrefix()) + t.AssertNil(err) + + // Register another service. + svc2 := &gsvc.LocalService{ + Name: svc1.Name, + Endpoints: gsvc.NewEndpoints("127.0.0.1:9999"), + } + registered, err := registry.Register(ctx, svc2) + t.AssertNil(err) + t.Assert(registered.GetName(), svc2.GetName()) + + // Watch and retrieve the service changes: + // svc1 and svc2 is the same service name, which has 2 endpoints. + proceedResult, err := watcher.Proceed() + t.AssertNil(err) + t.Assert(len(proceedResult), 1) + t.Assert( + proceedResult[0].GetEndpoints(), + gsvc.Endpoints{svc1.GetEndpoints()[0], svc2.GetEndpoints()[0]}, + ) + + // Watch and retrieve the service changes: + // left only svc1, which means this service has only 1 endpoint. + err = registry.Deregister(ctx, svc2) + t.AssertNil(err) + proceedResult, err = watcher.Proceed() + t.AssertNil(err) + t.Assert( + proceedResult[0].GetEndpoints(), + gsvc.Endpoints{svc1.GetEndpoints()[0]}, + ) + t.AssertNil(watcher.Close()) + }) + + gtest.C(t, func(t *gtest.T) { + err := registry.Deregister(ctx, svc1) + t.AssertNil(err) + }) +} diff --git a/contrib/registry/file/README.MD b/contrib/registry/file/README.MD new file mode 100644 index 000000000..adbd6029b --- /dev/null +++ b/contrib/registry/file/README.MD @@ -0,0 +1,77 @@ +# GoFrame File Registry + + +Use `file` as service registration and discovery management. + + +## Installation +``` +go get -u -v github.com/gogf/gf/contrib/registry/file/v2 +``` +suggested using `go.mod`: +``` +require github.com/gogf/gf/contrib/registry/file/v2 latest +``` + + +## Example + +### Reference example + +[server](example/registry/file/server/main.go) +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) + + s := g.Server(`hello.svc`) + s.BindHandler("/", func(r *ghttp.Request) { + g.Log().Info(r.Context(), `request received`) + r.Response.Write(`Hello world`) + }) + s.Run() +} + +``` + +[client](example/registry/file/client/main.go) +```go +package main + +import ( + "fmt" + "time" + + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) + + client := g.Client() + for i := 0; i < 100; i++ { + res, err := client.Get(gctx.New(), `http://hello.svc/`) + if err != nil { + panic(err) + } + fmt.Println(res.ReadAllString()) + res.Close() + time.Sleep(time.Second) + } +} + +``` + diff --git a/contrib/registry/file/file.go b/contrib/registry/file/file.go new file mode 100644 index 000000000..aa20fd071 --- /dev/null +++ b/contrib/registry/file/file.go @@ -0,0 +1,42 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package file implements service Registry and Discovery using file. +package file + +import ( + "time" + + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gfile" +) + +var ( + _ gsvc.Registry = &Registry{} +) + +const ( + updateAtKey = "UpdateAt" + serviceTTL = 20 * time.Second + serviceUpdateInterval = 10 * time.Second + defaultSeparator = "#" +) + +// Registry implements interface Registry using file. +// This implement is usually for testing only. +type Registry struct { + path string // Local storing folder path for Services. +} + +// New creates and returns a gsvc.Registry implements using file. +func New(path string) gsvc.Registry { + if !gfile.Exists(path) { + _ = gfile.Mkdir(path) + } + return &Registry{ + path: path, + } +} diff --git a/contrib/registry/file/file_discovery.go b/contrib/registry/file/file_discovery.go new file mode 100644 index 000000000..a8cc028d1 --- /dev/null +++ b/contrib/registry/file/file_discovery.go @@ -0,0 +1,134 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package file + +import ( + "context" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gfsnotify" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gstr" +) + +// Search searches and returns services with specified condition. +func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) (result []gsvc.Service, err error) { + services, err := r.getServices(ctx) + if err != nil { + return nil, err + } + for _, service := range services { + if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) { + continue + } + if in.Name != "" && service.GetName() != in.Name { + continue + } + if in.Version != "" && service.GetVersion() != in.Version { + continue + } + if len(in.Metadata) != 0 { + m1 := gmap.NewStrAnyMapFrom(in.Metadata) + m2 := gmap.NewStrAnyMapFrom(service.GetMetadata()) + if !m1.IsSubOf(m2) { + continue + } + } + resultItem := service + result = append(result, resultItem) + } + result = r.mergeServices(result) + return +} + +// Watch watches specified condition changes. +// The `key` is the prefix of service key. +func (r *Registry) Watch(ctx context.Context, key string) (watcher gsvc.Watcher, err error) { + fileWatcher := &Watcher{ + prefix: key, + discovery: r, + ch: make(chan gsvc.Service, 100), + } + _, err = gfsnotify.Add(r.path, func(event *gfsnotify.Event) { + if event.IsChmod() { + return + } + if !gstr.HasPrefix(gfile.Basename(event.Path), r.getServiceKeyForFile(key)) { + return + } + service, err := r.getServiceByFilePath(event.Path) + if err != nil { + return + } + fileWatcher.ch <- service + }) + return fileWatcher, err +} + +func (r *Registry) getServices(ctx context.Context) (services []gsvc.Service, err error) { + filePaths, err := gfile.ScanDirFile(r.path, "*", false) + if err != nil { + return nil, err + } + for _, filePath := range filePaths { + s, e := r.getServiceByFilePath(filePath) + if e != nil { + return nil, e + } + // Check service TTL. + var ( + updateAt = s.GetMetadata().Get(updateAtKey).GTime() + nowTime = gtime.Now() + subDuration = nowTime.Sub(updateAt) + ) + if updateAt.IsZero() || subDuration > serviceTTL { + g.Log().Debugf( + ctx, + `service "%s" is expired, update at: %s, current: %s, sub duration: %s`, + s.GetKey(), updateAt.String(), nowTime.String(), subDuration.String(), + ) + continue + } + services = append(services, s) + } + services = r.mergeServices(services) + return +} + +func (r *Registry) getServiceByFilePath(filePath string) (gsvc.Service, error) { + var ( + fileName = gfile.Basename(filePath) + fileContent = gfile.GetContents(filePath) + serviceKey = gstr.Replace(fileName, defaultSeparator, gsvc.DefaultSeparator) + ) + serviceKey = gsvc.DefaultSeparator + serviceKey + return gsvc.NewServiceWithKV(serviceKey, fileContent) +} + +func (r *Registry) mergeServices(services []gsvc.Service) []gsvc.Service { + if len(services) == 0 { + return services + } + + var ( + servicePrefixMap = make(map[string]*Service) + mergeServices = make([]gsvc.Service, 0) + ) + for _, service := range services { + if v, ok := servicePrefixMap[service.GetPrefix()]; ok { + v.Endpoints = append(v.Endpoints, service.GetEndpoints()...) + } else { + s := NewService(service) + servicePrefixMap[s.GetPrefix()] = s + mergeServices = append(mergeServices, s) + } + } + return mergeServices +} diff --git a/contrib/registry/file/file_registrar.go b/contrib/registry/file/file_registrar.go new file mode 100644 index 000000000..e9c4139b1 --- /dev/null +++ b/contrib/registry/file/file_registrar.go @@ -0,0 +1,62 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package file + +import ( + "context" + + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/text/gstr" +) + +// Register registers `service` to Registry. +// Note that it returns a new Service if it changes the input Service with custom one. +func (r *Registry) Register(ctx context.Context, service gsvc.Service) (registered gsvc.Service, err error) { + service = NewService(service) + service.GetMetadata().Set(updateAtKey, gtime.Now()) + var ( + filePath = r.getServiceFilePath(service) + fileContent = service.GetValue() + ) + err = gfile.PutContents(filePath, fileContent) + if err == nil { + gtimer.Add(ctx, serviceUpdateInterval, func(ctx context.Context) { + if !gfile.Exists(filePath) { + gtimer.Exit() + } + // Update TTL in timer. + service, _ = r.getServiceByFilePath(filePath) + if service != nil { + service.GetMetadata().Set(updateAtKey, gtime.Now()) + } + _ = gfile.PutContents(filePath, service.GetValue()) + }) + } + return service, err +} + +// Deregister off-lines and removes `service` from the Registry. +func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error { + return gfile.Remove(r.getServiceFilePath(service)) +} + +func (r *Registry) getServiceFilePath(service gsvc.Service) string { + return gfile.Join(r.path, r.getServiceFileName(service)) +} + +func (r *Registry) getServiceFileName(service gsvc.Service) string { + return r.getServiceKeyForFile(service.GetKey()) +} + +func (r *Registry) getServiceKeyForFile(key string) string { + key = gstr.Replace(key, gsvc.DefaultSeparator, defaultSeparator) + key = gstr.Trim(key, defaultSeparator) + return key +} diff --git a/contrib/registry/file/file_service.go b/contrib/registry/file/file_service.go new file mode 100644 index 000000000..557a36dbe --- /dev/null +++ b/contrib/registry/file/file_service.go @@ -0,0 +1,64 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package file + +import ( + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/net/gsvc" +) + +// Service wrapper. +type Service struct { + gsvc.Service + Endpoints gsvc.Endpoints + Metadata gsvc.Metadata +} + +// NewService creates and returns local Service from gsvc.Service interface object. +func NewService(service gsvc.Service) *Service { + s, ok := service.(*Service) + if ok { + if s.Endpoints == nil { + s.Endpoints = make(gsvc.Endpoints, 0) + } + if s.Metadata == nil { + s.Metadata = make(gsvc.Metadata) + } + return s + } + s = &Service{ + Service: service, + Endpoints: make(gsvc.Endpoints, 0), + Metadata: make(gsvc.Metadata), + } + if len(service.GetEndpoints()) > 0 { + s.Endpoints = service.GetEndpoints() + } + if len(service.GetMetadata()) > 0 { + s.Metadata = service.GetMetadata() + } + return s +} + +// GetMetadata returns the Metadata map of service. +// The Metadata is key-value pair map specifying extra attributes of a service. +func (s *Service) GetMetadata() gsvc.Metadata { + return s.Metadata +} + +// GetEndpoints returns the Endpoints of service. +// The Endpoints contain multiple host/port information of service. +func (s *Service) GetEndpoints() gsvc.Endpoints { + return s.Endpoints +} + +// GetValue formats and returns the value of the service. +// The result value is commonly used for key-value registrar server. +func (s *Service) GetValue() string { + b, _ := gjson.Marshal(s.Metadata) + return string(b) +} diff --git a/contrib/registry/file/file_watcher.go b/contrib/registry/file/file_watcher.go new file mode 100644 index 000000000..fa060ef0a --- /dev/null +++ b/contrib/registry/file/file_watcher.go @@ -0,0 +1,35 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package file + +import ( + "context" + + "github.com/gogf/gf/v2/net/gsvc" +) + +// Watcher for file changes watch. +type Watcher struct { + prefix string // Watched prefix key, not file name prefix. + discovery gsvc.Discovery // Service discovery. + ch chan gsvc.Service // Changes that caused by inotify. +} + +// Proceed proceeds watch in blocking way. +// It returns all complete services that watched by `key` if any change. +func (w *Watcher) Proceed() (services []gsvc.Service, err error) { + <-w.ch + return w.discovery.Search(context.Background(), gsvc.SearchInput{ + Prefix: w.prefix, + }) +} + +// Close closes the watcher. +func (w *Watcher) Close() error { + close(w.ch) + return nil +} diff --git a/contrib/registry/file/file_z_test.go b/contrib/registry/file/file_z_test.go new file mode 100644 index 000000000..7fc7827fb --- /dev/null +++ b/contrib/registry/file/file_z_test.go @@ -0,0 +1,150 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package file_test + +import ( + "testing" + + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/guid" +) + +func TestRegistry(t *testing.T) { + var ( + ctx = gctx.GetInitCtx() + path = gfile.Temp(guid.S()) + registry = file.New(path) + ) + defer gfile.Remove(path) + + svc := &gsvc.LocalService{ + Name: guid.S(), + Endpoints: gsvc.NewEndpoints("127.0.0.1:8888"), + Metadata: map[string]interface{}{ + "protocol": "https", + }, + } + gtest.C(t, func(t *gtest.T) { + registered, err := registry.Register(ctx, svc) + t.AssertNil(err) + t.Assert(registered.GetName(), svc.GetName()) + }) + + // Search by name. + gtest.C(t, func(t *gtest.T) { + result, err := registry.Search(ctx, gsvc.SearchInput{ + Name: svc.Name, + }) + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0].GetName(), svc.Name) + }) + + // Search by prefix. + gtest.C(t, func(t *gtest.T) { + result, err := registry.Search(ctx, gsvc.SearchInput{ + Prefix: svc.GetPrefix(), + }) + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0].GetName(), svc.Name) + }) + + // Search by metadata. + gtest.C(t, func(t *gtest.T) { + result, err := registry.Search(ctx, gsvc.SearchInput{ + Name: svc.GetName(), + Metadata: map[string]interface{}{ + "protocol": "https", + }, + }) + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0].GetName(), svc.Name) + }) + gtest.C(t, func(t *gtest.T) { + result, err := registry.Search(ctx, gsvc.SearchInput{ + Name: svc.GetName(), + Metadata: map[string]interface{}{ + "protocol": "grpc", + }, + }) + t.AssertNil(err) + t.Assert(len(result), 0) + }) + + gtest.C(t, func(t *gtest.T) { + err := registry.Deregister(ctx, svc) + t.AssertNil(err) + }) +} + +func TestWatch(t *testing.T) { + var ( + ctx = gctx.GetInitCtx() + path = gfile.Temp(guid.S()) + registry = file.New(path) + ) + defer gfile.Remove(path) + + svc1 := &gsvc.LocalService{ + Name: guid.S(), + Endpoints: gsvc.NewEndpoints("127.0.0.1:8888"), + Metadata: map[string]interface{}{ + "protocol": "https", + }, + } + gtest.C(t, func(t *gtest.T) { + registered, err := registry.Register(ctx, svc1) + t.AssertNil(err) + t.Assert(registered.GetName(), svc1.GetName()) + }) + + gtest.C(t, func(t *gtest.T) { + watcher, err := registry.Watch(ctx, svc1.GetPrefix()) + t.AssertNil(err) + + // Register another service. + svc2 := &gsvc.LocalService{ + Name: svc1.Name, + Endpoints: gsvc.NewEndpoints("127.0.0.1:9999"), + } + registered, err := registry.Register(ctx, svc2) + t.AssertNil(err) + t.Assert(registered.GetName(), svc2.GetName()) + + // Watch and retrieve the service changes: + // svc1 and svc2 is the same service name, which has 2 endpoints. + proceedResult, err := watcher.Proceed() + t.AssertNil(err) + t.Assert(len(proceedResult), 1) + t.Assert( + proceedResult[0].GetEndpoints(), + gsvc.Endpoints{svc1.GetEndpoints()[0], svc2.GetEndpoints()[0]}, + ) + + // Watch and retrieve the service changes: + // left only svc1, which means this service has only 1 endpoint. + err = registry.Deregister(ctx, svc2) + t.AssertNil(err) + proceedResult, err = watcher.Proceed() + t.AssertNil(err) + t.Assert( + proceedResult[0].GetEndpoints(), + gsvc.Endpoints{svc1.GetEndpoints()[0]}, + ) + }) + + gtest.C(t, func(t *gtest.T) { + err := registry.Deregister(ctx, svc1) + t.AssertNil(err) + }) +} diff --git a/contrib/registry/file/go.mod b/contrib/registry/file/go.mod new file mode 100644 index 000000000..f3734e7ab --- /dev/null +++ b/contrib/registry/file/go.mod @@ -0,0 +1,7 @@ +module github.com/gogf/gf/contrib/registry/file/v2 + +go 1.15 + +require github.com/gogf/gf/v2 v2.0.0 + +replace github.com/gogf/gf/v2 => ../../../ diff --git a/contrib/registry/file/go.sum b/contrib/registry/file/go.sum new file mode 100644 index 000000000..ea20ffef8 --- /dev/null +++ b/contrib/registry/file/go.sum @@ -0,0 +1,83 @@ +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= +github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0= +go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= +go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= +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/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-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y= +golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= +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.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/contrib/registry/polaris/polaris.go b/contrib/registry/polaris/polaris.go index c903e4dc9..289c62f00 100644 --- a/contrib/registry/polaris/polaris.go +++ b/contrib/registry/polaris/polaris.go @@ -133,7 +133,7 @@ func WithLogger(logger glog.ILogger) Option { } // New create a new registry. -func New(provider polaris.ProviderAPI, consumer polaris.ConsumerAPI, opts ...Option) (r *Registry) { +func New(provider polaris.ProviderAPI, consumer polaris.ConsumerAPI, opts ...Option) gsvc.Registry { op := options{ Namespace: "default", ServiceToken: "", @@ -160,7 +160,7 @@ func New(provider polaris.ProviderAPI, consumer polaris.ConsumerAPI, opts ...Opt } // NewWithConfig new a registry with config. -func NewWithConfig(conf config.Configuration, opts ...Option) (r *Registry) { +func NewWithConfig(conf config.Configuration, opts ...Option) gsvc.Registry { provider, err := polaris.NewProviderAPIByConfig(conf) if err != nil { panic(err) diff --git a/contrib/registry/polaris/polaris_discovery.go b/contrib/registry/polaris/polaris_discovery.go index d5b85a6be..a53f8f25d 100644 --- a/contrib/registry/polaris/polaris_discovery.go +++ b/contrib/registry/polaris/polaris_discovery.go @@ -11,10 +11,13 @@ import ( "fmt" "strings" - "github.com/gogf/gf/v2/net/gsvc" - "github.com/gogf/gf/v2/util/gconv" "github.com/polarismesh/polaris-go" "github.com/polarismesh/polaris-go/pkg/model" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" ) // Search returns the service instances in memory according to the service name. @@ -38,7 +41,29 @@ func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) ([]gsvc.Serv return nil, err } serviceInstances := instancesToServiceInstances(instancesResponse.GetInstances()) - return serviceInstances, nil + // Service filter. + filteredServices := make([]gsvc.Service, 0) + for _, service := range serviceInstances { + if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) { + continue + } + if in.Name != "" && service.GetName() != in.Name { + continue + } + if in.Version != "" && service.GetVersion() != in.Version { + continue + } + if len(in.Metadata) != 0 { + m1 := gmap.NewStrAnyMapFrom(in.Metadata) + m2 := gmap.NewStrAnyMapFrom(service.GetMetadata()) + if !m1.IsSubOf(m2) { + continue + } + } + resultItem := service + filteredServices = append(filteredServices, resultItem) + } + return filteredServices, nil } // Watch creates a watcher according to the service name. diff --git a/contrib/registry/zookeeper/zookeeper.go b/contrib/registry/zookeeper/zookeeper.go index 53a7aa87d..b32791855 100644 --- a/contrib/registry/zookeeper/zookeeper.go +++ b/contrib/registry/zookeeper/zookeeper.go @@ -9,14 +9,16 @@ package zookeeper import ( "github.com/go-zookeeper/zk" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/net/gsvc" "golang.org/x/sync/singleflight" "time" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/net/gsvc" ) var _ gsvc.Registry = &Registry{} +// Content for custom service Marshal/Unmarshal. type Content struct { Key string Value string diff --git a/contrib/registry/zookeeper/zookeeper_discovery.go b/contrib/registry/zookeeper/zookeeper_discovery.go index 35329185e..c930a1ce3 100644 --- a/contrib/registry/zookeeper/zookeeper_discovery.go +++ b/contrib/registry/zookeeper/zookeeper_discovery.go @@ -8,13 +8,16 @@ package zookeeper import ( "context" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/net/gsvc" "path" "strings" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/text/gstr" ) -// Search is the etcd discovery search function. +// Search searches and returns services with specified condition. func (r *Registry) Search(_ context.Context, in gsvc.SearchInput) ([]gsvc.Service, error) { prefix := strings.TrimPrefix(strings.ReplaceAll(in.Prefix, "/", "-"), "-") instances, err, _ := r.group.Do(prefix, func() (interface{}, error) { @@ -62,10 +65,33 @@ func (r *Registry) Search(_ context.Context, in gsvc.SearchInput) ([]gsvc.Servic "Error with group do", ) } - return instances.([]gsvc.Service), nil + // Service filter. + filteredServices := make([]gsvc.Service, 0) + for _, service := range instances.([]gsvc.Service) { + if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) { + continue + } + if in.Name != "" && service.GetName() != in.Name { + continue + } + if in.Version != "" && service.GetVersion() != in.Version { + continue + } + if len(in.Metadata) != 0 { + m1 := gmap.NewStrAnyMapFrom(in.Metadata) + m2 := gmap.NewStrAnyMapFrom(service.GetMetadata()) + if !m1.IsSubOf(m2) { + continue + } + } + resultItem := service + filteredServices = append(filteredServices, resultItem) + } + return filteredServices, nil } -// Watch is the etcd discovery watch function. +// Watch watches specified condition changes. +// The `key` is the prefix of service key. func (r *Registry) Watch(ctx context.Context, key string) (gsvc.Watcher, error) { return newWatcher(ctx, r.opts.namespace, key, r.conn) } diff --git a/contrib/registry/zookeeper/zookeeper_watcher.go b/contrib/registry/zookeeper/zookeeper_watcher.go index 3b03b5f7a..def4fa818 100644 --- a/contrib/registry/zookeeper/zookeeper_watcher.go +++ b/contrib/registry/zookeeper/zookeeper_watcher.go @@ -10,15 +10,17 @@ import ( "context" "errors" "github.com/go-zookeeper/zk" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/net/gsvc" "golang.org/x/sync/singleflight" "path" "strings" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/net/gsvc" ) var _ gsvc.Watcher = (*watcher)(nil) +// ErrWatcherStopped is the certain error for watcher closed. var ErrWatcherStopped = errors.New("watcher stopped") type watcher struct { @@ -43,6 +45,8 @@ func newWatcher(ctx context.Context, nameSpace, prefix string, conn *zk.Conn) (* return w, nil } +// Proceed proceeds watch in blocking way. +// It returns all complete services that watched by `key` if any change. func (w *watcher) Proceed() ([]gsvc.Service, error) { select { case <-w.ctx.Done(): @@ -111,6 +115,7 @@ func (w *watcher) getServicesByPrefix() ([]gsvc.Service, error) { return instances.([]gsvc.Service), nil } +// Close closes the watcher. func (w *watcher) Close() error { w.cancel() return nil diff --git a/contrib/rpc/grpcx/go.mod b/contrib/rpc/grpcx/go.mod new file mode 100644 index 000000000..933a1a6b5 --- /dev/null +++ b/contrib/rpc/grpcx/go.mod @@ -0,0 +1,18 @@ +module github.com/gogf/gf/contrib/rpc/grpcx/v2 + +go 1.15 + +require ( + github.com/gogf/gf/contrib/registry/file/v2 v2.0.0-20230223141509-94b2eae1bec0 + github.com/gogf/gf/v2 v2.0.0 + go.opentelemetry.io/otel v1.10.0 + go.opentelemetry.io/otel/trace v1.10.0 + golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1 // indirect + google.golang.org/grpc v1.49.0 + google.golang.org/protobuf v1.28.1 +) + +replace ( + github.com/gogf/gf/contrib/registry/file/v2 => ../../registry/file/ + github.com/gogf/gf/v2 => ../../../ +) diff --git a/contrib/rpc/grpcx/go.sum b/contrib/rpc/grpcx/go.sum new file mode 100644 index 000000000..5a78be5f1 --- /dev/null +++ b/contrib/rpc/grpcx/go.sum @@ -0,0 +1,199 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= +github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= +go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= +go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0= +go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= +go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= +go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1 h1:TWZxd/th7FbRSMret2MVQdlI8uT49QEtwZdvJrxjEHU= +golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y= +golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/contrib/rpc/grpcx/grpcx.go b/contrib/rpc/grpcx/grpcx.go new file mode 100644 index 000000000..f40cf44f4 --- /dev/null +++ b/contrib/rpc/grpcx/grpcx.go @@ -0,0 +1,40 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package grpcx provides grpc service functionalities. +package grpcx + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/balancer" + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/grpcctx" + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/resolver" +) + +type ( + modCtx = grpcctx.Ctx + modBalancer = balancer.Balancer + modResolver = resolver.Manager + modClient struct{} + modServer struct{} +) + +const ( + // FreePortAddress marks the server listens using random free port. + FreePortAddress = ":0" +) + +const ( + defaultServerName = `default` + configNodeNameGrpcServer = `grpc` +) + +var ( + Ctx = modCtx{} // Ctx is instance of module Context, which manages the context feature. + Balancer = modBalancer{} // Balancer is instance of module Balancer, which manages the load balancer features. + Resolver = modResolver{} // Resolver is instance of module Resolver, which manages the DNS resolving for client. + Client = modClient{} // Client is instance of module Client, which manages the client features. + Server = modServer{} // Server is instance of module Server, which manages the server feature. +) diff --git a/contrib/rpc/grpcx/grpcx_grpc_client.go b/contrib/rpc/grpcx/grpcx_grpc_client.go new file mode 100644 index 000000000..48015da4f --- /dev/null +++ b/contrib/rpc/grpcx/grpcx_grpc_client.go @@ -0,0 +1,77 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grpcx + +import ( + "fmt" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/gogf/gf/v2/net/gsel" + "github.com/gogf/gf/v2/net/gsvc" +) + +// DefaultGrpcDialOptions returns the default options for creating grpc client connection. +func (c modClient) DefaultGrpcDialOptions() []grpc.DialOption { + return []grpc.DialOption{ + Balancer.WithName(gsel.GetBuilder().Name()), + grpc.WithTransportCredentials(insecure.NewCredentials()), + } +} + +// NewGrpcClientConn creates and returns a client connection for given service `appId`. +func (c modClient) NewGrpcClientConn(serviceName string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { + autoLoadAndRegisterFileRegistry() + + var ( + service = gsvc.NewServiceWithName(serviceName) + grpcClientOptions = make([]grpc.DialOption, 0) + ) + grpcClientOptions = append(grpcClientOptions, c.DefaultGrpcDialOptions()...) + if len(opts) > 0 { + grpcClientOptions = append(grpcClientOptions, opts...) + } + grpcClientOptions = append(grpcClientOptions, c.ChainUnary( + c.UnaryTracing, + c.UnaryError, + )) + grpcClientOptions = append(grpcClientOptions, c.ChainStream( + c.StreamTracing, + )) + conn, err := grpc.Dial(fmt.Sprintf(`%s://%s`, gsvc.Schema, service.GetKey()), grpcClientOptions...) + if err != nil { + return nil, err + } + return conn, nil +} + +// MustNewGrpcClientConn creates and returns a client connection for given service `appId`. +// It panics if any error occurs. +func (c modClient) MustNewGrpcClientConn(serviceName string, opts ...grpc.DialOption) *grpc.ClientConn { + conn, err := c.NewGrpcClientConn(serviceName, opts...) + if err != nil { + panic(err) + } + return conn +} + +// ChainUnary creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainUnaryClient(one, two, three) will execute one before two before three. +func (c modClient) ChainUnary(interceptors ...grpc.UnaryClientInterceptor) grpc.DialOption { + return grpc.WithChainUnaryInterceptor(interceptors...) +} + +// ChainStream creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainStreamClient(one, two, three) will execute one before two before three. +func (c modClient) ChainStream(interceptors ...grpc.StreamClientInterceptor) grpc.DialOption { + return grpc.WithChainStreamInterceptor(interceptors...) +} diff --git a/contrib/rpc/grpcx/grpcx_grpc_server.go b/contrib/rpc/grpcx/grpcx_grpc_server.go new file mode 100644 index 000000000..ca4732adb --- /dev/null +++ b/contrib/rpc/grpcx/grpcx_grpc_server.go @@ -0,0 +1,264 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grpcx + +import ( + "fmt" + "net" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "google.golang.org/grpc" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gipv4" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/text/gstr" +) + +// GrpcServer is the server for GRPC protocol. +type GrpcServer struct { + Server *grpc.Server + config *GrpcServerConfig + listener net.Listener + services []gsvc.Service + waitGroup sync.WaitGroup + registrar gsvc.Registrar +} + +// Service implements gsvc.Service interface. +type Service struct { + gsvc.Service + Endpoints gsvc.Endpoints +} + +// New creates and returns a grpc server. +func (s modServer) New(conf ...*GrpcServerConfig) *GrpcServer { + autoLoadAndRegisterFileRegistry() + + var ( + ctx = gctx.GetInitCtx() + config *GrpcServerConfig + ) + if len(conf) > 0 { + config = conf[0] + } else { + config = s.NewConfig() + } + if config.Address == "" { + randomPort, err := gtcp.GetFreePort() + if err != nil { + g.Log().Fatalf(ctx, `%+v`, err) + } + config.Address = fmt.Sprintf(`:%d`, randomPort) + } + if !gstr.Contains(config.Address, ":") { + g.Log().Fatal(ctx, "invalid service address, should contain listening port") + } + if config.Logger == nil { + config.Logger = glog.New() + } + grpcServer := &GrpcServer{ + config: config, + registrar: gsvc.GetRegistry(), + } + grpcServer.config.Options = append([]grpc.ServerOption{ + s.ChainUnary( + s.UnaryRecover, + s.UnaryTracing, + s.UnaryError, + grpcServer.UnaryLogger, + ), + s.ChainStream( + s.StreamTracing, + ), + }, grpcServer.config.Options...) + grpcServer.Server = grpc.NewServer(grpcServer.config.Options...) + return grpcServer +} + +// Service binds service list to current server. +// Server will automatically register the service list after it starts. +func (s *GrpcServer) Service(services ...gsvc.Service) { + s.services = append(s.services, services...) +} + +// Run starts the server in blocking way. +func (s *GrpcServer) Run() { + var ( + err error + ctx = gctx.GetInitCtx() + ) + // Create listener to bind listening ip and port. + s.listener, err = net.Listen("tcp", s.config.Address) + if err != nil { + s.config.Logger.Fatalf(ctx, `%+v`, err) + } + + // Start listening. + go func() { + if err = s.Server.Serve(s.listener); err != nil { + s.config.Logger.Fatalf(ctx, `%+v`, err) + } + }() + + // Service register. + s.doServiceRegister() + s.config.Logger.Infof( + ctx, + "pid[%d]: grpc server started listening on [%s]", + gproc.Pid(), s.GetListenedAddress(), + ) + s.doSignalListen() +} + +// doSignalListen does signal listening and handling for gracefully shutdown. +func (s *GrpcServer) doSignalListen() { + var ( + ctx = gctx.GetInitCtx() + sigChan = make(chan os.Signal, 1) + ) + signal.Notify( + sigChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig := <-sigChan + switch sig { + case + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT: + s.config.Logger.Infof(ctx, "signal received: %s, gracefully shutting down", sig.String()) + s.doServiceDeregister() + time.Sleep(time.Second) + s.Stop() + return + } + } +} + +// doServiceRegister registers current service to Registry. +func (s *GrpcServer) doServiceRegister() { + if s.registrar == nil { + return + } + if len(s.services) == 0 { + s.services = []gsvc.Service{&gsvc.LocalService{ + Name: s.config.Name, + Metadata: gsvc.Metadata{}, + }} + } + var ( + err error + ctx = gctx.GetInitCtx() + protocol = `grpc` + ) + // Register service list after server starts. + for i, service := range s.services { + service = &gsvc.LocalService{ + Name: service.GetName(), + Endpoints: s.calculateListenedEndpoints(), + Metadata: service.GetMetadata(), + } + service.GetMetadata().Sets(gsvc.Metadata{ + gsvc.MDProtocol: protocol, + }) + s.config.Logger.Debugf(ctx, `service register: %+v`, service) + if service, err = s.registrar.Register(ctx, service); err != nil { + s.config.Logger.Fatalf(ctx, `%+v`, err) + } + s.services[i] = service + } +} + +// doServiceDeregister de-registers current service from Registry. +func (s *GrpcServer) doServiceDeregister() { + if s.registrar == nil { + return + } + var ctx = gctx.GetInitCtx() + for _, service := range s.services { + s.config.Logger.Debugf(ctx, `service deregister: %+v`, service) + if err := s.registrar.Deregister(ctx, service); err != nil { + s.config.Logger.Errorf(ctx, `%+v`, err) + } + } +} + +// Start starts the server in no-blocking way. +func (s *GrpcServer) Start() { + s.waitGroup.Add(1) + go func() { + defer s.waitGroup.Done() + s.Run() + }() +} + +// Wait works with Start, which blocks current goroutine until the server stops. +func (s *GrpcServer) Wait() { + s.waitGroup.Wait() +} + +// Stop gracefully stops the server. +func (s *GrpcServer) Stop() { + s.Server.GracefulStop() +} + +// GetListenedAddress retrieves and returns the address string which are listened by current server. +func (s *GrpcServer) GetListenedAddress() string { + if !gstr.Contains(s.config.Address, FreePortAddress) { + return s.config.Address + } + var ( + address = s.config.Address + listenedPort = s.GetListenedPort() + ) + address = gstr.Replace(address, FreePortAddress, fmt.Sprintf(`:%d`, listenedPort)) + return address +} + +// GetListenedPort retrieves and returns one port which is listened to by current server. +func (s *GrpcServer) GetListenedPort() int { + if ln := s.listener; ln != nil { + return ln.Addr().(*net.TCPAddr).Port + } + return -1 +} + +func (s *GrpcServer) calculateListenedEndpoints() gsvc.Endpoints { + var ( + address = s.config.Address + endpoints = make(gsvc.Endpoints, 0) + listenedPort = s.GetListenedPort() + listenedIps []string + ) + var addrArray = gstr.Split(address, ":") + switch addrArray[0] { + case "0.0.0.0", "": + listenedIps = []string{gipv4.MustGetIntranetIp()} + default: + listenedIps = []string{addrArray[0]} + } + for _, ip := range listenedIps { + endpoints = append(endpoints, gsvc.NewEndpoint(fmt.Sprintf(`%s:%d`, ip, listenedPort))) + } + return endpoints +} diff --git a/contrib/rpc/grpcx/grpcx_grpc_server_config.go b/contrib/rpc/grpcx/grpcx_grpc_server_config.go new file mode 100644 index 000000000..77e79a0fe --- /dev/null +++ b/contrib/rpc/grpcx/grpcx_grpc_server_config.go @@ -0,0 +1,72 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grpcx + +import ( + "context" + + "google.golang.org/grpc" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/util/gconv" +) + +// GrpcServerConfig is the configuration for server. +type GrpcServerConfig struct { + Address string // (optional) Address for server listening. + Name string // (optional) Name for current service. + Logger *glog.Logger // (optional) Logger for server. + LogPath string // (optional) LogPath specifies the directory for storing logging files. + LogStdout bool // (optional) LogStdout specifies whether printing logging content to stdout. + ErrorStack bool // (optional) ErrorStack specifies whether logging stack information when error. + ErrorLogEnabled bool // (optional) ErrorLogEnabled enables error logging content to files. + ErrorLogPattern string // (optional) ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log + AccessLogEnabled bool // (optional) AccessLogEnabled enables access logging content to file. + AccessLogPattern string // (optional) AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log + Options []grpc.ServerOption // (optional) GRPC Server options. +} + +// NewConfig creates and returns a ServerConfig object with default configurations. +// Note that, do not define this default configuration to local package variable, as there are +// some pointer attributes that may be shared in different servers. +func (s modServer) NewConfig() *GrpcServerConfig { + var ( + err error + ctx = context.TODO() + config = &GrpcServerConfig{ + Name: defaultServerName, + Logger: glog.New(), + LogStdout: true, + ErrorLogEnabled: true, + ErrorLogPattern: "error-{Ymd}.log", + AccessLogEnabled: false, + AccessLogPattern: "access-{Ymd}.log", + } + ) + // Reading configuration file and updating the configured keys. + if g.Cfg().Available(ctx) { + if err = g.Cfg().MustGet(ctx, configNodeNameGrpcServer).Struct(&config); err != nil { + g.Log().Error(ctx, err) + } + } + return config +} + +// SetWithMap changes current configuration with map. +// This is commonly used for changing several configurations of current object. +func (c *GrpcServerConfig) SetWithMap(m g.Map) error { + return gconv.Struct(m, c) +} + +// MustSetWithMap acts as SetWithMap but panics if error occurs. +func (c *GrpcServerConfig) MustSetWithMap(m g.Map) { + err := c.SetWithMap(m) + if err != nil { + panic(err) + } +} diff --git a/contrib/rpc/grpcx/grpcx_grpc_server_unary.go b/contrib/rpc/grpcx/grpcx_grpc_server_unary.go new file mode 100644 index 000000000..d7e2953e8 --- /dev/null +++ b/contrib/rpc/grpcx/grpcx_grpc_server_unary.go @@ -0,0 +1,89 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grpcx + +import ( + "context" + "fmt" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/text/gstr" +) + +// UnaryLogger is the default unary interceptor for logging purpose. +func (s *GrpcServer) UnaryLogger( + ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, +) (interface{}, error) { + var ( + start = time.Now() + res, err = handler(ctx, req) + duration = time.Since(start) + ) + s.handleAccessLog(ctx, err, duration, info, req, res) + s.handleErrorLog(ctx, err, duration, info, req, res) + return res, err +} + +// handleAccessLog handles the access logging for server. +func (s *GrpcServer) handleAccessLog( + ctx context.Context, err error, duration time.Duration, info *grpc.UnaryServerInfo, req, res interface{}, +) { + if !s.config.AccessLogEnabled { + return + } + content := fmt.Sprintf( + "%s, %.3fms, %+v, %+v", + info.FullMethod, float64(duration)/1e6, req, res, + ) + s.config.Logger.Stdout(s.config.LogStdout).File(s.config.AccessLogPattern).Print(ctx, content) +} + +// handleErrorLog handles the error logging for server. +func (s *GrpcServer) handleErrorLog( + ctx context.Context, err error, duration time.Duration, info *grpc.UnaryServerInfo, req, res interface{}, +) { + // It does nothing if error logging is custom disabled. + if !s.config.ErrorLogEnabled || err == nil { + return + } + var ( + code = gerror.Code(err) + codeDetail = code.Detail() + codeDetailStr string + grpcCode codes.Code + grpcMessage string + ) + if grpcStatus, ok := status.FromError(err); ok { + grpcCode = grpcStatus.Code() + grpcMessage = grpcStatus.Message() + } + if codeDetail != nil { + codeDetailStr = gstr.Replace(fmt.Sprintf(`%+v`, codeDetail), "\n", " ") + } + content := fmt.Sprintf( + `%s, %.3fms, %d, "%s", %+v, %+v, %d, "%s", "%s"`, + info.FullMethod, float64(duration)/1e6, grpcCode, grpcMessage, + req, res, code.Code(), code.Message(), codeDetailStr, + ) + if s.config.ErrorStack { + if stack := gerror.Stack(err); stack != "" { + content += "\nStack:\n" + stack + } else { + content += ", " + err.Error() + } + } else { + content += ", " + err.Error() + } + s.config.Logger.Stack(false). + Stdout(s.config.LogStdout). + File(s.config.ErrorLogPattern).Error(ctx, content) +} diff --git a/contrib/rpc/grpcx/grpcx_interceptor_client.go b/contrib/rpc/grpcx/grpcx_interceptor_client.go new file mode 100644 index 000000000..d14182349 --- /dev/null +++ b/contrib/rpc/grpcx/grpcx_interceptor_client.go @@ -0,0 +1,50 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grpcx + +import ( + "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/status" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/tracing" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" +) + +// UnaryError handles the error types converting between grpc and gerror. +// Note that, the minus error code is only used locally which will not be sent to other side. +func (c modClient) UnaryError(ctx context.Context, method string, req, reply interface{}, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + err := invoker(ctx, method, req, reply, cc, opts...) + if err != nil { + grpcStatus, ok := status.FromError(err) + if ok { + if code := grpcStatus.Code(); code != 0 { + return gerror.NewCode(gcode.New(int(code), "", nil), grpcStatus.Message()) + } + return gerror.New(grpcStatus.Message()) + } + } + return err +} + +// UnaryTracing is a unary interceptor for adding tracing feature for gRPC client using OpenTelemetry. +func (c modClient) UnaryTracing( + ctx context.Context, method string, req, reply interface{}, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + return tracing.UnaryClientInterceptor(ctx, method, req, reply, cc, invoker, opts...) +} + +// StreamTracing is a stream interceptor for adding tracing feature for gRPC client using OpenTelemetry. +func (c modClient) StreamTracing( + ctx context.Context, desc *grpc.StreamDesc, + cc *grpc.ClientConn, method string, streamer grpc.Streamer, + callOpts ...grpc.CallOption) (grpc.ClientStream, error) { + return tracing.StreamClientInterceptor(ctx, desc, cc, method, streamer, callOpts...) +} diff --git a/contrib/rpc/grpcx/grpcx_interceptor_server.go b/contrib/rpc/grpcx/grpcx_interceptor_server.go new file mode 100644 index 000000000..e83c53f00 --- /dev/null +++ b/contrib/rpc/grpcx/grpcx_interceptor_server.go @@ -0,0 +1,91 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grpcx + +import ( + "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/tracing" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gutil" +) + +// ChainUnary returns a ServerOption that specifies the chained interceptor +// for unary RPCs. The first interceptor will be the outermost, +// while the last interceptor will be the innermost wrapper around the real call. +// All unary interceptors added by this method will be chained. +func (s modServer) ChainUnary(interceptors ...grpc.UnaryServerInterceptor) grpc.ServerOption { + return grpc.ChainUnaryInterceptor(interceptors...) +} + +// ChainStream returns a ServerOption that specifies the chained interceptor +// for stream RPCs. The first interceptor will be the outermost, +// while the last interceptor will be the innermost wrapper around the real call. +// All stream interceptors added by this method will be chained. +func (s modServer) ChainStream(interceptors ...grpc.StreamServerInterceptor) grpc.ServerOption { + return grpc.ChainStreamInterceptor(interceptors...) +} + +// UnaryError is the default unary interceptor for error converting from custom error to grpc error. +func (s modServer) UnaryError( + ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, +) (interface{}, error) { + res, err := handler(ctx, req) + if err != nil { + code := gerror.Code(err) + if code.Code() != -1 { + err = status.Error(codes.Code(code.Code()), err.Error()) + } + } + return res, err +} + +// UnaryRecover is the first interceptor that keep server not down from panics. +func (s modServer) UnaryRecover( + ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, +) (res interface{}, err error) { + gutil.TryCatch(ctx, func(ctx2 context.Context) { + res, err = handler(ctx, req) + }, func(ctx context.Context, exception error) { + err = gerror.WrapCode(gcode.New(int(codes.Internal), "", nil), err, "panic recovered") + }) + return +} + +// UnaryValidate Common validation unary interpreter. +func (s modServer) UnaryValidate( + ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, +) (interface{}, error) { + // It does nothing if there's no validation tag in the struct definition. + if err := g.Validator().Data(req).Run(ctx); err != nil { + return nil, gerror.NewCode( + gcode.New(int(codes.InvalidArgument), "", nil), + gerror.Current(err).Error(), + ) + } + return handler(ctx, req) +} + +// UnaryTracing is a unary interceptor for adding tracing feature for gRPC server using OpenTelemetry. +func (s modServer) UnaryTracing( + ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, +) (interface{}, error) { + return tracing.UnaryServerInterceptor(ctx, req, info, handler) +} + +// StreamTracing is a stream unary interceptor for adding tracing feature for gRPC server using OpenTelemetry. +func (s modServer) StreamTracing( + srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, +) error { + return tracing.StreamServerInterceptor(srv, ss, info, handler) +} diff --git a/contrib/rpc/grpcx/grpcx_registry_file.go b/contrib/rpc/grpcx/grpcx_registry_file.go new file mode 100644 index 000000000..8886f9641 --- /dev/null +++ b/contrib/rpc/grpcx/grpcx_registry_file.go @@ -0,0 +1,34 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grpcx + +import ( + "github.com/gogf/gf/contrib/registry/file/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/resolver" +) + +// autoLoadAndRegisterFileRegistry checks and registers ETCD service as default service registry +// if no registry is registered previously. +func autoLoadAndRegisterFileRegistry() { + // It ignores etcd registry if any registry already registered. + if gsvc.GetRegistry() != nil { + return + } + var ( + ctx = gctx.GetInitCtx() + fileRegistry = file.New(gfile.Temp("gsvc")) + ) + + g.Log().Debug(ctx, `set default registry using file registry as no custom registry set`) + resolver.SetRegistry(fileRegistry) +} diff --git a/contrib/rpc/grpcx/grpcx_unit_ctx_test.go b/contrib/rpc/grpcx/grpcx_unit_ctx_test.go new file mode 100644 index 000000000..cacf4e294 --- /dev/null +++ b/contrib/rpc/grpcx/grpcx_unit_ctx_test.go @@ -0,0 +1,63 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grpcx_test + +import ( + "context" + "testing" + + "google.golang.org/grpc/metadata" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/test/gtest" +) + +func Test_Ctx_Basic(t *testing.T) { + ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs( + "k1", "v1", + "k2", "v2", + )) + gtest.C(t, func(t *gtest.T) { + m1 := grpcx.Ctx.IncomingMap(ctx) + t.Assert(m1.Get("k1"), "v1") + t.Assert(m1.Get("k2"), "v2") + m2 := grpcx.Ctx.OutgoingMap(ctx) + t.Assert(m2.Size(), 0) + }) + gtest.C(t, func(t *gtest.T) { + ctx := grpcx.Ctx.IncomingToOutgoing(ctx) + m1 := grpcx.Ctx.IncomingMap(ctx) + t.Assert(m1.Get("k1"), "v1") + t.Assert(m1.Get("k2"), "v2") + m2 := grpcx.Ctx.OutgoingMap(ctx) + t.Assert(m2.Get("k1"), "v1") + t.Assert(m2.Get("k2"), "v2") + }) + gtest.C(t, func(t *gtest.T) { + ctx := grpcx.Ctx.IncomingToOutgoing(ctx, "k1") + m1 := grpcx.Ctx.IncomingMap(ctx) + t.Assert(m1.Get("k1"), "v1") + t.Assert(m1.Get("k2"), "v2") + m2 := grpcx.Ctx.OutgoingMap(ctx) + t.Assert(m2.Get("k1"), "v1") + t.Assert(m2.Get("k2"), "") + }) + gtest.C(t, func(t *gtest.T) { + ctx := grpcx.Ctx.NewIncoming(ctx) + ctx = grpcx.Ctx.SetIncoming(ctx, g.Map{"k1": "v1"}) + ctx = grpcx.Ctx.SetIncoming(ctx, g.Map{"k2": "v2"}) + ctx = grpcx.Ctx.SetOutgoing(ctx, g.Map{"k3": "v3"}) + ctx = grpcx.Ctx.SetOutgoing(ctx, g.Map{"k4": "v4"}) + m1 := grpcx.Ctx.IncomingMap(ctx) + t.Assert(m1.Get("k1"), "v1") + t.Assert(m1.Get("k2"), "v2") + m2 := grpcx.Ctx.OutgoingMap(ctx) + t.Assert(m2.Get("k3"), "v3") + t.Assert(m2.Get("k4"), "v4") + }) +} diff --git a/contrib/rpc/grpcx/internal/balancer/balancer.go b/contrib/rpc/grpcx/internal/balancer/balancer.go new file mode 100644 index 000000000..389f5142a --- /dev/null +++ b/contrib/rpc/grpcx/internal/balancer/balancer.go @@ -0,0 +1,77 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package balancer defines APIs for load balancing in gRPC. +package balancer + +import ( + "fmt" + + "google.golang.org/grpc" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/balancer/base" + + "github.com/gogf/gf/v2/net/gsel" +) + +type Balancer struct{} + +const ( + rawSvcKeyInSubConnInfo = `RawService` +) + +var ( + Random = gsel.NewBuilderRandom() + Weight = gsel.NewBuilderWeight() + RoundRobin = gsel.NewBuilderRoundRobin() + LeastConnection = gsel.NewBuilderLeastConnection() +) + +func init() { + b := Balancer{} + b.Register(Random, Weight, RoundRobin, LeastConnection) +} + +// Register registers the given balancer builder with the given name. +func (Balancer) Register(builders ...gsel.Builder) { + for _, builder := range builders { + balancer.Register( + base.NewBalancerBuilder( + builder.Name(), + &Builder{builder: builder}, + base.Config{HealthCheck: true}, + ), + ) + } +} + +// WithRandom returns a grpc.DialOption which enables random load balancing. +func (b Balancer) WithRandom() grpc.DialOption { + return b.WithName(Random.Name()) +} + +// WithWeight returns a grpc.DialOption which enables weight load balancing. +func (b Balancer) WithWeight() grpc.DialOption { + return b.WithName(Weight.Name()) +} + +// WithRoundRobin returns a grpc.DialOption which enables round-robin load balancing. +func (b Balancer) WithRoundRobin() grpc.DialOption { + return b.WithName(RoundRobin.Name()) +} + +// WithLeastConnection returns a grpc.DialOption which enables the least connection load balancing. +func (b Balancer) WithLeastConnection() grpc.DialOption { + return b.WithName(LeastConnection.Name()) +} + +// WithName returns a grpc.DialOption which enables the load balancing by name. +func (b Balancer) WithName(name string) grpc.DialOption { + return grpc.WithDefaultServiceConfig(fmt.Sprintf( + `{"loadBalancingPolicy": "%s"}`, + name, + )) +} diff --git a/contrib/rpc/grpcx/internal/balancer/balancer_builder.go b/contrib/rpc/grpcx/internal/balancer/balancer_builder.go new file mode 100644 index 000000000..e87d7f3a0 --- /dev/null +++ b/contrib/rpc/grpcx/internal/balancer/balancer_builder.go @@ -0,0 +1,52 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package balancer + +import ( + "context" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/balancer/base" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsel" + "github.com/gogf/gf/v2/net/gsvc" +) + +// Builder implements grpc balancer base.PickerBuilder, +// which returns a picker that will be used by gRPC to pick a SubConn. +type Builder struct { + builder gsel.Builder +} + +// Build returns a picker that will be used by gRPC to pick a SubConn. +func (b *Builder) Build(info base.PickerBuildInfo) balancer.Picker { + if len(info.ReadySCs) == 0 { + return base.NewErrPicker(balancer.ErrNoSubConnAvailable) + } + var ( + ctx = context.Background() + nodes = make([]gsel.Node, 0) + ) + for conn, subConnInfo := range info.ReadySCs { + svc, _ := subConnInfo.Address.Attributes.Value(rawSvcKeyInSubConnInfo).(gsvc.Service) + if svc == nil { + g.Log().Noticef(ctx, `empty service read from: %+v`, subConnInfo.Address) + continue + } + nodes = append(nodes, &Node{ + service: svc, + conn: conn, + }) + } + p := &Picker{ + selector: b.builder.Build(), + } + if err := p.selector.Update(ctx, nodes); err != nil { + panic(err) + } + return p +} diff --git a/contrib/rpc/grpcx/internal/balancer/balancer_node.go b/contrib/rpc/grpcx/internal/balancer/balancer_node.go new file mode 100644 index 000000000..53477d0b1 --- /dev/null +++ b/contrib/rpc/grpcx/internal/balancer/balancer_node.go @@ -0,0 +1,33 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package balancer + +import ( + "google.golang.org/grpc/balancer" + + "github.com/gogf/gf/v2/net/gsvc" +) + +// Node is the node for the balancer. +type Node struct { + service gsvc.Service + conn balancer.SubConn +} + +// Service returns the service of the node. +func (n *Node) Service() gsvc.Service { + return n.service +} + +// Address returns the address of the node. +func (n *Node) Address() string { + endpoints := n.service.GetEndpoints() + if len(endpoints) == 0 { + return "" + } + return endpoints[0].String() +} diff --git a/contrib/rpc/grpcx/internal/balancer/balancer_picker.go b/contrib/rpc/grpcx/internal/balancer/balancer_picker.go new file mode 100644 index 000000000..c16b469e4 --- /dev/null +++ b/contrib/rpc/grpcx/internal/balancer/balancer_picker.go @@ -0,0 +1,64 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package balancer + +import ( + "google.golang.org/grpc/balancer" + + "github.com/gogf/gf/v2/net/gsel" +) + +// Picker implements grpc balancer.Picker, +// which is used by gRPC to pick a SubConn to send an RPC. +// Balancer is expected to generate a new picker from its snapshot every time its +// internal state has changed. +// +// The pickers used by gRPC can be updated by ClientConn.UpdateState(). +type Picker struct { + selector gsel.Selector +} + +// Pick returns the connection to use for this RPC and related information. +// +// Pick should not block. If the balancer needs to do I/O or any blocking +// or time-consuming work to service this call, it should return +// ErrNoSubConnAvailable, and the Pick call will be repeated by gRPC when +// the Picker is updated (using ClientConn.UpdateState). +// +// If an error is returned: +// +// - If the error is ErrNoSubConnAvailable, gRPC will block until a new +// Picker is provided by the balancer (using ClientConn.UpdateState). +// +// - If the error is a status error (implemented by the grpc/status +// package), gRPC will terminate the RPC with the code and message +// provided. +// +// - For all other errors, wait for ready RPCs will wait, but non-wait for +// ready RPCs will be terminated with this error's Error() string and +// status code Unavailable. +func (p *Picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { + node, done, err := p.selector.Pick(info.Ctx) + if err != nil { + return balancer.PickResult{}, err + } + return balancer.PickResult{ + SubConn: node.(*Node).conn, + Done: func(di balancer.DoneInfo) { + if done == nil { + return + } + done(info.Ctx, gsel.DoneInfo{ + Err: di.Err, + Trailer: di.Trailer, + BytesSent: di.BytesSent, + BytesReceived: di.BytesReceived, + ServerLoad: di.ServerLoad, + }) + }, + }, nil +} diff --git a/contrib/rpc/grpcx/internal/grpcctx/grpcctx.go b/contrib/rpc/grpcx/internal/grpcctx/grpcctx.go new file mode 100644 index 000000000..f6a6a59db --- /dev/null +++ b/contrib/rpc/grpcx/internal/grpcctx/grpcctx.go @@ -0,0 +1,117 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package grpcctx provides context feature for GRPC. +package grpcctx + +import ( + "context" + + "google.golang.org/grpc/metadata" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +type ( + Ctx struct{} +) + +func (c Ctx) NewIncoming(ctx context.Context, data ...g.Map) context.Context { + if len(data) > 0 { + incomingMd := make(metadata.MD) + for key, value := range data[0] { + incomingMd.Set(key, gconv.String(value)) + } + return metadata.NewIncomingContext(ctx, incomingMd) + } + return metadata.NewIncomingContext(ctx, nil) +} + +func (c Ctx) NewOutgoing(ctx context.Context, data ...g.Map) context.Context { + if len(data) > 0 { + outgoingMd := make(metadata.MD) + for key, value := range data[0] { + outgoingMd.Set(key, gconv.String(value)) + } + return metadata.NewOutgoingContext(ctx, outgoingMd) + } + return metadata.NewOutgoingContext(ctx, nil) +} + +func (c Ctx) IncomingToOutgoing(ctx context.Context, keys ...string) context.Context { + incomingMd, _ := metadata.FromIncomingContext(ctx) + if incomingMd == nil { + return ctx + } + outgoingMd, _ := metadata.FromOutgoingContext(ctx) + if outgoingMd == nil { + outgoingMd = make(metadata.MD) + } + if len(keys) > 0 { + for _, key := range keys { + outgoingMd[key] = append(outgoingMd[key], incomingMd.Get(key)...) + } + } else { + for key, values := range incomingMd { + outgoingMd[key] = append(outgoingMd[key], values...) + } + } + return metadata.NewOutgoingContext(ctx, outgoingMd) +} + +func (c Ctx) IncomingMap(ctx context.Context) *gmap.Map { + var ( + data = gmap.New() + incomingMd, _ = metadata.FromIncomingContext(ctx) + ) + for key, values := range incomingMd { + if len(values) == 1 { + data.Set(key, values[0]) + } else { + data.Set(key, values) + } + } + return data +} + +func (c Ctx) OutgoingMap(ctx context.Context) *gmap.Map { + var ( + data = gmap.New() + outgoingMd, _ = metadata.FromOutgoingContext(ctx) + ) + for key, values := range outgoingMd { + if len(values) == 1 { + data.Set(key, values[0]) + } else { + data.Set(key, values) + } + } + return data +} + +func (c Ctx) SetIncoming(ctx context.Context, data g.Map) context.Context { + incomingMd, _ := metadata.FromIncomingContext(ctx) + if incomingMd == nil { + incomingMd = make(metadata.MD) + } + for key, value := range data { + incomingMd.Set(key, gconv.String(value)) + } + return metadata.NewIncomingContext(ctx, incomingMd) +} + +func (c Ctx) SetOutgoing(ctx context.Context, data g.Map) context.Context { + outgoingMd, _ := metadata.FromOutgoingContext(ctx) + if outgoingMd == nil { + outgoingMd = make(metadata.MD) + } + for key, value := range data { + outgoingMd.Set(key, gconv.String(value)) + } + return metadata.NewOutgoingContext(ctx, outgoingMd) +} diff --git a/contrib/rpc/grpcx/internal/resolver/resolver.go b/contrib/rpc/grpcx/internal/resolver/resolver.go new file mode 100644 index 000000000..3be402685 --- /dev/null +++ b/contrib/rpc/grpcx/internal/resolver/resolver.go @@ -0,0 +1,35 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package resolver defines APIs for name resolution in gRPC. +package resolver + +import ( + "google.golang.org/grpc/resolver" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/net/gsvc" +) + +const ( + rawSvcKeyInSubConnInfo = `RawService` +) + +func init() { + // It registers default resolver here. + // It uses default builder handling the name resolving for grpc service requests. + // Use `grpc.WithResolver` to custom resolver for client. + resolver.Register(NewBuilder(gsvc.GetRegistry())) +} + +// SetRegistry sets the default Registry implements as your own implemented interface. +func SetRegistry(registry gsvc.Registry) { + if registry == nil { + panic(gerror.New(`invalid Registry value "nil" given`)) + } + gsvc.SetRegistry(registry) + resolver.Register(NewBuilder(registry)) +} diff --git a/contrib/rpc/grpcx/internal/resolver/resolver_builder.go b/contrib/rpc/grpcx/internal/resolver/resolver_builder.go new file mode 100644 index 000000000..e4b140198 --- /dev/null +++ b/contrib/rpc/grpcx/internal/resolver/resolver_builder.go @@ -0,0 +1,63 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package resolver + +import ( + "context" + + "google.golang.org/grpc/resolver" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" +) + +// Builder is the builder for the etcd discovery resolver. +type Builder struct { + discovery gsvc.Discovery +} + +// NewBuilder creates and returns a Builder. +func NewBuilder(discovery gsvc.Discovery) *Builder { + return &Builder{ + discovery: discovery, + } +} + +// Build creates a new etcd discovery resolver. +func (b *Builder) Build( + target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions, +) (resolver.Resolver, error) { + var ( + err error + watcher gsvc.Watcher + watchKey = target.URL.Path + ctx, cancel = context.WithCancel(gctx.GetInitCtx()) + ) + g.Log().Debugf(ctx, `Watch key "%s"`, watchKey) + if watcher, err = b.discovery.Watch(ctx, watchKey); err != nil { + cancel() + return nil, gerror.Wrap(err, `registry.Watch failed`) + } + r := &Resolver{ + discovery: b.discovery, + watcher: watcher, + watchKey: watchKey, + cc: cc, + ctx: ctx, + cancel: cancel, + logger: g.Log(), + } + go r.watch() + return r, nil +} + +// Scheme return scheme of discovery +func (*Builder) Scheme() string { + return gsvc.Schema +} diff --git a/contrib/rpc/grpcx/internal/resolver/resolver_manager.go b/contrib/rpc/grpcx/internal/resolver/resolver_manager.go new file mode 100644 index 000000000..d9ea97716 --- /dev/null +++ b/contrib/rpc/grpcx/internal/resolver/resolver_manager.go @@ -0,0 +1,20 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package resolver + +import ( + "google.golang.org/grpc/resolver" + + "github.com/gogf/gf/v2/net/gsvc" +) + +// Manager for Builder creating. +type Manager struct{} + +func (m Manager) New(discovery gsvc.Discovery) resolver.Builder { + return NewBuilder(discovery) +} diff --git a/contrib/rpc/grpcx/internal/resolver/resolver_resolver.go b/contrib/rpc/grpcx/internal/resolver/resolver_resolver.go new file mode 100644 index 000000000..7ba12ca31 --- /dev/null +++ b/contrib/rpc/grpcx/internal/resolver/resolver_resolver.go @@ -0,0 +1,123 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package resolver + +import ( + "context" + "errors" + "time" + + "google.golang.org/grpc/attributes" + "google.golang.org/grpc/resolver" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/glog" +) + +// Resolver implements grpc resolver.Resolver, +// which watches for the updates on the specified target. +// Updates include address updates and service config updates. +type Resolver struct { + discovery gsvc.Discovery // Service discovery. + watcher gsvc.Watcher // Service watcher + watchKey string // Watched key. + cc resolver.ClientConn // GRPC client conn. + ctx context.Context + cancel context.CancelFunc + logger *glog.Logger +} + +func (r *Resolver) watch() { + var ( + err error + services []gsvc.Service + ) + // It updates the resolver state in time. + services, err = r.discovery.Search(r.ctx, gsvc.SearchInput{ + Prefix: r.watchKey, + }) + if err != nil && !errors.Is(err, context.Canceled) { + r.logger.Warningf(r.ctx, `discovery.Search error: %+v`, err) + } + if len(services) > 0 { + r.update(services) + } + // Then watch. + for { + select { + case <-r.ctx.Done(): + return + + default: + services, err = r.watcher.Proceed() + if err != nil && !errors.Is(err, context.Canceled) { + r.logger.Warningf(r.ctx, `watcher.Proceed error: %+v`, err) + time.Sleep(time.Second) + continue + } + if len(services) > 0 { + r.update(services) + } + } + } +} + +func (r *Resolver) update(services []gsvc.Service) { + var ( + err error + addresses = make([]resolver.Address, 0) + ) + for _, service := range services { + for _, endpoint := range service.GetEndpoints() { + addr := resolver.Address{ + Addr: endpoint.String(), + ServerName: service.GetName(), + Attributes: newAttributesFromMetadata(service.GetMetadata()), + } + addr.Attributes = addr.Attributes.WithValue(rawSvcKeyInSubConnInfo, service) + addresses = append(addresses, addr) + } + } + if len(addresses) == 0 { + r.logger.Noticef(r.ctx, "empty addresses parsed from: %+v", services) + return + } + r.logger.Debugf(r.ctx, "client conn updated with addresses %s", gjson.MustEncodeString(addresses)) + if err = r.cc.UpdateState(resolver.State{Addresses: addresses}); err != nil { + r.logger.Errorf(r.ctx, "UpdateState failed: %+v", err) + } +} + +// Close closes the resolver. +func (r *Resolver) Close() { + r.logger.Debugf(r.ctx, `resolver closed`) + if err := r.watcher.Close(); err != nil { + r.logger.Errorf(r.ctx, `%+v`, err) + } + r.cancel() +} + +// ResolveNow will be called by gRPC to try to resolve the target name +// again. It's just a hint, resolver can ignore this if it's not necessary. +// +// It could be called multiple times concurrently. +func (r *Resolver) ResolveNow(options resolver.ResolveNowOptions) { + +} + +func newAttributesFromMetadata(metadata map[string]interface{}) *attributes.Attributes { + var a *attributes.Attributes + for k, v := range metadata { + if a == nil { + a = attributes.New(k, v) + } else { + a = a.WithValue(k, v) + } + } + return a +} diff --git a/contrib/rpc/grpcx/internal/tracing/tracing.go b/contrib/rpc/grpcx/internal/tracing/tracing.go new file mode 100644 index 000000000..d8e22ea65 --- /dev/null +++ b/contrib/rpc/grpcx/internal/tracing/tracing.go @@ -0,0 +1,85 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package tracing provide tracing feature for GRPC. +// +// Refer to: opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go +package tracing + +import ( + "context" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/baggage" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc/metadata" +) + +const ( + // GRPCStatusCodeKey is convention for numeric status code of a gRPC request. + GRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code") +) + +const ( + tracingMaxContentLogSize = 256 * 1024 // Max log size for request and response body. + tracingInstrumentGrpcClient = "github.com/gogf/gf/contrib/rpc/grpcx/v2/krpc.GrpcClient" + tracingInstrumentGrpcServer = "github.com/gogf/gf/contrib/rpc/grpcx/v2/krpc.GrpcServer" + tracingEventGrpcRequest = "grpc.request" + tracingEventGrpcRequestMessage = "grpc.request.message" + tracingEventGrpcRequestBaggage = "grpc.request.baggage" + tracingEventGrpcMetadataOutgoing = "grpc.metadata.outgoing" + tracingEventGrpcMetadataIncoming = "grpc.metadata.incoming" + tracingEventGrpcResponse = "grpc.response" + tracingEventGrpcResponseMessage = "grpc.response.message" +) + +type metadataSupplier struct { + metadata metadata.MD +} + +func (s *metadataSupplier) Get(key string) string { + values := s.metadata.Get(key) + if len(values) == 0 { + return "" + } + return values[0] +} + +func (s *metadataSupplier) Set(key string, value string) { + s.metadata.Set(key, value) +} + +func (s *metadataSupplier) Keys() []string { + var ( + index = 0 + keys = make([]string, s.metadata.Len()) + ) + for k := range s.metadata { + keys[index] = k + index++ + } + return keys +} + +// Inject injects correlation context and span context into the gRPC +// metadata object. This function is meant to be used on outgoing +// requests. +func Inject(ctx context.Context, metadata metadata.MD) { + otel.GetTextMapPropagator().Inject(ctx, &metadataSupplier{ + metadata: metadata, + }) +} + +// Extract returns the correlation context and span context that +// another service encoded in the gRPC metadata object with Inject. +// This function is meant to be used on incoming requests. +func Extract(ctx context.Context, metadata metadata.MD) (baggage.Baggage, trace.SpanContext) { + ctx = otel.GetTextMapPropagator().Extract(ctx, &metadataSupplier{ + metadata: metadata, + }) + return baggage.FromContext(ctx), trace.SpanContextFromContext(ctx) +} diff --git a/contrib/rpc/grpcx/internal/tracing/tracing_interceptor.go b/contrib/rpc/grpcx/internal/tracing/tracing_interceptor.go new file mode 100644 index 000000000..d7e149d87 --- /dev/null +++ b/contrib/rpc/grpcx/internal/tracing/tracing_interceptor.go @@ -0,0 +1,287 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// https://github.com/open-telemetry/opentelemetry-go-contrib/blob/master/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go + +package tracing + +// gRPC tracing middleware +// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md +import ( + "context" + "errors" + "io" + "net" + "strings" + + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc" + grpcCodes "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + "google.golang.org/protobuf/proto" +) + +type messageType attribute.KeyValue + +// Event adds an event of the messageType to the span associated with the +// passed context with id and size (if message is a proto message). +func (m messageType) Event(ctx context.Context, id int, message interface{}) { + span := trace.SpanFromContext(ctx) + if p, ok := message.(proto.Message); ok { + span.AddEvent("message", trace.WithAttributes( + attribute.KeyValue(m), + attribute.Key("message.id").Int(id), + attribute.Key("message.uncompressed_size").Int(proto.Size(p)), + )) + } else { + span.AddEvent("message", trace.WithAttributes( + attribute.KeyValue(m), + attribute.Key("message.id").Int(id), + )) + } +} + +var ( + messageSent = messageType(attribute.Key("message.type").String("SENT")) + messageReceived = messageType(attribute.Key("message.type").String("RECEIVED")) +) + +type streamEventType int + +type streamEvent struct { + Type streamEventType + Err error +} + +const ( + closeEvent streamEventType = iota + receiveEndEvent + errorEvent +) + +// clientStream wraps around the embedded grpc.ClientStream, and intercepts the RecvMsg and +// SendMsg method call. +type clientStream struct { + grpc.ClientStream + + desc *grpc.StreamDesc + events chan streamEvent + eventsDone chan struct{} + finished chan error + + receivedMessageID int + sentMessageID int +} + +var _ = proto.Marshal + +func (w *clientStream) RecvMsg(m interface{}) error { + err := w.ClientStream.RecvMsg(m) + + if err == nil && !w.desc.ServerStreams { + w.sendStreamEvent(receiveEndEvent, nil) + } else if errors.Is(err, io.EOF) { + w.sendStreamEvent(receiveEndEvent, nil) + } else if err != nil { + w.sendStreamEvent(errorEvent, err) + } else { + w.receivedMessageID++ + messageReceived.Event(w.Context(), w.receivedMessageID, m) + } + + return err +} + +func (w *clientStream) SendMsg(m interface{}) error { + err := w.ClientStream.SendMsg(m) + + w.sentMessageID++ + messageSent.Event(w.Context(), w.sentMessageID, m) + + if err != nil { + w.sendStreamEvent(errorEvent, err) + } + + return err +} + +func (w *clientStream) Header() (metadata.MD, error) { + md, err := w.ClientStream.Header() + + if err != nil { + w.sendStreamEvent(errorEvent, err) + } + + return md, err +} + +func (w *clientStream) CloseSend() error { + err := w.ClientStream.CloseSend() + + if err != nil { + w.sendStreamEvent(errorEvent, err) + } else { + w.sendStreamEvent(closeEvent, nil) + } + + return err +} + +const ( + clientClosedState byte = 1 << iota + receiveEndedState +) + +func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc) *clientStream { + events := make(chan streamEvent) + eventsDone := make(chan struct{}) + finished := make(chan error) + + go func() { + defer close(eventsDone) + + // Both streams have to be closed + state := byte(0) + + for event := range events { + switch event.Type { + case closeEvent: + state |= clientClosedState + case receiveEndEvent: + state |= receiveEndedState + case errorEvent: + finished <- event.Err + return + } + + if state == clientClosedState|receiveEndedState { + finished <- nil + return + } + } + }() + + return &clientStream{ + ClientStream: s, + desc: desc, + events: events, + eventsDone: eventsDone, + finished: finished, + } +} + +func (w *clientStream) sendStreamEvent(eventType streamEventType, err error) { + select { + case <-w.eventsDone: + case w.events <- streamEvent{Type: eventType, Err: err}: + } +} + +// serverStream wraps around the embedded grpc.ServerStream, and intercepts the RecvMsg and +// SendMsg method call. +type serverStream struct { + grpc.ServerStream + ctx context.Context + + receivedMessageID int + sentMessageID int +} + +func (w *serverStream) Context() context.Context { + return w.ctx +} + +func (w *serverStream) RecvMsg(m interface{}) error { + err := w.ServerStream.RecvMsg(m) + + if err == nil { + w.receivedMessageID++ + messageReceived.Event(w.Context(), w.receivedMessageID, m) + } + + return err +} + +func (w *serverStream) SendMsg(m interface{}) error { + err := w.ServerStream.SendMsg(m) + + w.sentMessageID++ + messageSent.Event(w.Context(), w.sentMessageID, m) + + return err +} + +func wrapServerStream(ctx context.Context, ss grpc.ServerStream) *serverStream { + return &serverStream{ + ServerStream: ss, + ctx: ctx, + } +} + +// spanInfo returns a span name and all appropriate attributes from the gRPC +// method and peer address. +func spanInfo(fullMethod, peerAddress string) (string, []attribute.KeyValue) { + attrs := []attribute.KeyValue{attribute.Key("rpc.system").String("grpc")} + name, mAttrs := parseFullMethod(fullMethod) + attrs = append(attrs, mAttrs...) + attrs = append(attrs, peerAttr(peerAddress)...) + return name, attrs +} + +// peerAttr returns attributes about the peer address. +func peerAttr(addr string) []attribute.KeyValue { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return []attribute.KeyValue(nil) + } + + if host == "" { + host = "127.0.0.1" + } + + return []attribute.KeyValue{ + semconv.NetPeerIPKey.String(host), + semconv.NetPeerPortKey.String(port), + } +} + +// peerFromCtx returns a peer address from a context, if one exists. +func peerFromCtx(ctx context.Context) string { + p, ok := peer.FromContext(ctx) + if !ok { + return "" + } + return p.Addr.String() +} + +// parseFullMethod returns a span name following the OpenTelemetry semantic +// conventions as well as all applicable span attribute.KeyValue attributes based +// on a gRPC's FullMethod. +func parseFullMethod(fullMethod string) (string, []attribute.KeyValue) { + name := strings.TrimLeft(fullMethod, "/") + parts := strings.SplitN(name, "/", 2) + if len(parts) != 2 { + // Invalid format, does not follow `/package.service/method`. + return name, []attribute.KeyValue(nil) + } + + var attrs []attribute.KeyValue + if service := parts[0]; service != "" { + attrs = append(attrs, semconv.RPCServiceKey.String(service)) + } + if method := parts[1]; method != "" { + attrs = append(attrs, semconv.RPCMethodKey.String(method)) + } + return name, attrs +} + +// statusCodeAttr returns status code attribute based on given gRPC code. +func statusCodeAttr(c grpcCodes.Code) attribute.KeyValue { + return GRPCStatusCodeKey.Int64(int64(c)) +} diff --git a/contrib/rpc/grpcx/internal/tracing/tracing_interceptor_client.go b/contrib/rpc/grpcx/internal/tracing/tracing_interceptor_client.go new file mode 100644 index 000000000..2ccf475d8 --- /dev/null +++ b/contrib/rpc/grpcx/internal/tracing/tracing_interceptor_client.go @@ -0,0 +1,137 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package tracing + +import ( + "context" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc" + grpcCodes "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/grpcctx" + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/utils" + "github.com/gogf/gf/v2" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/util/gconv" +) + +// UnaryClientInterceptor returns a grpc.UnaryClientInterceptor suitable +// for use in a grpc.Dial call. +func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, callOpts ...grpc.CallOption) error { + tracer := otel.GetTracerProvider().Tracer( + tracingInstrumentGrpcClient, + trace.WithInstrumentationVersion(gf.VERSION), + ) + requestMetadata, _ := metadata.FromOutgoingContext(ctx) + metadataCopy := requestMetadata.Copy() + name, attr := spanInfo(method, cc.Target()) + var span trace.Span + ctx, span = tracer.Start( + ctx, + name, + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(attr...), + ) + defer span.End() + + Inject(ctx, metadataCopy) + + ctx = metadata.NewOutgoingContext(ctx, metadataCopy) + + // If it is now using default trace provider, it then does no complex tracing jobs. + if gtrace.IsUsingDefaultProvider() { + return invoker(ctx, method, req, reply, cc, callOpts...) + } + + span.SetAttributes(gtrace.CommonLabels()...) + + span.AddEvent(tracingEventGrpcRequest, trace.WithAttributes( + attribute.String(tracingEventGrpcRequestBaggage, gconv.String(gtrace.GetBaggageMap(ctx))), + attribute.String(tracingEventGrpcMetadataOutgoing, gconv.String(grpcctx.Ctx{}.OutgoingMap(ctx))), + attribute.String( + tracingEventGrpcRequestMessage, + utils.MarshalMessageToJsonStringForTracing( + req, "Request", tracingMaxContentLogSize, + ), + ), + )) + + err := invoker(ctx, method, req, reply, cc, callOpts...) + + span.AddEvent(tracingEventGrpcResponse, trace.WithAttributes( + attribute.String( + tracingEventGrpcResponseMessage, + utils.MarshalMessageToJsonStringForTracing( + reply, "Response", tracingMaxContentLogSize, + ), + ), + )) + + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(codes.Error, s.Message()) + span.SetAttributes(statusCodeAttr(s.Code())) + } else { + span.SetAttributes(statusCodeAttr(grpcCodes.OK)) + } + return err +} + +// StreamClientInterceptor returns a grpc.StreamClientInterceptor suitable +// for use in a grpc.Dial call. +func StreamClientInterceptor( + ctx context.Context, desc *grpc.StreamDesc, + cc *grpc.ClientConn, method string, streamer grpc.Streamer, + callOpts ...grpc.CallOption) (grpc.ClientStream, error) { + tracer := otel.GetTracerProvider().Tracer( + tracingInstrumentGrpcClient, + trace.WithInstrumentationVersion(gf.VERSION), + ) + requestMetadata, _ := metadata.FromOutgoingContext(ctx) + metadataCopy := requestMetadata.Copy() + name, attr := spanInfo(method, cc.Target()) + + var span trace.Span + ctx, span = tracer.Start( + ctx, + name, + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(attr...), + ) + + Inject(ctx, metadataCopy) + ctx = metadata.NewOutgoingContext(ctx, metadataCopy) + + span.SetAttributes(gtrace.CommonLabels()...) + + s, err := streamer(ctx, desc, cc, method, callOpts...) + stream := wrapClientStream(s, desc) + go func() { + if err == nil { + err = <-stream.finished + } + + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(codes.Error, s.Message()) + span.SetAttributes(statusCodeAttr(s.Code())) + } else { + span.SetAttributes(statusCodeAttr(grpcCodes.OK)) + } + + span.End() + }() + + return stream, err +} diff --git a/contrib/rpc/grpcx/internal/tracing/tracing_interceptor_server.go b/contrib/rpc/grpcx/internal/tracing/tracing_interceptor_server.go new file mode 100644 index 000000000..3ec503976 --- /dev/null +++ b/contrib/rpc/grpcx/internal/tracing/tracing_interceptor_server.go @@ -0,0 +1,127 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package tracing + +import ( + "context" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/baggage" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc" + grpcCodes "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/grpcctx" + "github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/utils" + "github.com/gogf/gf/v2" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/util/gconv" +) + +// UnaryServerInterceptor returns a grpc.UnaryServerInterceptor suitable +// for use in a grpc.NewServer call. +func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + tracer := otel.GetTracerProvider().Tracer( + tracingInstrumentGrpcServer, + trace.WithInstrumentationVersion(gf.VERSION), + ) + requestMetadata, _ := metadata.FromIncomingContext(ctx) + metadataCopy := requestMetadata.Copy() + + entries, spanCtx := Extract(ctx, metadataCopy) + ctx = baggage.ContextWithBaggage(ctx, entries) + ctx = trace.ContextWithRemoteSpanContext(ctx, spanCtx) + name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx)) + ctx, span := tracer.Start( + ctx, + name, + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes(attr...), + ) + defer span.End() + + // If it is now using default trace provider, it then does no complex tracing jobs. + if gtrace.IsUsingDefaultProvider() { + return handler(ctx, req) + } + + span.SetAttributes(gtrace.CommonLabels()...) + + span.AddEvent(tracingEventGrpcRequest, trace.WithAttributes( + attribute.String(tracingEventGrpcRequestBaggage, gconv.String(gtrace.GetBaggageMap(ctx))), + attribute.String(tracingEventGrpcMetadataIncoming, gconv.String(grpcctx.Ctx{}.IncomingMap(ctx))), + attribute.String( + tracingEventGrpcRequestMessage, + utils.MarshalMessageToJsonStringForTracing( + req, "Request", tracingMaxContentLogSize, + ), + ), + )) + + res, err := handler(ctx, req) + + span.AddEvent(tracingEventGrpcResponse, trace.WithAttributes( + attribute.String( + tracingEventGrpcResponseMessage, + utils.MarshalMessageToJsonStringForTracing( + res, "Response", tracingMaxContentLogSize, + ), + ), + )) + + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(codes.Error, s.Message()) + span.SetAttributes(statusCodeAttr(s.Code())) + } else { + span.SetAttributes(statusCodeAttr(grpcCodes.OK)) + } + + return res, err +} + +// StreamServerInterceptor returns a grpc.StreamServerInterceptor suitable +// for use in a grpc.NewServer call. +func StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + tracer := otel.GetTracerProvider().Tracer( + tracingInstrumentGrpcServer, + trace.WithInstrumentationVersion(gf.VERSION), + ) + + ctx := ss.Context() + requestMetadata, _ := metadata.FromIncomingContext(ctx) + metadataCopy := requestMetadata.Copy() + entries, spanCtx := Extract(ctx, metadataCopy) + ctx = baggage.ContextWithBaggage(ctx, entries) + ctx = trace.ContextWithRemoteSpanContext(ctx, spanCtx) + name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx)) + ctx, span := tracer.Start( + ctx, + name, + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes(attr...), + ) + defer span.End() + + span.SetAttributes(gtrace.CommonLabels()...) + + err := handler(srv, wrapServerStream(ctx, ss)) + + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(codes.Error, s.Message()) + span.SetAttributes(statusCodeAttr(s.Code())) + } else { + span.SetAttributes(statusCodeAttr(grpcCodes.OK)) + } + + return err +} diff --git a/contrib/rpc/grpcx/internal/utils/utils.go b/contrib/rpc/grpcx/internal/utils/utils.go new file mode 100644 index 000000000..aa42aea22 --- /dev/null +++ b/contrib/rpc/grpcx/internal/utils/utils.go @@ -0,0 +1,44 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package utils provides utilities for GRPC. +package utils + +import ( + "fmt" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +var ( + protoJSONMarshaller = &protojson.MarshalOptions{ + EmitUnpopulated: true, + } +) + +// MarshalPbMessageToJsonString marshals protobuf message to json string. +func MarshalPbMessageToJsonString(msg proto.Message) string { + return protoJSONMarshaller.Format(msg) +} + +func MarshalMessageToJsonStringForTracing(value interface{}, msgType string, maxBytes int) string { + var messageContent string + if msg, ok := value.(proto.Message); ok { + if proto.Size(msg) <= maxBytes { + messageContent = MarshalPbMessageToJsonString(msg) + } else { + messageContent = fmt.Sprintf( + "[%s Message Too Large For Tracing, Max: %d bytes]", + msgType, + maxBytes, + ) + } + } else { + messageContent = fmt.Sprintf("%v", value) + } + return messageContent +} diff --git a/example/go.mod b/example/go.mod index feff794d3..502d82265 100644 --- a/example/go.mod +++ b/example/go.mod @@ -7,30 +7,33 @@ require ( github.com/gogf/gf/contrib/config/kubecm/v2 v2.0.0 github.com/gogf/gf/contrib/config/nacos/v2 v2.2.6 github.com/gogf/gf/contrib/config/polaris/v2 v2.0.0 - github.com/gogf/gf/contrib/drivers/mysql/v2 v2.2.1 + github.com/gogf/gf/contrib/drivers/mysql/v2 v2.0.0 github.com/gogf/gf/contrib/nosql/redis/v2 v2.2.6 github.com/gogf/gf/contrib/registry/etcd/v2 v2.2.6 + github.com/gogf/gf/contrib/registry/file/v2 v2.2.6 github.com/gogf/gf/contrib/registry/polaris/v2 v2.0.0 + github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.0.0 github.com/gogf/gf/contrib/trace/jaeger/v2 v2.0.0 github.com/gogf/gf/v2 v2.2.1 - github.com/gogf/katyusha v0.4.1-0.20220620125113-f55d6f739773 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.2 github.com/nacos-group/nacos-sdk-go v1.1.2 github.com/polarismesh/polaris-go v1.2.0-beta.3 - google.golang.org/grpc v1.46.2 + google.golang.org/grpc v1.49.0 + google.golang.org/protobuf v1.28.1 k8s.io/client-go v0.25.2 ) replace ( github.com/gogf/gf/contrib/config/apollo/v2 => ../contrib/config/apollo/ github.com/gogf/gf/contrib/config/kubecm/v2 => ../contrib/config/kubecm/ - github.com/gogf/gf/contrib/config/nacos/v2 => ../contrib/config/nacos/ github.com/gogf/gf/contrib/config/polaris/v2 => ../contrib/config/polaris/ github.com/gogf/gf/contrib/drivers/mysql/v2 => ../contrib/drivers/mysql/ github.com/gogf/gf/contrib/nosql/redis/v2 => ../contrib/nosql/redis/ github.com/gogf/gf/contrib/registry/etcd/v2 => ../contrib/registry/etcd/ + github.com/gogf/gf/contrib/registry/file/v2 => ../contrib/registry/file/ github.com/gogf/gf/contrib/registry/polaris/v2 => ../contrib/registry/polaris/ + github.com/gogf/gf/contrib/rpc/grpcx/v2 => ../contrib/rpc/grpcx/ github.com/gogf/gf/contrib/trace/jaeger/v2 => ../contrib/trace/jaeger/ github.com/gogf/gf/v2 => ../ ) diff --git a/example/go.sum b/example/go.sum index 08390ca6d..c7b5e316e 100644 --- a/example/go.sum +++ b/example/go.sum @@ -188,8 +188,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogf/katyusha v0.4.1-0.20220620125113-f55d6f739773 h1:YQBLawktoymYtPGs9idE9JS5Wqd3SjIzUEZOPKCdSw0= -github.com/gogf/katyusha v0.4.1-0.20220620125113-f55d6f739773/go.mod h1:Z0GCeHXz1UI0HtA0K45c6TzEGM4DL/PLatS747/WarI= +github.com/gogf/gf/contrib/config/nacos/v2 v2.2.6 h1:QQLN9bB9vFPFvROWM2uSchsHDaMr6ciNjrWY9xuZlIk= +github.com/gogf/gf/contrib/config/nacos/v2 v2.2.6/go.mod h1:phuuQsfTgyMdEENfotnk22ZrEu48pO7rjho158bZsD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -544,14 +544,16 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= +go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= go.opentelemetry.io/otel/exporters/jaeger v1.7.0 h1:wXgjiRldljksZkZrldGVe6XrG9u3kYDyQmkZwmm5dI0= go.opentelemetry.io/otel/exporters/jaeger v1.7.0/go.mod h1:PwQAOqBgqbLQRKlj466DuD2qyMjbtcPpfPfj+AqbSBs= go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0= go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= -go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= +go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= +go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -668,9 +670,9 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1 h1:TWZxd/th7FbRSMret2MVQdlI8uT49QEtwZdvJrxjEHU= +golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -776,8 +778,9 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -985,8 +988,9 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1001,8 +1005,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/example/registry/file/client/main.go b/example/registry/file/client/main.go new file mode 100644 index 000000000..8929a5554 --- /dev/null +++ b/example/registry/file/client/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "time" + + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) + + client := g.Client() + for i := 0; i < 100; i++ { + res, err := client.Get(gctx.New(), `http://hello.svc/`) + if err != nil { + panic(err) + } + fmt.Println(res.ReadAllString()) + res.Close() + time.Sleep(time.Second) + } +} diff --git a/example/registry/file/server/main.go b/example/registry/file/server/main.go new file mode 100644 index 000000000..480d57cf7 --- /dev/null +++ b/example/registry/file/server/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) + + s := g.Server(`hello.svc`) + s.BindHandler("/", func(r *ghttp.Request) { + g.Log().Info(r.Context(), `request received`) + r.Response.Write(`Hello world`) + }) + s.Run() +} diff --git a/example/rpc/grpcx/basic/client/config.yaml b/example/rpc/grpcx/basic/client/config.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/example/rpc/grpcx/basic/client/main.go b/example/rpc/grpcx/basic/client/main.go new file mode 100644 index 000000000..f747162af --- /dev/null +++ b/example/rpc/grpcx/basic/client/main.go @@ -0,0 +1,32 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package main + +import ( + "time" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/protocol" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.GetInitCtx() + client = protocol.NewEchoClient(grpcx.Client.MustNewGrpcClientConn("demo")) + ) + for i := 0; i < 100; i++ { + res, err := client.Say(ctx, &protocol.SayReq{Content: "Hello"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Print(ctx, "Response:", res.Content) + time.Sleep(time.Second) + } +} diff --git a/example/rpc/grpcx/basic/protobuf/echo.proto b/example/rpc/grpcx/basic/protobuf/echo.proto new file mode 100644 index 000000000..d13b3ed41 --- /dev/null +++ b/example/rpc/grpcx/basic/protobuf/echo.proto @@ -0,0 +1,21 @@ +// protoc --go_out=plugins=grpc:. *.proto + +syntax = "proto3"; + +package proto; + +option go_package = "/proto"; + +service Echo{ + rpc Say(SayReq) returns (SayRes) {} +} + +message SayReq { + string content = 1; +} + +message SayRes { + string content = 1; +} + + diff --git a/example/rpc/grpcx/basic/protocol/echo.pb.go b/example/rpc/grpcx/basic/protocol/echo.pb.go new file mode 100644 index 000000000..c6753d6f2 --- /dev/null +++ b/example/rpc/grpcx/basic/protocol/echo.pb.go @@ -0,0 +1,305 @@ +// protoc --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.23.0 +// protoc v3.11.4 +// source: echo.proto + +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package protocol + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type SayReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` +} + +func (x *SayReq) Reset() { + *x = SayReq{} + if protoimpl.UnsafeEnabled { + mi := &file_echo_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SayReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SayReq) ProtoMessage() {} + +func (x *SayReq) ProtoReflect() protoreflect.Message { + mi := &file_echo_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SayReq.ProtoReflect.Descriptor instead. +func (*SayReq) Descriptor() ([]byte, []int) { + return file_echo_proto_rawDescGZIP(), []int{0} +} + +func (x *SayReq) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +type SayRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` +} + +func (x *SayRes) Reset() { + *x = SayRes{} + if protoimpl.UnsafeEnabled { + mi := &file_echo_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SayRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SayRes) ProtoMessage() {} + +func (x *SayRes) ProtoReflect() protoreflect.Message { + mi := &file_echo_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SayRes.ProtoReflect.Descriptor instead. +func (*SayRes) Descriptor() ([]byte, []int) { + return file_echo_proto_rawDescGZIP(), []int{1} +} + +func (x *SayRes) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +var File_echo_proto protoreflect.FileDescriptor + +var file_echo_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x65, 0x63, 0x68, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a, 0x06, 0x53, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x22, 0x0a, 0x06, 0x53, 0x61, 0x79, 0x52, 0x65, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x32, 0x2d, 0x0a, 0x04, 0x45, + 0x63, 0x68, 0x6f, 0x12, 0x25, 0x0a, 0x03, 0x53, 0x61, 0x79, 0x12, 0x0d, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x61, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x53, 0x61, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_echo_proto_rawDescOnce sync.Once + file_echo_proto_rawDescData = file_echo_proto_rawDesc +) + +func file_echo_proto_rawDescGZIP() []byte { + file_echo_proto_rawDescOnce.Do(func() { + file_echo_proto_rawDescData = protoimpl.X.CompressGZIP(file_echo_proto_rawDescData) + }) + return file_echo_proto_rawDescData +} + +var file_echo_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_echo_proto_goTypes = []interface{}{ + (*SayReq)(nil), // 0: proto.SayReq + (*SayRes)(nil), // 1: proto.SayRes +} +var file_echo_proto_depIdxs = []int32{ + 0, // 0: proto.Echo.Say:input_type -> proto.SayReq + 1, // 1: proto.Echo.Say:output_type -> proto.SayRes + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_echo_proto_init() } +func file_echo_proto_init() { + if File_echo_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_echo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SayReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_echo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SayRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_echo_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_echo_proto_goTypes, + DependencyIndexes: file_echo_proto_depIdxs, + MessageInfos: file_echo_proto_msgTypes, + }.Build() + File_echo_proto = out.File + file_echo_proto_rawDesc = nil + file_echo_proto_goTypes = nil + file_echo_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// EchoClient is the client API for Echo service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type EchoClient interface { + Say(ctx context.Context, in *SayReq, opts ...grpc.CallOption) (*SayRes, error) +} + +type echoClient struct { + cc grpc.ClientConnInterface +} + +func NewEchoClient(cc grpc.ClientConnInterface) EchoClient { + return &echoClient{cc} +} + +func (c *echoClient) Say(ctx context.Context, in *SayReq, opts ...grpc.CallOption) (*SayRes, error) { + out := new(SayRes) + err := c.cc.Invoke(ctx, "/proto.Echo/Say", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EchoServer is the server API for Echo service. +type EchoServer interface { + Say(context.Context, *SayReq) (*SayRes, error) +} + +// UnimplementedEchoServer can be embedded to have forward compatible implementations. +type UnimplementedEchoServer struct { +} + +func (*UnimplementedEchoServer) Say(context.Context, *SayReq) (*SayRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method Say not implemented") +} + +func RegisterEchoServer(s *grpc.Server, srv EchoServer) { + s.RegisterService(&_Echo_serviceDesc, srv) +} + +func _Echo_Say_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SayReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EchoServer).Say(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Echo/Say", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EchoServer).Say(ctx, req.(*SayReq)) + } + return interceptor(ctx, in, info, handler) +} + +var _Echo_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Echo", + HandlerType: (*EchoServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Say", + Handler: _Echo_Say_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "echo.proto", +} diff --git a/example/rpc/grpcx/basic/server/config.yaml b/example/rpc/grpcx/basic/server/config.yaml new file mode 100644 index 000000000..071b62623 --- /dev/null +++ b/example/rpc/grpcx/basic/server/config.yaml @@ -0,0 +1,8 @@ +grpc: + name: "demo" + logPath: "./log" + logStdout: true + errorLogEnabled: true + accessLogEnabled: true + errorStack: true + diff --git a/example/rpc/grpcx/basic/server/main.go b/example/rpc/grpcx/basic/server/main.go new file mode 100644 index 000000000..f420fd070 --- /dev/null +++ b/example/rpc/grpcx/basic/server/main.go @@ -0,0 +1,19 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/protocol" + "github.com/gogf/gf/example/rpc/grpcx/basic/service" +) + +func main() { + s := grpcx.Server.New() + protocol.RegisterEchoServer(s.Server, new(service.Echo)) + s.Run() +} diff --git a/example/rpc/grpcx/basic/service/service_echo.go b/example/rpc/grpcx/basic/service/service_echo.go new file mode 100644 index 000000000..d66f5ab23 --- /dev/null +++ b/example/rpc/grpcx/basic/service/service_echo.go @@ -0,0 +1,27 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package service + +import ( + "fmt" + + "context" + + "github.com/gogf/gf/example/rpc/grpcx/basic/protocol" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" +) + +// Echo is the service for echo. +type Echo struct{} + +// Say implements the protobuf.EchoServer interface. +func (s *Echo) Say(ctx context.Context, r *protocol.SayReq) (*protocol.SayRes, error) { + g.Log().Print(ctx, "Received:", r.Content) + text := fmt.Sprintf(`%s: > %s`, gcmd.GetOpt("node", "default"), r.Content) + return &protocol.SayRes{Content: text}, nil +} diff --git a/example/rpc/grpcx/basic_with_tag/client/config.yaml b/example/rpc/grpcx/basic_with_tag/client/config.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/example/rpc/grpcx/basic_with_tag/client/main.go b/example/rpc/grpcx/basic_with_tag/client/main.go new file mode 100644 index 000000000..c4079e31c --- /dev/null +++ b/example/rpc/grpcx/basic_with_tag/client/main.go @@ -0,0 +1,34 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package main + +import ( + "time" + + "github.com/gogf/gf/example/rpc/grpcx/basic_with_tag/protocol" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.GetInitCtx() + client, err = protocol.NewClient() + ) + if err != nil { + g.Log().Fatalf(ctx, `%+v`, err) + } + for i := 0; i < 100; i++ { + res, err := client.Echo().Say(ctx, &protocol.SayReq{Content: "Hello"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Print(ctx, "Response:", res.Content) + time.Sleep(time.Second) + } +} diff --git a/example/rpc/grpcx/basic_with_tag/protobuf/echo.proto b/example/rpc/grpcx/basic_with_tag/protobuf/echo.proto new file mode 100644 index 000000000..6a3d3e1cf --- /dev/null +++ b/example/rpc/grpcx/basic_with_tag/protobuf/echo.proto @@ -0,0 +1,19 @@ +// protoc --go_out=plugins=grpc:. *.proto + +syntax = "proto3"; + +package proto; + +service Echo{ + rpc Say(SayReq) returns (SayRes) {} +} + +message SayReq { + string content = 1; +} + +message SayRes { + string content = 1; +} + + diff --git a/example/rpc/grpcx/basic_with_tag/protocol/client.go b/example/rpc/grpcx/basic_with_tag/protocol/client.go new file mode 100644 index 000000000..1177da05e --- /dev/null +++ b/example/rpc/grpcx/basic_with_tag/protocol/client.go @@ -0,0 +1,31 @@ +package protocol + +import ( + "google.golang.org/grpc" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" +) + +const ( + // AppID is the application ID for the protobuf service. + AppID = "demo" +) + +// Client is the client for protobuf. +type Client struct { + conn *grpc.ClientConn +} + +// NewClient creates and returns a new client. +func NewClient(options ...grpc.DialOption) (*Client, error) { + conn, err := grpcx.Client.NewGrpcClientConn(AppID, options...) + if err != nil { + return nil, err + } + return &Client{conn: conn}, nil +} + +// Echo is the client for protobuf.Echo. +func (c *Client) Echo() EchoClient { + return NewEchoClient(c.conn) +} diff --git a/example/rpc/grpcx/basic_with_tag/protocol/echo.pb.go b/example/rpc/grpcx/basic_with_tag/protocol/echo.pb.go new file mode 100644 index 000000000..c6753d6f2 --- /dev/null +++ b/example/rpc/grpcx/basic_with_tag/protocol/echo.pb.go @@ -0,0 +1,305 @@ +// protoc --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.23.0 +// protoc v3.11.4 +// source: echo.proto + +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package protocol + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type SayReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` +} + +func (x *SayReq) Reset() { + *x = SayReq{} + if protoimpl.UnsafeEnabled { + mi := &file_echo_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SayReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SayReq) ProtoMessage() {} + +func (x *SayReq) ProtoReflect() protoreflect.Message { + mi := &file_echo_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SayReq.ProtoReflect.Descriptor instead. +func (*SayReq) Descriptor() ([]byte, []int) { + return file_echo_proto_rawDescGZIP(), []int{0} +} + +func (x *SayReq) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +type SayRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` +} + +func (x *SayRes) Reset() { + *x = SayRes{} + if protoimpl.UnsafeEnabled { + mi := &file_echo_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SayRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SayRes) ProtoMessage() {} + +func (x *SayRes) ProtoReflect() protoreflect.Message { + mi := &file_echo_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SayRes.ProtoReflect.Descriptor instead. +func (*SayRes) Descriptor() ([]byte, []int) { + return file_echo_proto_rawDescGZIP(), []int{1} +} + +func (x *SayRes) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +var File_echo_proto protoreflect.FileDescriptor + +var file_echo_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x65, 0x63, 0x68, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a, 0x06, 0x53, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x22, 0x0a, 0x06, 0x53, 0x61, 0x79, 0x52, 0x65, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x32, 0x2d, 0x0a, 0x04, 0x45, + 0x63, 0x68, 0x6f, 0x12, 0x25, 0x0a, 0x03, 0x53, 0x61, 0x79, 0x12, 0x0d, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x61, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x53, 0x61, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_echo_proto_rawDescOnce sync.Once + file_echo_proto_rawDescData = file_echo_proto_rawDesc +) + +func file_echo_proto_rawDescGZIP() []byte { + file_echo_proto_rawDescOnce.Do(func() { + file_echo_proto_rawDescData = protoimpl.X.CompressGZIP(file_echo_proto_rawDescData) + }) + return file_echo_proto_rawDescData +} + +var file_echo_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_echo_proto_goTypes = []interface{}{ + (*SayReq)(nil), // 0: proto.SayReq + (*SayRes)(nil), // 1: proto.SayRes +} +var file_echo_proto_depIdxs = []int32{ + 0, // 0: proto.Echo.Say:input_type -> proto.SayReq + 1, // 1: proto.Echo.Say:output_type -> proto.SayRes + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_echo_proto_init() } +func file_echo_proto_init() { + if File_echo_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_echo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SayReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_echo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SayRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_echo_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_echo_proto_goTypes, + DependencyIndexes: file_echo_proto_depIdxs, + MessageInfos: file_echo_proto_msgTypes, + }.Build() + File_echo_proto = out.File + file_echo_proto_rawDesc = nil + file_echo_proto_goTypes = nil + file_echo_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// EchoClient is the client API for Echo service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type EchoClient interface { + Say(ctx context.Context, in *SayReq, opts ...grpc.CallOption) (*SayRes, error) +} + +type echoClient struct { + cc grpc.ClientConnInterface +} + +func NewEchoClient(cc grpc.ClientConnInterface) EchoClient { + return &echoClient{cc} +} + +func (c *echoClient) Say(ctx context.Context, in *SayReq, opts ...grpc.CallOption) (*SayRes, error) { + out := new(SayRes) + err := c.cc.Invoke(ctx, "/proto.Echo/Say", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EchoServer is the server API for Echo service. +type EchoServer interface { + Say(context.Context, *SayReq) (*SayRes, error) +} + +// UnimplementedEchoServer can be embedded to have forward compatible implementations. +type UnimplementedEchoServer struct { +} + +func (*UnimplementedEchoServer) Say(context.Context, *SayReq) (*SayRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method Say not implemented") +} + +func RegisterEchoServer(s *grpc.Server, srv EchoServer) { + s.RegisterService(&_Echo_serviceDesc, srv) +} + +func _Echo_Say_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SayReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EchoServer).Say(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Echo/Say", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EchoServer).Say(ctx, req.(*SayReq)) + } + return interceptor(ctx, in, info, handler) +} + +var _Echo_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Echo", + HandlerType: (*EchoServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Say", + Handler: _Echo_Say_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "echo.proto", +} diff --git a/example/rpc/grpcx/basic_with_tag/server/config.yaml b/example/rpc/grpcx/basic_with_tag/server/config.yaml new file mode 100644 index 000000000..071b62623 --- /dev/null +++ b/example/rpc/grpcx/basic_with_tag/server/config.yaml @@ -0,0 +1,8 @@ +grpc: + name: "demo" + logPath: "./log" + logStdout: true + errorLogEnabled: true + accessLogEnabled: true + errorStack: true + diff --git a/example/rpc/grpcx/basic_with_tag/server/main.go b/example/rpc/grpcx/basic_with_tag/server/main.go new file mode 100644 index 000000000..6f91533a8 --- /dev/null +++ b/example/rpc/grpcx/basic_with_tag/server/main.go @@ -0,0 +1,19 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic_with_tag/protocol" + "github.com/gogf/gf/example/rpc/grpcx/basic_with_tag/service" +) + +func main() { + s := grpcx.Server.New() + protocol.RegisterEchoServer(s.Server, new(service.Echo)) + s.Run() +} diff --git a/example/rpc/grpcx/basic_with_tag/service/service_echo.go b/example/rpc/grpcx/basic_with_tag/service/service_echo.go new file mode 100644 index 000000000..c18d8b8d1 --- /dev/null +++ b/example/rpc/grpcx/basic_with_tag/service/service_echo.go @@ -0,0 +1,27 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package service + +import ( + "fmt" + + "context" + + "github.com/gogf/gf/example/rpc/grpcx/basic_with_tag/protocol" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" +) + +// Echo is the service for echo. +type Echo struct{} + +// Say implements the protobuf.EchoServer interface. +func (s *Echo) Say(ctx context.Context, r *protocol.SayReq) (*protocol.SayRes, error) { + g.Log().Print(ctx, "Received:", r.Content) + text := fmt.Sprintf(`%s: > %s`, gcmd.GetOpt("node", "default"), r.Content) + return &protocol.SayRes{Content: text}, nil +} diff --git a/example/rpc/grpcx/rawgrpc/greeter_client/main.go b/example/rpc/grpcx/rawgrpc/greeter_client/main.go new file mode 100644 index 000000000..8d54a160e --- /dev/null +++ b/example/rpc/grpcx/rawgrpc/greeter_client/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + + "github.com/gogf/gf/contrib/registry/etcd/v2" + pb "github.com/gogf/gf/example/rpc/grpcx/rawgrpc/helloworld" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + gsvc.SetRegistry(etcd.New("127.0.0.1:2379")) + + var ( + ctx = gctx.GetInitCtx() + service = gsvc.NewServiceWithName(`hello`) + ) + // Set up a connection to the server. + conn, err := grpc.Dial( + fmt.Sprintf(`%s`, service.GetKey()), + grpcx.Balancer.WithRandom(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + g.Log().Fatalf(ctx, "did not connect: %v", err) + } + defer conn.Close() + + // Send requests. + client := pb.NewGreeterClient(conn) + for i := 0; i < 10; i++ { + res, err := client.SayHello(ctx, &pb.HelloRequest{Name: `GoFrame`}) + if err != nil { + g.Log().Fatalf(ctx, "could not greet: %+v", err) + } + g.Log().Printf(ctx, "Greeting: %s", res.Message) + time.Sleep(time.Second) + } +} diff --git a/example/rpc/grpcx/rawgrpc/greeter_server/main.go b/example/rpc/grpcx/rawgrpc/greeter_server/main.go new file mode 100644 index 000000000..6a7511267 --- /dev/null +++ b/example/rpc/grpcx/rawgrpc/greeter_server/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "fmt" + "net" + + "google.golang.org/grpc" + + "github.com/gogf/gf/contrib/registry/etcd/v2" + pb "github.com/gogf/gf/example/rpc/grpcx/rawgrpc/helloworld" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gipv4" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/gctx" +) + +type GreetingServer struct { + pb.UnimplementedGreeterServer +} + +// SayHello implements helloworld.GreeterServer +func (s *GreetingServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { + g.Log().Printf(ctx, "Received: %v", in.GetName()) + return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil +} + +func main() { + gsvc.SetRegistry(etcd.New("127.0.0.1:2379")) + + var ( + err error + ctx = gctx.GetInitCtx() + address = fmt.Sprintf("%s:%d", gipv4.MustGetIntranetIp(), gtcp.MustGetFreePort()) + service = &gsvc.LocalService{ + Name: "hello", + Endpoints: gsvc.NewEndpoints(address), + } + ) + + // Service registry. + _, err = gsvc.Register(ctx, service) + if err != nil { + panic(err) + } + defer func() { + _ = gsvc.Deregister(ctx, service) + }() + + // Server listening. + listen, err := net.Listen("tcp", address) + if err != nil { + g.Log().Fatalf(ctx, "failed to listen: %v", err) + } + + s := grpc.NewServer() + pb.RegisterGreeterServer(s, &GreetingServer{}) + g.Log().Printf(ctx, "server listening at %v", listen.Addr()) + if err = s.Serve(listen); err != nil { + g.Log().Fatalf(ctx, "failed to serve: %v", err) + } +} diff --git a/example/rpc/grpcx/rawgrpc/helloworld/helloworld.pb.go b/example/rpc/grpcx/rawgrpc/helloworld/helloworld.pb.go new file mode 100644 index 000000000..1d7cc808b --- /dev/null +++ b/example/rpc/grpcx/rawgrpc/helloworld/helloworld.pb.go @@ -0,0 +1,234 @@ +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: helloworld/helloworld.proto + +package helloworld + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The request message containing the user's name. +type HelloRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *HelloRequest) Reset() { + *x = HelloRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_helloworld_helloworld_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HelloRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloRequest) ProtoMessage() {} + +func (x *HelloRequest) ProtoReflect() protoreflect.Message { + mi := &file_helloworld_helloworld_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. +func (*HelloRequest) Descriptor() ([]byte, []int) { + return file_helloworld_helloworld_proto_rawDescGZIP(), []int{0} +} + +func (x *HelloRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// The response message containing the greetings +type HelloReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *HelloReply) Reset() { + *x = HelloReply{} + if protoimpl.UnsafeEnabled { + mi := &file_helloworld_helloworld_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HelloReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloReply) ProtoMessage() {} + +func (x *HelloReply) ProtoReflect() protoreflect.Message { + mi := &file_helloworld_helloworld_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. +func (*HelloReply) Descriptor() ([]byte, []int) { + return file_helloworld_helloworld_proto_rawDescGZIP(), []int{1} +} + +func (x *HelloReply) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_helloworld_helloworld_proto protoreflect.FileDescriptor + +var file_helloworld_helloworld_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x68, 0x65, 0x6c, + 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, + 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, + 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x49, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, + 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, + 0x42, 0x67, 0x0a, 0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x42, + 0x0f, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x50, 0x01, 0x5a, 0x35, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, + 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_helloworld_helloworld_proto_rawDescOnce sync.Once + file_helloworld_helloworld_proto_rawDescData = file_helloworld_helloworld_proto_rawDesc +) + +func file_helloworld_helloworld_proto_rawDescGZIP() []byte { + file_helloworld_helloworld_proto_rawDescOnce.Do(func() { + file_helloworld_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_helloworld_proto_rawDescData) + }) + return file_helloworld_helloworld_proto_rawDescData +} + +var file_helloworld_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_helloworld_helloworld_proto_goTypes = []interface{}{ + (*HelloRequest)(nil), // 0: helloworld.HelloRequest + (*HelloReply)(nil), // 1: helloworld.HelloReply +} +var file_helloworld_helloworld_proto_depIdxs = []int32{ + 0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest + 1, // 1: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_helloworld_helloworld_proto_init() } +func file_helloworld_helloworld_proto_init() { + if File_helloworld_helloworld_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_helloworld_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HelloRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_helloworld_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HelloReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_helloworld_helloworld_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_helloworld_helloworld_proto_goTypes, + DependencyIndexes: file_helloworld_helloworld_proto_depIdxs, + MessageInfos: file_helloworld_helloworld_proto_msgTypes, + }.Build() + File_helloworld_helloworld_proto = out.File + file_helloworld_helloworld_proto_rawDesc = nil + file_helloworld_helloworld_proto_goTypes = nil + file_helloworld_helloworld_proto_depIdxs = nil +} diff --git a/example/rpc/grpcx/rawgrpc/helloworld/helloworld.proto b/example/rpc/grpcx/rawgrpc/helloworld/helloworld.proto new file mode 100644 index 000000000..692ef9ded --- /dev/null +++ b/example/rpc/grpcx/rawgrpc/helloworld/helloworld.proto @@ -0,0 +1,38 @@ +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option go_package = "google.golang.org/grpc/examples/helloworld/helloworld"; +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/example/rpc/grpcx/rawgrpc/helloworld/helloworld_grpc.pb.go b/example/rpc/grpcx/rawgrpc/helloworld/helloworld_grpc.pb.go new file mode 100644 index 000000000..cebcffbce --- /dev/null +++ b/example/rpc/grpcx/rawgrpc/helloworld/helloworld_grpc.pb.go @@ -0,0 +1,107 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.12 +// source: helloworld/helloworld.proto + +package helloworld + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GreeterClient is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GreeterClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) +} + +type greeterClient struct { + cc grpc.ClientConnInterface +} + +func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + out := new(HelloReply) + err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GreeterServer is the server API for Greeter service. +// All implementations must embed UnimplementedGreeterServer +// for forward compatibility +type GreeterServer interface { + // Sends a greeting + SayHello(context.Context, *HelloRequest) (*HelloReply, error) + mustEmbedUnimplementedGreeterServer() +} + +// UnimplementedGreeterServer must be embedded to have forward compatible implementations. +type UnimplementedGreeterServer struct { +} + +func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") +} +func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} + +// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GreeterServer will +// result in compilation errors. +type UnsafeGreeterServer interface { + mustEmbedUnimplementedGreeterServer() +} + +func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { + s.RegisterService(&Greeter_ServiceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/helloworld.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Greeter_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "helloworld.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "helloworld/helloworld.proto", +} diff --git a/example/trace/grpc_with_db/protobuf/user/client.go b/example/trace/grpc_with_db/protobuf/user/client.go index 1644dffc3..57db950f5 100644 --- a/example/trace/grpc_with_db/protobuf/user/client.go +++ b/example/trace/grpc_with_db/protobuf/user/client.go @@ -1,8 +1,9 @@ package user import ( - "github.com/gogf/katyusha/krpc" "google.golang.org/grpc" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" ) const ( @@ -14,7 +15,7 @@ type Client struct { } func NewClient(options ...grpc.DialOption) (*Client, error) { - conn, err := krpc.Client.NewGrpcClientConn(ServiceName, options...) + conn, err := grpcx.Client.NewGrpcClientConn(ServiceName, options...) if err != nil { return nil, err } diff --git a/example/trace/grpc_with_db/protocol/user/user.proto b/example/trace/grpc_with_db/protocol/user/user.proto index d93272ae6..1bcc8270d 100644 --- a/example/trace/grpc_with_db/protocol/user/user.proto +++ b/example/trace/grpc_with_db/protocol/user/user.proto @@ -1,4 +1,6 @@ -// protoc --gofast_out=plugins=grpc:. protocol/user/*.proto -I/Users/john/Workspace/Go/GOPATH/src -I/Users/john/Workspace/Go/GOPATH/src/gitee.com/johng/katyusha/.examples/tracing +// protoc --gofast_out=plugins=grpc:. \ +// protocol/user/*.proto \ +// -I/Users/john/Workspace/Go/GOPATH/src syntax = "proto3"; package user; diff --git a/example/trace/grpc_with_db/server/main.go b/example/trace/grpc_with_db/server/main.go index 5738c796e..aa6ef1cba 100644 --- a/example/trace/grpc_with_db/server/main.go +++ b/example/trace/grpc_with_db/server/main.go @@ -1,20 +1,18 @@ package main import ( - _ "github.com/gogf/gf/contrib/drivers/mysql/v2" - "context" "fmt" "time" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" "github.com/gogf/gf/contrib/trace/jaeger/v2" + "github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gcache" "github.com/gogf/gf/v2/os/gctx" - "github.com/gogf/katyusha/krpc" - - "github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user" ) type server struct{} @@ -35,7 +33,7 @@ func main() { // Set ORM cache adapter with redis. g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) - s := krpc.Server.NewGrpcServer() + s := grpcx.Server.New() user.RegisterUserServer(s.Server, &server{}) s.Run() } diff --git a/example/trace/http_with_db/server/main.go b/example/trace/http_with_db/server/main.go index 98951396b..d2351e91a 100644 --- a/example/trace/http_with_db/server/main.go +++ b/example/trace/http_with_db/server/main.go @@ -1,12 +1,11 @@ package main import ( - _ "github.com/gogf/gf/contrib/drivers/mysql/v2" - "context" "fmt" "time" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" "github.com/gogf/gf/contrib/trace/jaeger/v2" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" @@ -46,7 +45,6 @@ func main() { type InsertReq struct { Name string `v:"required#Please input user name."` } - type InsertRes struct { Id int64 } @@ -67,7 +65,6 @@ func (c *cTrace) Insert(ctx context.Context, req *InsertReq) (res *InsertRes, er type QueryReq struct { Id int `v:"min:1#User id is required for querying"` } - type QueryRes struct { User gdb.Record } @@ -92,7 +89,6 @@ func (c *cTrace) Query(ctx context.Context, req *QueryReq) (res *QueryRes, err e type DeleteReq struct { Id int `v:"min:1#User id is required for deleting."` } - type DeleteRes struct{} // Delete is a route handler for deleting specified user info. diff --git a/net/gclient/gclient.go b/net/gclient/gclient.go index 70450822c..5fd55f8c7 100644 --- a/net/gclient/gclient.go +++ b/net/gclient/gclient.go @@ -18,6 +18,7 @@ import ( "github.com/gogf/gf/v2" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/net/gsel" + "github.com/gogf/gf/v2/net/gsvc" "github.com/gogf/gf/v2/os/gfile" ) @@ -32,7 +33,8 @@ type Client struct { retryCount int // Retry count when request fails. retryInterval time.Duration // Retry interval when request fails. middlewareHandler []HandlerFunc // Interceptor handlers - selectorBuilder gsel.Builder // Builder for request balance. + discovery gsvc.Discovery // Discovery for service. + builder gsel.Builder // Builder for request balance. } const ( @@ -66,8 +68,10 @@ func New() *Client { DisableKeepAlives: true, }, }, - header: make(map[string]string), - cookies: make(map[string]string), + header: make(map[string]string), + cookies: make(map[string]string), + builder: gsel.GetBuilder(), + discovery: gsvc.GetRegistry(), } c.header[httpHeaderUserAgent] = defaultClientAgent // It enables OpenTelemetry for client in default. diff --git a/net/gclient/gclient_config.go b/net/gclient/gclient_config.go index 493724d36..807b12fec 100644 --- a/net/gclient/gclient_config.go +++ b/net/gclient/gclient_config.go @@ -20,6 +20,8 @@ import ( "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/intlog" + "github.com/gogf/gf/v2/net/gsel" + "github.com/gogf/gf/v2/net/gsvc" "github.com/gogf/gf/v2/text/gregex" "github.com/gogf/gf/v2/text/gstr" ) @@ -197,3 +199,13 @@ func (c *Client) SetTLSConfig(tlsConfig *tls.Config) error { } return gerror.New(`cannot set TLSClientConfig for custom Transport of the client`) } + +// SetBuilder sets the load balance builder for client. +func (c *Client) SetBuilder(builder gsel.Builder) { + c.builder = builder +} + +// SetDiscovery sets the load balance builder for client. +func (c *Client) SetDiscovery(discovery gsvc.Discovery) { + c.discovery = discovery +} diff --git a/net/gclient/gclient_discovery.go b/net/gclient/gclient_discovery.go index d05db7d96..bcc06b0cb 100644 --- a/net/gclient/gclient_discovery.go +++ b/net/gclient/gclient_discovery.go @@ -14,11 +14,6 @@ import ( "github.com/gogf/gf/v2/internal/intlog" "github.com/gogf/gf/v2/net/gsel" "github.com/gogf/gf/v2/net/gsvc" - "github.com/gogf/gf/v2/os/gctx" -) - -const ( - discoveryMiddlewareHandled gctx.StrKey = `MiddlewareClientDiscoveryHandled` ) type discoveryNode struct { @@ -40,21 +35,18 @@ var clientSelectorMap = gmap.New(true) // internalMiddlewareDiscovery is a client middleware that enables service discovery feature for client. func internalMiddlewareDiscovery(c *Client, r *http.Request) (response *Response, err error) { - ctx := r.Context() - // Mark this request is handled by server tracing middleware, - // to avoid repeated handling by the same middleware. - if ctx.Value(discoveryMiddlewareHandled) != nil { + if c.discovery == nil { return c.Next(r) } - if gsvc.GetRegistry() == nil { - return c.Next(r) - } - var service gsvc.Service - service, err = gsvc.GetAndWatch(ctx, r.URL.Host, func(service gsvc.Service) { + var ( + ctx = r.Context() + service gsvc.Service + ) + service, err = gsvc.GetAndWatchWithDiscovery(ctx, c.discovery, r.URL.Host, func(service gsvc.Service) { intlog.Printf(ctx, `http client watching service "%s" changed`, service.GetPrefix()) if v := clientSelectorMap.Get(service.GetPrefix()); v != nil { if err = updateSelectorNodesByService(ctx, v.(gsel.Selector), service); err != nil { - intlog.Errorf(context.Background(), `%+v`, err) + intlog.Errorf(ctx, `%+v`, err) } } }) @@ -69,7 +61,7 @@ func internalMiddlewareDiscovery(c *Client, r *http.Request) (response *Response selectorMapKey = service.GetPrefix() selectorMapValue = clientSelectorMap.GetOrSetFuncLock(selectorMapKey, func() interface{} { intlog.Printf(ctx, `http client create selector for service "%s"`, selectorMapKey) - selector := gsel.GetBuilder().Build() + selector := c.builder.Build() // Update selector nodes. if err = updateSelectorNodesByService(ctx, selector, service); err != nil { return nil @@ -89,8 +81,8 @@ func internalMiddlewareDiscovery(c *Client, r *http.Request) (response *Response if done != nil { defer done(ctx, gsel.DoneInfo{}) } - r.URL.Host = node.Address() r.Host = node.Address() + r.URL.Host = node.Address() return c.Next(r) } diff --git a/net/ghttp/ghttp.go b/net/ghttp/ghttp.go index 76cd40426..501bad512 100644 --- a/net/ghttp/ghttp.go +++ b/net/ghttp/ghttp.go @@ -41,6 +41,7 @@ type ( sessionManager *gsession.Manager // Session manager. openapi *goai.OpenApiV3 // The OpenApi specification management object. service gsvc.Service // The service for Registry. + registrar gsvc.Registrar // Registrar for service register. } // Router object. diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 1076e9209..3ddbf3db4 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -27,7 +27,9 @@ import ( "github.com/gogf/gf/v2/internal/intlog" "github.com/gogf/gf/v2/net/ghttp/internal/swaggerui" "github.com/gogf/gf/v2/net/goai" + "github.com/gogf/gf/v2/net/gsvc" "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/genv" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/glog" @@ -93,36 +95,35 @@ func GetServer(name ...interface{}) *Server { if len(name) > 0 && name[0] != "" { serverName = gconv.String(name[0]) } - if s := serverMapping.Get(serverName); s != nil { - return s.(*Server) - } - s := &Server{ - instance: serverName, - plugins: make([]Plugin, 0), - servers: make([]*gracefulServer, 0), - closeChan: make(chan struct{}, 10000), - serverCount: gtype.NewInt(), - statusHandlerMap: make(map[string][]HandlerFunc), - serveTree: make(map[string]interface{}), - serveCache: gcache.New(), - routesMap: make(map[string][]*HandlerItem), - openapi: goai.New(), - } - // Initialize the server using default configurations. - if err := s.SetConfig(NewConfig()); err != nil { - panic(gerror.WrapCode(gcode.CodeInvalidConfiguration, err, "")) - } - // Record the server to internal server mapping by name. - serverMapping.Set(serverName, s) - // It enables OpenTelemetry for server in default. - s.Use(internalMiddlewareServerTracing) - return s + v := serverMapping.GetOrSetFuncLock(serverName, func() interface{} { + s := &Server{ + instance: serverName, + plugins: make([]Plugin, 0), + servers: make([]*gracefulServer, 0), + closeChan: make(chan struct{}, 10000), + serverCount: gtype.NewInt(), + statusHandlerMap: make(map[string][]HandlerFunc), + serveTree: make(map[string]interface{}), + serveCache: gcache.New(), + routesMap: make(map[string][]*HandlerItem), + openapi: goai.New(), + registrar: gsvc.GetRegistry(), + } + // Initialize the server using default configurations. + if err := s.SetConfig(NewConfig()); err != nil { + panic(gerror.WrapCode(gcode.CodeInvalidConfiguration, err, "")) + } + // It enables OpenTelemetry for server in default. + s.Use(internalMiddlewareServerTracing) + return s + }) + return v.(*Server) } // Start starts listening on configured port. // This function does not block the process, you can use function Wait blocking the process. func (s *Server) Start() error { - var ctx = context.TODO() + var ctx = gctx.GetInitCtx() // Swagger UI. if s.config.SwaggerPath != "" { diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go index 3daf659e3..92a1fad9f 100644 --- a/net/ghttp/ghttp_server_config.go +++ b/net/ghttp/ghttp_server_config.go @@ -19,6 +19,7 @@ import ( "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/intlog" + "github.com/gogf/gf/v2/net/gsvc" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/os/gres" @@ -529,3 +530,13 @@ func (s *Server) GetHandler() func(w http.ResponseWriter, r *http.Request) { } return s.config.Handler } + +// SetRegistrar sets the Registrar for server. +func (s *Server) SetRegistrar(registrar gsvc.Registrar) { + s.registrar = registrar +} + +// GetRegistrar returns the Registrar of server. +func (s *Server) GetRegistrar() gsvc.Registrar { + return s.registrar +} diff --git a/net/ghttp/ghttp_server_registry.go b/net/ghttp/ghttp_server_registry.go index 12408db5f..f682fd19f 100644 --- a/net/ghttp/ghttp_server_registry.go +++ b/net/ghttp/ghttp_server_registry.go @@ -7,37 +7,26 @@ package ghttp import ( - "context" "fmt" "github.com/gogf/gf/v2/net/gipv4" "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" ) // doServiceRegister registers current service to Registry. func (s *Server) doServiceRegister() { - if gsvc.GetRegistry() == nil { + if s.registrar == nil { return } var ( - ctx = context.Background() + ctx = gctx.GetInitCtx() protocol = gsvc.DefaultProtocol insecure = true - address = s.config.Address err error ) - if address == "" { - address = s.config.HTTPSAddr - } - var ( - array = gstr.Split(address, ":") - ip = array[0] - port = array[1] - ) - if ip == "" { - ip = gipv4.MustGetIntranetIp() - } if s.config.TLSConfig != nil { protocol = `https` insecure = false @@ -48,23 +37,54 @@ func (s *Server) doServiceRegister() { } s.service = &gsvc.LocalService{ Name: s.GetName(), - Endpoints: gsvc.NewEndpoints(fmt.Sprintf(`%s:%s`, ip, port)), + Endpoints: s.calculateListenedEndpoints(), Metadata: metadata, } s.Logger().Debugf(ctx, `service register: %+v`, s.service) - if s.service, err = gsvc.Register(ctx, s.service); err != nil { + if s.service, err = s.registrar.Register(ctx, s.service); err != nil { s.Logger().Fatalf(ctx, `%+v`, err) } } // doServiceDeregister de-registers current service from Registry. func (s *Server) doServiceDeregister() { - if gsvc.GetRegistry() == nil { + if s.registrar == nil { return } - var ctx = context.Background() + var ctx = gctx.GetInitCtx() s.Logger().Debugf(ctx, `service deregister: %+v`, s.service) - if err := gsvc.Deregister(ctx, s.service); err != nil { + if err := s.registrar.Deregister(ctx, s.service); err != nil { s.Logger().Errorf(ctx, `%+v`, err) } } + +func (s *Server) calculateListenedEndpoints() gsvc.Endpoints { + var ( + address = s.config.Address + endpoints = make(gsvc.Endpoints, 0) + listenedIps []string + listenedPorts []int + ) + if address == "" { + address = s.config.HTTPSAddr + } + var addrArray = gstr.Split(address, ":") + switch addrArray[0] { + case "0.0.0.0", "": + listenedIps = []string{gipv4.MustGetIntranetIp()} + default: + listenedIps = []string{addrArray[0]} + } + switch addrArray[1] { + case "0": + listenedPorts = s.GetListenedPorts() + default: + listenedPorts = []int{gconv.Int(addrArray[1])} + } + for _, ip := range listenedIps { + for _, port := range listenedPorts { + endpoints = append(endpoints, gsvc.NewEndpoint(fmt.Sprintf(`%s:%d`, ip, port))) + } + } + return endpoints +} diff --git a/net/gsel/gsel.go b/net/gsel/gsel.go index f616aaaa0..ce28506dd 100644 --- a/net/gsel/gsel.go +++ b/net/gsel/gsel.go @@ -15,6 +15,7 @@ import ( // Builder creates and returns selector in runtime. type Builder interface { + Name() string Build() Selector } diff --git a/net/gsel/gsel_builder_least_connection.go b/net/gsel/gsel_builder_least_connection.go index d13b8c70b..b4abeb600 100644 --- a/net/gsel/gsel_builder_least_connection.go +++ b/net/gsel/gsel_builder_least_connection.go @@ -12,6 +12,10 @@ func NewBuilderLeastConnection() Builder { return &builderLeastConnection{} } +func (*builderLeastConnection) Name() string { + return "BalancerLeastConnection" +} + func (*builderLeastConnection) Build() Selector { return NewSelectorLeastConnection() } diff --git a/net/gsel/gsel_builder_random.go b/net/gsel/gsel_builder_random.go index 49448f033..d2fafe19d 100644 --- a/net/gsel/gsel_builder_random.go +++ b/net/gsel/gsel_builder_random.go @@ -12,6 +12,10 @@ func NewBuilderRandom() Builder { return &builderRandom{} } +func (*builderRandom) Name() string { + return "BalancerRandom" +} + func (*builderRandom) Build() Selector { return NewSelectorRandom() } diff --git a/net/gsel/gsel_builder_round_robin.go b/net/gsel/gsel_builder_round_robin.go index bff17a337..b048ee12b 100644 --- a/net/gsel/gsel_builder_round_robin.go +++ b/net/gsel/gsel_builder_round_robin.go @@ -12,6 +12,10 @@ func NewBuilderRoundRobin() Builder { return &builderRoundRobin{} } +func (*builderRoundRobin) Name() string { + return "BalancerRoundRobin" +} + func (*builderRoundRobin) Build() Selector { return NewSelectorRoundRobin() } diff --git a/net/gsel/gsel_builder_weight.go b/net/gsel/gsel_builder_weight.go index a2d3b6b8f..532c51234 100644 --- a/net/gsel/gsel_builder_weight.go +++ b/net/gsel/gsel_builder_weight.go @@ -12,6 +12,10 @@ func NewBuilderWeight() Builder { return &builderWeight{} } +func (*builderWeight) Name() string { + return "BalancerWeight" +} + func (*builderWeight) Build() Selector { return NewSelectorWeight() } diff --git a/net/gsel/gsel_selector_least_connection.go b/net/gsel/gsel_selector_least_connection.go index 96e2c145f..c31b7fc0d 100644 --- a/net/gsel/gsel_selector_least_connection.go +++ b/net/gsel/gsel_selector_least_connection.go @@ -14,8 +14,6 @@ import ( "github.com/gogf/gf/v2/internal/intlog" ) -const SelectorLeastConnection = "BalancerLeastConnection" - type selectorLeastConnection struct { mu sync.RWMutex nodes []*leastConnectionNode diff --git a/net/gsel/gsel_selector_random.go b/net/gsel/gsel_selector_random.go index 973f09da6..d638af9d8 100644 --- a/net/gsel/gsel_selector_random.go +++ b/net/gsel/gsel_selector_random.go @@ -14,8 +14,6 @@ import ( "github.com/gogf/gf/v2/util/grand" ) -const SelectorRandom = "BalancerRandom" - type selectorRandom struct { mu sync.RWMutex nodes Nodes diff --git a/net/gsel/gsel_selector_round_robin.go b/net/gsel/gsel_selector_round_robin.go index d8f98fca3..fe611a649 100644 --- a/net/gsel/gsel_selector_round_robin.go +++ b/net/gsel/gsel_selector_round_robin.go @@ -13,8 +13,6 @@ import ( "github.com/gogf/gf/v2/internal/intlog" ) -const SelectorRoundRobin = "BalancerRoundRobin" - type selectorRoundRobin struct { mu sync.RWMutex nodes Nodes diff --git a/net/gsel/gsel_selector_weight.go b/net/gsel/gsel_selector_weight.go index 873d343b8..e15968354 100644 --- a/net/gsel/gsel_selector_weight.go +++ b/net/gsel/gsel_selector_weight.go @@ -15,8 +15,6 @@ import ( "github.com/gogf/gf/v2/util/grand" ) -const SelectorWeight = "BalancerWeight" - type selectorWeight struct { mu sync.RWMutex nodes Nodes diff --git a/net/gsvc/gsvc.go b/net/gsvc/gsvc.go index 20d9765e8..e192c38da 100644 --- a/net/gsvc/gsvc.go +++ b/net/gsvc/gsvc.go @@ -24,7 +24,7 @@ type Registry interface { type Registrar interface { // Register registers `service` to Registry. // Note that it returns a new Service if it changes the input Service with custom one. - Register(ctx context.Context, service Service) (Service, error) + Register(ctx context.Context, service Service) (registered Service, err error) // Deregister off-lines and removes `service` from the Registry. Deregister(ctx context.Context, service Service) error @@ -33,17 +33,18 @@ type Registrar interface { // Discovery interface for service discovery. type Discovery interface { // Search searches and returns services with specified condition. - Search(ctx context.Context, in SearchInput) ([]Service, error) + Search(ctx context.Context, in SearchInput) (result []Service, err error) // Watch watches specified condition changes. - Watch(ctx context.Context, key string) (Watcher, error) + // The `key` is the prefix of service key. + Watch(ctx context.Context, key string) (watcher Watcher, err error) } // Watcher interface for service. type Watcher interface { // Proceed proceeds watch in blocking way. // It returns all complete services that watched by `key` if any change. - Proceed() ([]Service, error) + Proceed() (services []Service, err error) // Close closes the watcher. Close() error @@ -113,8 +114,8 @@ type SearchInput struct { } const ( - Schema = `services` - DefaultHead = `services` + Schema = `service` + DefaultHead = `service` DefaultDeployment = `default` DefaultNamespace = `default` DefaultVersion = `latest` diff --git a/net/gsvc/gsvc_discovery.go b/net/gsvc/gsvc_discovery.go index ff7fdc96f..e6da407e7 100644 --- a/net/gsvc/gsvc_discovery.go +++ b/net/gsvc/gsvc_discovery.go @@ -17,25 +17,43 @@ import ( "github.com/gogf/gf/v2/util/gutil" ) -// watchedServiceMap stores used service -var watchedServiceMap = gmap.New(true) +// watchedMap stores discovery object and its watched service mapping. +var watchedMap = gmap.New(true) // ServiceWatch is used to watch the service status. type ServiceWatch func(service Service) // Get retrieves and returns the service by service name. func Get(ctx context.Context, name string) (service Service, err error) { - return GetAndWatch(ctx, name, nil) + return GetAndWatchWithDiscovery(ctx, defaultRegistry, name, nil) +} + +// GetWithDiscovery retrieves and returns the service by service name in `discovery`. +func GetWithDiscovery(ctx context.Context, discovery Discovery, name string) (service Service, err error) { + return GetAndWatchWithDiscovery(ctx, discovery, name, nil) } // GetAndWatch is used to getting the service with custom watch callback function. func GetAndWatch(ctx context.Context, name string, watch ServiceWatch) (service Service, err error) { - v := watchedServiceMap.GetOrSetFuncLock(name, func() interface{} { + return GetAndWatchWithDiscovery(ctx, defaultRegistry, name, watch) +} + +// GetAndWatchWithDiscovery is used to getting the service with custom watch callback function in `discovery`. +func GetAndWatchWithDiscovery(ctx context.Context, discovery Discovery, name string, watch ServiceWatch) (service Service, err error) { + if discovery == nil { + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `discovery cannot be nil`) + } + // Retrieve service map by discovery object. + watchedServiceMap := watchedMap.GetOrSetFunc(discovery, func() interface{} { + return gmap.NewStrAnyMap(true) + }).(*gmap.StrAnyMap) + // Retrieve service by name. + storedService := watchedServiceMap.GetOrSetFuncLock(name, func() interface{} { var ( services []Service watcher Watcher ) - services, err = Search(ctx, SearchInput{ + services, err = discovery.Search(ctx, SearchInput{ Name: name, }) if err != nil { @@ -51,21 +69,21 @@ func GetAndWatch(ctx context.Context, name string, watch ServiceWatch) (service // Watch the service changes in goroutine. if watch != nil { - if watcher, err = Watch(ctx, service.GetPrefix()); err != nil { + if watcher, err = discovery.Watch(ctx, service.GetPrefix()); err != nil { return nil } - go watchAndUpdateService(watcher, service, watch) + go watchAndUpdateService(watchedServiceMap, watcher, service, watch) } return service }) - if v != nil { - service = v.(Service) + if storedService != nil { + service = storedService.(Service) } return } // watchAndUpdateService watches and updates the service in memory if it is changed. -func watchAndUpdateService(watcher Watcher, service Service, watchFunc ServiceWatch) { +func watchAndUpdateService(watchedServiceMap *gmap.StrAnyMap, watcher Watcher, service Service, watchFunc ServiceWatch) { var ( ctx = context.Background() err error diff --git a/net/gsvc/gsvc_metadata.go b/net/gsvc/gsvc_metadata.go index 264021ce3..015c599eb 100644 --- a/net/gsvc/gsvc_metadata.go +++ b/net/gsvc/gsvc_metadata.go @@ -11,7 +11,7 @@ import ( ) // Set sets key-value pair into metadata. -func (m Metadata) Set(key string, value string) { +func (m Metadata) Set(key string, value interface{}) { m[key] = value } diff --git a/os/gstructs/gstructs_z_bench_test.go b/os/gstructs/gstructs_z_bench_test.go index 17ab8a854..6bce57fda 100644 --- a/os/gstructs/gstructs_z_bench_test.go +++ b/os/gstructs/gstructs_z_bench_test.go @@ -7,6 +7,7 @@ package gstructs_test import ( + "reflect" "testing" "github.com/gogf/gf/v2/os/gstructs" @@ -23,6 +24,12 @@ var ( userNilPointer *User ) +func Benchmark_ReflectTypeOf(b *testing.B) { + for i := 0; i < b.N; i++ { + reflect.TypeOf(user).String() + } +} + func Benchmark_TagFields(b *testing.B) { for i := 0; i < b.N; i++ { gstructs.TagFields(user, []string{"params", "my-tag1"})