diff --git a/consul/README.md b/consul/README.md index dee4af7..ab6da87 100644 --- a/consul/README.md +++ b/consul/README.md @@ -15,6 +15,7 @@ package main import ( "context" + "fmt" "log" "github.com/cloudwego/hertz/pkg/app" @@ -26,7 +27,6 @@ import ( "github.com/hertz-contrib/registry/consul" ) - func main() { // build a consul client config := consulapi.DefaultConfig() @@ -40,7 +40,11 @@ func main() { r := consul.NewConsulRegister(consulClient) // run Hertz with the consul register - addr := "127.0.0.1:8888" + localIP, err := consul.GetLocalIPv4Address() + if err != nil { + log.Fatal(err) + } + addr := fmt.Sprintf("%s:8888",localIP) h := server.Default( server.WithHostPorts(addr), server.WithRegistry(r, ®istry.Info{ @@ -136,9 +140,9 @@ func main() { ## Example -[Server](example/server/main.go):`example/server/main.go` +[Server](example/basic/server/main.go):`example/server/main.go` -[Client](example/client/main.go):`example/client/main.go` +[Client](example/basic/client/main.go):`example/client/main.go` ## Compatibility @@ -146,4 +150,4 @@ Compatible with consul from v1.11.x to v1.13.x. [consul version list](https://releases.hashicorp.com/consul) -maintained by: [Lemonfish](https://github.com/LemonFish873310466) \ No newline at end of file +maintained by: [Lemonfish](https://github.com/LemonFish873310466) / [claude-zq](https://github.com/Claude-Zq) \ No newline at end of file diff --git a/consul/README_CN.md b/consul/README_CN.md index 17f317e..962692c 100644 --- a/consul/README_CN.md +++ b/consul/README_CN.md @@ -15,6 +15,7 @@ package main import ( "context" + "fmt" "log" "github.com/cloudwego/hertz/pkg/app" @@ -39,7 +40,11 @@ func main() { r := consul.NewConsulRegister(consulClient) // run Hertz with the consul register - addr := "127.0.0.1:8888" + localIP, err := consul.GetLocalIPv4Address() + if err != nil { + log.Fatal(err) + } + addr := fmt.Sprintf("%s:8888",localIP) h := server.Default( server.WithHostPorts(addr), server.WithRegistry(r, ®istry.Info{ @@ -135,9 +140,9 @@ func main() { ## 使用样例 -[服务端](example/server/main.go):`example/server/main.go` +[服务端](example/basic/server/main.go):`example/server/main.go` -[客户端](example/client/main.go):`example/client/main.go` +[客户端](example/basic/client/main.go):`example/client/main.go` ## 兼容性 @@ -145,4 +150,4 @@ func main() { [consul版本列表](https://releases.hashicorp.com/consul) -维护者: [Lemonfish](https://github.com/LemonFish873310466) \ No newline at end of file +维护者: [Lemonfish](https://github.com/LemonFish873310466) / [claude-zq](https://github.com/Claude-Zq) \ No newline at end of file diff --git a/consul/consul_test.go b/consul/consul_test.go index 7b22d88..6fc2b3f 100644 --- a/consul/consul_test.go +++ b/consul/consul_test.go @@ -40,7 +40,7 @@ var ( cRegistry registry.Registry cResolver discovery.Resolver consulAddr = "127.0.0.1:8500" - localIpAddr = utils.LocalIP() + localIpAddr string ) func init() { @@ -61,6 +61,28 @@ func init() { return } cResolver = NewConsulResolver(cli2) + + localIpAddr, err = getLocalIPv4Address() + if err != nil { + log.Fatal(err) + } +} + +func getLocalIPv4Address() (string, error) { + addr, err := net.InterfaceAddrs() + if err != nil { + return "", err + } + for _, addr := range addr { + ipNet, isIpNet := addr.(*net.IPNet) + if isIpNet && !ipNet.IP.IsLoopback() { + ipv4 := ipNet.IP.To4() + if ipv4 != nil { + return ipv4.String(), nil + } + } + } + return "", fmt.Errorf("not found ipv4 address") } // TestNewConsulResolver tests unit test preparatory work. @@ -83,7 +105,7 @@ func TestNewConsulRegister(t *testing.T) { assert.NotNil(t, consulRegister) } -// TestNewConsulResolver tests the NewConsulResolver function with check option. +// TestNewConsulRegisterWithCheckOption tests the NewConsulRegister function with check option. func TestNewConsulRegisterWithCheckOption(t *testing.T) { config := consulapi.DefaultConfig() config.Address = consulAddr @@ -98,7 +120,7 @@ func TestNewConsulRegisterWithCheckOption(t *testing.T) { check.Interval = "10s" check.DeregisterCriticalServiceAfter = "1m" - consulResolver := NewConsulResolver(cli, WithCheck(check)) + consulResolver := NewConsulRegister(cli, WithCheck(check)) assert.NotNil(t, consulResolver) } diff --git a/consul/example/client/main.go b/consul/example/basic/client/main.go similarity index 100% rename from consul/example/client/main.go rename to consul/example/basic/client/main.go diff --git a/consul/example/server/main.go b/consul/example/basic/server/main.go similarity index 93% rename from consul/example/server/main.go rename to consul/example/basic/server/main.go index 04cb756..1b774c1 100644 --- a/consul/example/server/main.go +++ b/consul/example/basic/server/main.go @@ -17,19 +17,22 @@ package main import ( "context" "log" + "net" "sync" - "github.com/cloudwego/hertz/pkg/app/server/registry" - consulapi "github.com/hashicorp/consul/api" - "github.com/cloudwego/hertz/pkg/app" "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/app/server/registry" "github.com/cloudwego/hertz/pkg/common/utils" "github.com/cloudwego/hertz/pkg/protocol/consts" + consulapi "github.com/hashicorp/consul/api" "github.com/hertz-contrib/registry/consul" ) -var wg sync.WaitGroup +var ( + wg sync.WaitGroup + localIP = "your ip" +) func main() { config := consulapi.DefaultConfig() @@ -43,7 +46,7 @@ func main() { wg.Add(2) go func() { defer wg.Done() - addr := "127.0.0.1:8888" + addr := net.JoinHostPort(localIP, "8888") r := consul.NewConsulRegister(consulClient) h := server.Default( server.WithHostPorts(addr), @@ -62,7 +65,7 @@ func main() { }() go func() { defer wg.Done() - addr := "127.0.0.1:8889" + addr := net.JoinHostPort(localIP, "8889") r := consul.NewConsulRegister(consulClient) h := server.Default( server.WithHostPorts(addr), diff --git a/consul/example/custom-config/client/main.go b/consul/example/custom-config/client/main.go new file mode 100644 index 0000000..1e3760d --- /dev/null +++ b/consul/example/custom-config/client/main.go @@ -0,0 +1,157 @@ +// Copyright 2022 CloudWeGo 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. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net" + "time" + + "github.com/cloudwego/hertz/pkg/app/client" + "github.com/cloudwego/hertz/pkg/app/client/discovery" + "github.com/cloudwego/hertz/pkg/app/client/loadbalance" + "github.com/cloudwego/hertz/pkg/app/middlewares/client/sd" + "github.com/cloudwego/hertz/pkg/common/config" + "github.com/cloudwego/hertz/pkg/common/hlog" + "github.com/cloudwego/hertz/pkg/protocol" + "github.com/hashicorp/consul/api" + "github.com/hertz-contrib/registry/consul" +) + +var localIP = "your ip" + +type Example struct { + A int `json:"a"` + B int `json:"b"` +} + +func main() { + // build a consul client + consulConfig := api.DefaultConfig() + consulConfig.Address = "127.0.0.1:8500" + consulClient, err := api.NewClient(consulConfig) + if err != nil { + log.Fatal(err) + return + } + // build a consul resolver with the consul client + r := consul.NewConsulResolver(consulClient) + + discoveryWithSD(r) + discoveryWithTag(r) + discoveryWithCustomizedAddr(r) + discoveryWithLoadBalanceOptions(r) + discoveryThenUsePostMethod(r) +} + +func discoveryWithSD(r discovery.Resolver) { + fmt.Println("simply discovery:") + cli, err := client.NewClient() + if err != nil { + panic(err) + } + cli.Use(sd.Discovery(r)) + for i := 0; i < 10; i++ { + status, body, err := cli.Get(context.Background(), nil, "http://custom-config-demo/ping", config.WithSD(true)) + if err != nil { + hlog.Fatal(err) + } + hlog.Infof("code=%d,body=%s", status, string(body)) + } +} + +func discoveryWithTag(r discovery.Resolver) { + fmt.Println("discovery with tag:") + cli, err := client.NewClient() + if err != nil { + panic(err) + } + cli.Use(sd.Discovery(r)) + for i := 0; i < 10; i++ { + status, body, err := cli.Get(context.Background(), nil, "http://custom-config-demo/ping", config.WithSD(true), config.WithTag("key1", "val1")) + if err != nil { + hlog.Fatal(err) + } + hlog.Infof("code=%d,body=%s", status, string(body)) + } +} + +func discoveryWithCustomizedAddr(r discovery.Resolver) { + fmt.Println("discovery with customizedAddr:") + cli, err := client.NewClient() + if err != nil { + panic(err) + } + + cli.Use(sd.Discovery(r, sd.WithCustomizedAddrs(net.JoinHostPort(localIP, "5001")))) + for i := 0; i < 10; i++ { + status, body, err := cli.Get(context.Background(), nil, "http://custom-config-demo/ping", config.WithSD(true), config.WithTag("key1", "val1")) + if err != nil { + hlog.Fatal(err) + } + hlog.Infof("code=%d,body=%s", status, string(body)) + } +} + +func discoveryWithLoadBalanceOptions(r discovery.Resolver) { + fmt.Println("discovery with loadBalanceOptions:") + cli, err := client.NewClient() + if err != nil { + panic(err) + } + cli.Use(sd.Discovery(r, sd.WithLoadBalanceOptions(loadbalance.NewWeightedBalancer(), loadbalance.Options{ + RefreshInterval: 5 * time.Second, + ExpireInterval: 15 * time.Second, + }))) + for i := 0; i < 10; i++ { + status, body, err := cli.Get(context.Background(), nil, "http://custom-config-demo/ping", config.WithSD(true)) + if err != nil { + hlog.Fatal(err) + } + hlog.Infof("code=%d,body=%s", status, string(body)) + } +} + +func discoveryThenUsePostMethod(r discovery.Resolver) { + fmt.Println("discovery and use post method to send request:") + cli, err := client.NewClient() + if err != nil { + panic(err) + } + cli.Use(sd.Discovery(r)) + + for i := 0; i < 10; i++ { + // set request config、method、request uri. + req := protocol.AcquireRequest() + req.SetOptions(config.WithSD(true)) + req.SetMethod("POST") + req.SetRequestURI("http://custom-config-demo/ping") + t := Example{A: i, B: i} + bytes, _ := json.Marshal(t) + // set body and content type + req.SetBody(bytes) + req.Header.SetContentTypeBytes([]byte("application/json")) + resp := protocol.AcquireResponse() + // send request + err := cli.Do(context.Background(), req, resp) + if err != nil { + hlog.Fatal(err) + } + hlog.Infof("code=%d,body=%s", resp.StatusCode(), string(resp.Body())) + } +} diff --git a/consul/example/custom-config/server/main.go b/consul/example/custom-config/server/main.go new file mode 100644 index 0000000..48efd7a --- /dev/null +++ b/consul/example/custom-config/server/main.go @@ -0,0 +1,118 @@ +// Copyright 2022 CloudWeGo 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. + +package main + +import ( + "context" + "log" + "net" + "sync" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/app/server/registry" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "github.com/hashicorp/consul/api" + "github.com/hertz-contrib/registry/consul" +) + +var ( + wg sync.WaitGroup + localIP = "your ip" +) + +type Example struct { + A int `json:"a"` + B int `json:"b"` +} + +func main() { + config := api.DefaultConfig() + config.Address = "127.0.0.1:8500" + consulClient, err := api.NewClient(config) + if err != nil { + log.Fatal(err) + return + } + + // custom check + check := &api.AgentServiceCheck{ + Interval: "7s", + Timeout: "5s", + DeregisterCriticalServiceAfter: "15s", + } + r := consul.NewConsulRegister(consulClient, consul.WithCheck(check)) + + wg.Add(2) + go func() { + defer wg.Done() + addr := net.JoinHostPort(localIP, "5001") + + h := server.Default( + server.WithHostPorts(addr), + server.WithRegistry(r, ®istry.Info{ + ServiceName: "custom-config-demo", + Addr: utils.NewNetAddr("tcp", addr), + Weight: 10, + Tags: map[string]string{ + "key1": "val1", + }, + }), + ) + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong1"}) + }) + h.POST("/ping", func(c context.Context, ctx *app.RequestContext) { + e := Example{} + if err := ctx.Bind(&e); err != nil { + ctx.String(consts.StatusBadRequest, err.Error()) + return + } + ctx.JSON(consts.StatusOK, e) + }) + h.Spin() + }() + + go func() { + defer wg.Done() + addr := net.JoinHostPort(localIP, "5002") + h := server.Default( + server.WithHostPorts(addr), + server.WithRegistry(r, ®istry.Info{ + ServiceName: "custom-config-demo", + Addr: utils.NewNetAddr("tcp", addr), + Weight: 10, + Tags: map[string]string{ + "key2": "val2", + }, + }), + ) + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong2"}) + }) + h.POST("/ping", func(c context.Context, ctx *app.RequestContext) { + e := Example{} + if err := ctx.Bind(&e); err != nil { + ctx.String(consts.StatusBadRequest, err.Error()) + return + } + ctx.JSON(consts.StatusOK, e) + }) + h.Spin() + }() + + wg.Wait() +} diff --git a/consul/registry.go b/consul/registry.go index b987746..41b284d 100644 --- a/consul/registry.go +++ b/consul/registry.go @@ -23,6 +23,18 @@ import ( "github.com/hashicorp/consul/api" ) +const ( + DefaultCheckInterval = "5s" + DefaultCheckTimeout = "5s" + DefaultCheckDeregisterCriticalServiceAfter = "1m" +) + +var ( + ErrNilInfo = errors.New("info is nil") + ErrMissingServiceName = errors.New("missing service name in consul register") + ErrMissingAddr = errors.New("missing addr in consul register") +) + type consulRegistry struct { consulClient *api.Client opts options @@ -57,8 +69,7 @@ func NewConsulRegister(consulClient *api.Client, opts ...Option) registry.Regist // Register register a service to consul. func (c *consulRegistry) Register(info *registry.Info) error { - err := validateRegistryInfo(info) - if err != nil { + if err := validateRegistryInfo(info); err != nil { return fmt.Errorf("validating registry info failed, err: %w", err) } @@ -109,22 +120,22 @@ func (c *consulRegistry) Deregister(info *registry.Info) error { func defaultCheck() *api.AgentServiceCheck { check := new(api.AgentServiceCheck) - check.Timeout = "5s" - check.Interval = "5s" - check.DeregisterCriticalServiceAfter = "1m" + check.Timeout = DefaultCheckTimeout + check.Interval = DefaultCheckInterval + check.DeregisterCriticalServiceAfter = DefaultCheckDeregisterCriticalServiceAfter return check } func validateRegistryInfo(info *registry.Info) error { if info == nil { - return errors.New("info is nil") + return ErrNilInfo } if info.ServiceName == "" { - return errors.New("missing service name in consul register") + return ErrMissingServiceName } if info.Addr == nil { - return errors.New("missing addr in consul register") + return ErrMissingAddr } return nil diff --git a/consul/resolver.go b/consul/resolver.go index 92ccc32..6a178e7 100644 --- a/consul/resolver.go +++ b/consul/resolver.go @@ -34,17 +34,12 @@ type consulResolver struct { var _ discovery.Resolver = (*consulResolver)(nil) // NewConsulResolver create a service resolver using consul. -func NewConsulResolver(consulClient *api.Client, opts ...Option) discovery.Resolver { - op := options{} - for _, opt := range opts { - opt(&op) - } - +func NewConsulResolver(consulClient *api.Client) discovery.Resolver { return &consulResolver{consulClient: consulClient} } // Target return a description for the given target that is suitable for being a key for cache. -func (c *consulResolver) Target(ctx context.Context, target *discovery.TargetInfo) (description string) { +func (c *consulResolver) Target(_ context.Context, target *discovery.TargetInfo) (description string) { return target.Host } @@ -54,7 +49,7 @@ func (c *consulResolver) Name() string { } // Resolve a service info by desc. -func (c *consulResolver) Resolve(ctx context.Context, desc string) (discovery.Result, error) { +func (c *consulResolver) Resolve(_ context.Context, desc string) (discovery.Result, error) { var eps []discovery.Instance agentServiceList, _, err := c.consulClient.Health().Service(desc, "", true, nil) if err != nil { diff --git a/consul/utils.go b/consul/utils.go index ccbcd4f..9b662bf 100644 --- a/consul/utils.go +++ b/consul/utils.go @@ -20,7 +20,6 @@ import ( "strconv" "github.com/cloudwego/hertz/pkg/app/server/registry" - "github.com/cloudwego/hertz/pkg/common/utils" ) func parseAddr(addr net.Addr) (host string, port int, err error) { @@ -30,7 +29,7 @@ func parseAddr(addr net.Addr) (host string, port int, err error) { } if host == "" || host == "::" { - host = utils.LocalIP() + return "", 0, fmt.Errorf("empty host") } port, err = strconv.Atoi(portStr)