mirror of
https://gitee.com/johng/gf
synced 2026-07-06 13:42:46 +08:00
add service registry feature for package ghttp/gclient
This commit is contained in:
@ -19,3 +19,7 @@ type Node struct {
|
||||
func (n *Node) Service() *gsvc.Service {
|
||||
return n.service
|
||||
}
|
||||
|
||||
func (n *Node) Address() string {
|
||||
return n.service.Address()
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ import (
|
||||
|
||||
var (
|
||||
_ gsvc.Registry = &Registry{}
|
||||
_ gsvc.Watcher = &watcher{}
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
|
||||
@ -46,12 +46,10 @@ func (r *Registry) Register(ctx context.Context, service *gsvc.Service) error {
|
||||
}
|
||||
|
||||
func (r *Registry) Deregister(ctx context.Context, service *gsvc.Service) error {
|
||||
defer func() {
|
||||
if r.lease != nil {
|
||||
_ = r.lease.Close()
|
||||
}
|
||||
}()
|
||||
_, err := r.client.Delete(ctx, service.Key())
|
||||
if r.lease != nil {
|
||||
_ = r.lease.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,10 @@ import (
|
||||
etcd3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
_ gsvc.Watcher = &watcher{}
|
||||
)
|
||||
|
||||
type watcher struct {
|
||||
key string
|
||||
ctx context.Context
|
||||
|
||||
21
example/registry/http/client/main.go
Normal file
21
example/registry/http/client/main.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/contrib/registry/etcd/v2"
|
||||
"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`))
|
||||
|
||||
res, err := g.Client().Get(gctx.New(), `http://hello.svc:12345/`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer res.Close()
|
||||
fmt.Println(res.ReadAllString())
|
||||
}
|
||||
18
example/registry/http/server/main.go
Normal file
18
example/registry/http/server/main.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/contrib/registry/etcd/v2"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`))
|
||||
|
||||
s := g.Server(`hello.svc`)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(`Hello world`)
|
||||
})
|
||||
s.Run()
|
||||
}
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/contrib/balancer/v2"
|
||||
"github.com/gogf/gf/contrib/registry/etcd/v2"
|
||||
"github.com/gogf/gf/contrib/resolver/v2"
|
||||
pb "github.com/gogf/gf/example/rawgrpc/helloworld"
|
||||
pb "github.com/gogf/gf/example/registry/rawgrpc/helloworld"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/gogf/gf/contrib/registry/etcd/v2"
|
||||
"github.com/gogf/gf/contrib/resolver/v2"
|
||||
pb "github.com/gogf/gf/example/rawgrpc/helloworld"
|
||||
pb "github.com/gogf/gf/example/registry/rawgrpc/helloworld"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gipv4"
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
@ -63,7 +63,9 @@ func Server(name ...interface{}) *ghttp.Server {
|
||||
// The configuration is not necessary, so it just prints internal logs.
|
||||
intlog.Printf(ctx, `missing configuration from configuration component for HTTP server "%s"`, instanceName)
|
||||
}
|
||||
|
||||
if s.GetName() == "" || s.GetName() == ghttp.DefaultServerName {
|
||||
s.SetName(instanceName)
|
||||
}
|
||||
// Server logger configuration checks.
|
||||
serverLoggerNodeName := fmt.Sprintf(`%s.%s.%s`, configNodeName, s.GetName(), configNodeNameLogger)
|
||||
if v, _ := Config().Get(ctx, serverLoggerNodeName); !v.IsEmpty() {
|
||||
|
||||
@ -78,7 +78,7 @@ func New() *Client {
|
||||
}
|
||||
c.header[httpHeaderUserAgent] = defaultClientAgent
|
||||
// It enables OpenTelemetry for client in default.
|
||||
c.Use(internalMiddlewareTracing)
|
||||
c.Use(internalMiddlewareTracing, internalMiddlewareDiscovery)
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
39
net/gclient/gclient_discovery.go
Normal file
39
net/gclient/gclient_discovery.go
Normal file
@ -0,0 +1,39 @@
|
||||
// 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 gclient
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
const (
|
||||
discoveryMiddlewareHandled gctx.StrKey = `MiddlewareClientDiscoveryHandled`
|
||||
)
|
||||
|
||||
// internalMiddlewareDiscovery is a client middleware that enables service discovery feature for client.
|
||||
func internalMiddlewareDiscovery(c *Client, r *http.Request) (response *Response, err error) {
|
||||
var 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 {
|
||||
return c.Next(r)
|
||||
}
|
||||
if gsvc.GetRegistry() != nil {
|
||||
service, err := gsvc.Get(ctx, r.URL.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if service != nil {
|
||||
r.URL.Host = service.Address()
|
||||
r.Host = service.Address()
|
||||
}
|
||||
}
|
||||
return c.Next(r)
|
||||
}
|
||||
@ -48,9 +48,7 @@ const (
|
||||
|
||||
// internalMiddlewareTracing is a client middleware that enables tracing feature using standards of OpenTelemetry.
|
||||
func internalMiddlewareTracing(c *Client, r *http.Request) (response *Response, err error) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
)
|
||||
var ctx = r.Context()
|
||||
// Mark this request is handled by server tracing middleware,
|
||||
// to avoid repeated handling by the same middleware.
|
||||
if ctx.Value(tracingMiddlewareHandled) != nil {
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
@ -24,7 +25,7 @@ import (
|
||||
type (
|
||||
// Server wraps the http.Server and provides more rich features.
|
||||
Server struct {
|
||||
name string // Unique name for instance management.
|
||||
instance string // Instance name.
|
||||
config ServerConfig // Configuration.
|
||||
plugins []Plugin // Plugin array to extend server functionality.
|
||||
servers []*gracefulServer // Underlying http.Server array.
|
||||
@ -36,6 +37,7 @@ type (
|
||||
statusHandlerMap map[string][]HandlerFunc // Custom status handler map.
|
||||
sessionManager *gsession.Manager // Session manager.
|
||||
openapi *goai.OpenApiV3 // The OpenApi specification management object.
|
||||
service *gsvc.Service // The service for Registry.
|
||||
}
|
||||
|
||||
// Router object.
|
||||
|
||||
@ -47,9 +47,7 @@ func init() {
|
||||
|
||||
// serverProcessInit initializes some process configurations, which can only be done once.
|
||||
func serverProcessInit() {
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
)
|
||||
var ctx = context.TODO()
|
||||
if !serverProcessInitialized.Cas(false, true) {
|
||||
return
|
||||
}
|
||||
@ -98,7 +96,7 @@ func GetServer(name ...interface{}) *Server {
|
||||
return s.(*Server)
|
||||
}
|
||||
s := &Server{
|
||||
name: serverName,
|
||||
instance: serverName,
|
||||
plugins: make([]Plugin, 0),
|
||||
servers: make([]*gracefulServer, 0),
|
||||
closeChan: make(chan struct{}, 10000),
|
||||
@ -123,9 +121,7 @@ func GetServer(name ...interface{}) *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 = context.TODO()
|
||||
|
||||
// Swagger UI.
|
||||
if s.config.SwaggerPath != "" {
|
||||
@ -183,7 +179,7 @@ func (s *Server) Start() error {
|
||||
if s.config.SessionStorage == nil {
|
||||
path := ""
|
||||
if s.config.SessionPath != "" {
|
||||
path = gfile.Join(s.config.SessionPath, s.name)
|
||||
path = gfile.Join(s.config.SessionPath, s.config.Name)
|
||||
if !gfile.Exists(path) {
|
||||
if err := gfile.Mkdir(path); err != nil {
|
||||
return gerror.Wrapf(err, `mkdir failed for "%s"`, path)
|
||||
@ -231,7 +227,7 @@ func (s *Server) Start() error {
|
||||
fdMapStr := genv.Get(adminActionReloadEnvKey).String()
|
||||
if len(fdMapStr) > 0 {
|
||||
sfm := bufferToServerFdMap([]byte(fdMapStr))
|
||||
if v, ok := sfm[s.name]; ok {
|
||||
if v, ok := sfm[s.config.Name]; ok {
|
||||
s.startServer(v)
|
||||
reloaded = true
|
||||
}
|
||||
@ -249,6 +245,7 @@ func (s *Server) Start() error {
|
||||
})
|
||||
}
|
||||
s.initOpenApi()
|
||||
s.doServiceRegister()
|
||||
s.dumpRouterMap()
|
||||
return nil
|
||||
}
|
||||
@ -352,7 +349,7 @@ func (s *Server) GetRoutes() []RouterItem {
|
||||
array, _ := gregex.MatchString(`(.*?)%([A-Z]+):(.+)@(.+)`, k)
|
||||
for index, registeredItem := range registeredItems {
|
||||
item := RouterItem{
|
||||
Server: s.name,
|
||||
Server: s.config.Name,
|
||||
Address: address,
|
||||
Domain: array[4],
|
||||
Type: registeredItem.Handler.Type,
|
||||
@ -417,9 +414,8 @@ func (s *Server) GetRoutes() []RouterItem {
|
||||
// Run starts server listening in blocking way.
|
||||
// It's commonly used for single server situation.
|
||||
func (s *Server) Run() {
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
)
|
||||
var ctx = context.TODO()
|
||||
|
||||
if err := s.Start(); err != nil {
|
||||
s.Logger().Fatalf(ctx, `%+v`, err)
|
||||
}
|
||||
@ -434,15 +430,15 @@ func (s *Server) Run() {
|
||||
}
|
||||
}
|
||||
}
|
||||
s.doServiceDeregister()
|
||||
s.Logger().Printf(ctx, "%d: all servers shutdown", gproc.Pid())
|
||||
}
|
||||
|
||||
// Wait blocks to wait for all servers done.
|
||||
// It's commonly used in multiple servers situation.
|
||||
// It's commonly used in multiple server situation.
|
||||
func Wait() {
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
)
|
||||
var ctx = context.TODO()
|
||||
|
||||
<-allDoneChan
|
||||
// Remove plugins.
|
||||
serverMapping.Iterator(func(k string, v interface{}) bool {
|
||||
@ -560,7 +556,7 @@ func (s *Server) startServer(fdMap listenerFdMap) {
|
||||
if s.serverCount.Add(-1) < 1 {
|
||||
s.closeChan <- struct{}{}
|
||||
if serverRunning.Add(-1) < 1 {
|
||||
serverMapping.Remove(s.name)
|
||||
serverMapping.Remove(s.instance)
|
||||
allDoneChan <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,11 +14,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
"github.com/gogf/gf/v2/os/gsession"
|
||||
"github.com/gogf/gf/v2/os/gview"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
@ -38,6 +40,9 @@ type ServerConfig struct {
|
||||
// Basic.
|
||||
// ======================================================================================================
|
||||
|
||||
// Service name, which is for service registry and discovery.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Address specifies the server listening address like "port" or ":port",
|
||||
// multiple addresses joined using ','.
|
||||
Address string `json:"address"`
|
||||
@ -234,6 +239,7 @@ type ServerConfig struct {
|
||||
// some pointer attributes that may be shared in different servers.
|
||||
func NewConfig() ServerConfig {
|
||||
return ServerConfig{
|
||||
Name: DefaultServerName,
|
||||
Address: "",
|
||||
HTTPSAddr: "",
|
||||
Handler: nil,
|
||||
@ -311,6 +317,13 @@ func (s *Server) SetConfigWithMap(m map[string]interface{}) error {
|
||||
// SetConfig sets the configuration for the server.
|
||||
func (s *Server) SetConfig(c ServerConfig) error {
|
||||
s.config = c
|
||||
// Address, check and use a random free port.
|
||||
array := gstr.Split(s.config.Address, ":")
|
||||
if s.config.Address == "" || len(array) < 2 || array[1] == "0" {
|
||||
s.config.Address = gstr.Join([]string{
|
||||
array[0], gconv.String(gtcp.MustGetFreePort()),
|
||||
}, ":")
|
||||
}
|
||||
// Static.
|
||||
if c.ServerRoot != "" {
|
||||
s.SetServerRoot(c.ServerRoot)
|
||||
@ -382,9 +395,7 @@ func (s *Server) SetHTTPSPort(port ...int) {
|
||||
// EnableHTTPS enables HTTPS with given certification and key files for the server.
|
||||
// The optional parameter `tlsConfig` specifies custom TLS configuration.
|
||||
func (s *Server) EnableHTTPS(certFile, keyFile string, tlsConfig ...*tls.Config) {
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
)
|
||||
var ctx = context.TODO()
|
||||
certFileRealPath := gfile.RealPath(certFile)
|
||||
if certFileRealPath == "" {
|
||||
certFileRealPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + certFile)
|
||||
@ -462,7 +473,12 @@ func (s *Server) SetView(view *gview.View) {
|
||||
|
||||
// GetName returns the name of the server.
|
||||
func (s *Server) GetName() string {
|
||||
return s.name
|
||||
return s.config.Name
|
||||
}
|
||||
|
||||
// SetName sets the name for the server.
|
||||
func (s *Server) SetName(name string) {
|
||||
s.config.Name = name
|
||||
}
|
||||
|
||||
// Handler returns the request handler of the server.
|
||||
|
||||
56
net/ghttp/ghttp_server_registry.go
Normal file
56
net/ghttp/ghttp_server_registry.go
Normal file
@ -0,0 +1,56 @@
|
||||
// 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 ghttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/net/gipv4"
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// doServiceRegister registers current service to Registry.
|
||||
func (s *Server) doServiceRegister() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
protocol = `http`
|
||||
insecure = true
|
||||
address = s.config.Address
|
||||
)
|
||||
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
|
||||
}
|
||||
metadata := gsvc.Metadata{
|
||||
gsvc.MDProtocol: protocol,
|
||||
gsvc.MDInsecure: insecure,
|
||||
}
|
||||
s.service = &gsvc.Service{
|
||||
Name: s.GetName(),
|
||||
Endpoints: []string{fmt.Sprintf(`%s:%s`, ip, port)},
|
||||
Metadata: metadata,
|
||||
}
|
||||
_ = gsvc.Register(ctx, s.service)
|
||||
}
|
||||
|
||||
// doServiceDeregister de-registers current service from Registry.
|
||||
func (s *Server) doServiceDeregister() {
|
||||
_ = gsvc.Deregister(context.Background(), s.service)
|
||||
}
|
||||
@ -25,6 +25,7 @@ type Selector interface {
|
||||
// Node is node interface.
|
||||
type Node interface {
|
||||
Service() *gsvc.Service
|
||||
Address() string
|
||||
}
|
||||
|
||||
// DoneFunc is callback function when RPC invoke done.
|
||||
|
||||
@ -105,3 +105,9 @@ func SetRegistry(registry Registry) {
|
||||
}
|
||||
defaultRegistry = registry
|
||||
}
|
||||
|
||||
// GetRegistry returns the default Registry that is previously set.
|
||||
// It returns nil if no Registry is set.
|
||||
func GetRegistry() Registry {
|
||||
return defaultRegistry
|
||||
}
|
||||
|
||||
@ -88,6 +88,15 @@ func (s *Service) Value() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Address returns the first endpoint of Service.
|
||||
// Eg: 192.168.1.12:9000.
|
||||
func (s *Service) Address() string {
|
||||
if len(s.Endpoints) == 0 {
|
||||
return ""
|
||||
}
|
||||
return s.Endpoints[0]
|
||||
}
|
||||
|
||||
func (s *Service) autoFillDefaultAttributes() {
|
||||
if s.Prefix == "" {
|
||||
s.Prefix = gcmd.GetOptWithEnv(EnvPrefix, DefaultPrefix).String()
|
||||
|
||||
Reference in New Issue
Block a user