2026-05-05 09:42:15 +08:00
|
|
|
package discover
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"reflect"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
|
"apigo.cc/go/cast"
|
|
|
|
|
gohttp "apigo.cc/go/http"
|
|
|
|
|
"apigo.cc/go/log"
|
2026-05-09 21:11:46 +08:00
|
|
|
"apigo.cc/go/timer"
|
2026-05-05 09:42:15 +08:00
|
|
|
)
|
|
|
|
|
|
2026-05-05 14:52:32 +08:00
|
|
|
func (d *Discoverer) getHttpClient(app string, timeout time.Duration, h2c bool) *gohttp.Client {
|
|
|
|
|
d.appClientPoolsLock.RLock()
|
|
|
|
|
c := d.appClientPools[app]
|
|
|
|
|
d.appClientPoolsLock.RUnlock()
|
2026-05-05 09:42:15 +08:00
|
|
|
if c != nil {
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 14:52:32 +08:00
|
|
|
d.appClientPoolsLock.Lock()
|
|
|
|
|
defer d.appClientPoolsLock.Unlock()
|
|
|
|
|
c = d.appClientPools[app]
|
2026-05-05 09:42:15 +08:00
|
|
|
if c != nil {
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if h2c {
|
|
|
|
|
c = gohttp.NewClientH2C(timeout)
|
|
|
|
|
} else {
|
|
|
|
|
c = gohttp.NewClient(timeout)
|
|
|
|
|
}
|
2026-05-05 14:52:32 +08:00
|
|
|
d.appClientPools[app] = c
|
2026-05-05 09:42:15 +08:00
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// Caller 用于发起服务间调用
|
2026-05-05 09:42:15 +08:00
|
|
|
type Caller struct {
|
2026-05-05 14:27:15 +08:00
|
|
|
discoverer *Discoverer
|
|
|
|
|
Request *http.Request // 原始请求,用于透传 Header
|
|
|
|
|
NoBody bool // 是否不发送请求体
|
|
|
|
|
logger *log.Logger // 用于日志记录的 Logger
|
2026-05-05 09:42:15 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 21:11:46 +08:00
|
|
|
func (d *Discoverer) From(request *http.Request) *Caller {
|
|
|
|
|
return &Caller{discoverer: d, Request: request, logger: d.logger}
|
2026-05-05 14:27:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *Discoverer) NewCaller(request *http.Request, logger *log.Logger) *Caller {
|
|
|
|
|
return &Caller{discoverer: d, Request: request, logger: logger}
|
2026-05-05 09:42:15 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// logError 记录 Discover 调用器错误
|
|
|
|
|
func (c *Caller) logError(msg string, extra ...any) {
|
2026-05-05 09:42:15 +08:00
|
|
|
if c.logger == nil {
|
|
|
|
|
c.logger = log.DefaultLogger
|
|
|
|
|
}
|
2026-05-05 13:59:03 +08:00
|
|
|
c.logger.Error("Discover Caller: "+msg, extra...)
|
2026-05-05 09:42:15 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// Get 发起 GET 请求
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) Get(app, path string, headers ...string) *gohttp.Result {
|
|
|
|
|
return c.Do("GET", app, path, nil, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// Post 发起 POST 请求
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) Post(app, path string, data any, headers ...string) *gohttp.Result {
|
|
|
|
|
return c.Do("POST", app, path, data, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// Put 发起 PUT 请求
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) Put(app, path string, data any, headers ...string) *gohttp.Result {
|
|
|
|
|
return c.Do("PUT", app, path, data, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// Delete 发起 DELETE 请求
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) Delete(app, path string, data any, headers ...string) *gohttp.Result {
|
|
|
|
|
return c.Do("DELETE", app, path, data, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// Head 发起 HEAD 请求
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) Head(app, path string, headers ...string) *gohttp.Result {
|
|
|
|
|
return c.Do("HEAD", app, path, nil, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 21:11:46 +08:00
|
|
|
// Get 发起 GET 请求
|
|
|
|
|
func (d *Discoverer) Get(app, path string, headers ...string) *gohttp.Result {
|
|
|
|
|
return d.NewCaller(nil, nil).Get(app, path, headers...)
|
2026-05-09 17:31:39 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 21:11:46 +08:00
|
|
|
// Post 发起 POST 请求
|
|
|
|
|
func (d *Discoverer) Post(app, path string, data any, headers ...string) *gohttp.Result {
|
|
|
|
|
return d.NewCaller(nil, nil).Post(app, path, data, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Put 发起 PUT 请求
|
|
|
|
|
func (d *Discoverer) Put(app, path string, data any, headers ...string) *gohttp.Result {
|
|
|
|
|
return d.NewCaller(nil, nil).Put(app, path, data, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete 发起 DELETE 请求
|
|
|
|
|
func (d *Discoverer) Delete(app, path string, data any, headers ...string) *gohttp.Result {
|
|
|
|
|
return d.NewCaller(nil, nil).Delete(app, path, data, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Head 发起 HEAD 请求
|
|
|
|
|
func (d *Discoverer) Head(app, path string, headers ...string) *gohttp.Result {
|
|
|
|
|
return d.NewCaller(nil, nil).Head(app, path, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Do 发起通用请求
|
|
|
|
|
func (d *Discoverer) Do(method, app, path string, data any, headers ...string) *gohttp.Result {
|
|
|
|
|
return d.NewCaller(nil, nil).Do(method, app, path, data, headers...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Open 发起 WebSocket 连接
|
|
|
|
|
func (d *Discoverer) Open(app, path string, headers ...string) *websocket.Conn {
|
|
|
|
|
return d.NewCaller(nil, nil).Open(app, path, headers...)
|
2026-05-09 17:31:39 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// Do 发起通用请求
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) Do(method, app, path string, data any, headers ...string) *gohttp.Result {
|
|
|
|
|
r, _ := c.DoWithNode(method, app, "", path, data, headers...)
|
|
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// Open 发起 WebSocket 连接
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) Open(app, path string, headers ...string) *websocket.Conn {
|
|
|
|
|
r, _ := c.doWithNode(false, "WS", app, "", path, nil, headers...)
|
|
|
|
|
if v, ok := r.(*websocket.Conn); ok {
|
|
|
|
|
return v
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// DoWithNode 发起请求并返回结果及节点地址
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) DoWithNode(method, app, withNode, path string, data any, headers ...string) (*gohttp.Result, string) {
|
|
|
|
|
r, nodeAddr := c.doWithNode(false, method, app, withNode, path, data, headers...)
|
|
|
|
|
if v, ok := r.(*gohttp.Result); ok {
|
|
|
|
|
return v, nodeAddr
|
|
|
|
|
}
|
|
|
|
|
return nil, nodeAddr
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
// ManualDoWithNode 发起请求(手动处理响应)并返回结果及节点地址
|
2026-05-05 09:42:15 +08:00
|
|
|
func (c *Caller) ManualDoWithNode(method, app, withNode, path string, data any, headers ...string) (*gohttp.Result, string) {
|
|
|
|
|
r, nodeAddr := c.doWithNode(true, method, app, withNode, path, data, headers...)
|
|
|
|
|
if v, ok := r.(*gohttp.Result); ok {
|
|
|
|
|
return v, nodeAddr
|
|
|
|
|
}
|
|
|
|
|
return nil, nodeAddr
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
func (c *Caller) doWithNode(manual bool, method, app, withNode, path string, data any, headers ...string) (any, string) {
|
2026-05-05 09:42:15 +08:00
|
|
|
callerHeaders := make(map[string]string)
|
|
|
|
|
for i := 1; i < len(headers); i += 2 {
|
|
|
|
|
callerHeaders[headers[i-1]] = headers[i]
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 14:27:15 +08:00
|
|
|
if c.discoverer.isServer {
|
2026-05-09 21:11:46 +08:00
|
|
|
callerHeaders[HeaderFromApp] = c.discoverer.app
|
2026-05-05 14:27:15 +08:00
|
|
|
callerHeaders[HeaderFromNode] = c.discoverer.myAddr
|
2026-05-05 09:42:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callData := make(map[string]any)
|
|
|
|
|
if data != nil && !c.NoBody {
|
|
|
|
|
rv := cast.RealValue(reflect.ValueOf(data))
|
|
|
|
|
if rv.Kind() == reflect.Map || rv.Kind() == reflect.Struct {
|
|
|
|
|
cast.Convert(&callData, data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
appClient := AppClient{
|
2026-05-05 14:27:15 +08:00
|
|
|
discoverer: c.discoverer,
|
|
|
|
|
Logger: c.logger,
|
|
|
|
|
App: app,
|
|
|
|
|
Method: method,
|
|
|
|
|
Path: path,
|
|
|
|
|
Data: callData,
|
|
|
|
|
Headers: callerHeaders,
|
2026-05-05 09:42:15 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-05 14:27:15 +08:00
|
|
|
if c.discoverer.settedRoute != nil {
|
|
|
|
|
c.discoverer.settedRoute(&appClient, c.Request)
|
2026-05-05 09:42:15 +08:00
|
|
|
app = appClient.App
|
|
|
|
|
method = appClient.Method
|
|
|
|
|
path = appClient.Path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !appClient.CheckApp(app) {
|
|
|
|
|
return &gohttp.Result{Error: fmt.Errorf("app %s not found", app)}, ""
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 21:11:46 +08:00
|
|
|
callInfo, hasCallInfo := c.discoverer.getCallInfo(app)
|
|
|
|
|
if hasCallInfo && callInfo.Token != nil {
|
|
|
|
|
tk := callInfo.Token.Open()
|
|
|
|
|
callerHeaders["Access-Token"] = tk.String()
|
|
|
|
|
tk.Close()
|
2026-05-05 09:42:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
settedHeaders := make([]string, 0, len(callerHeaders)*2)
|
|
|
|
|
for k, v := range callerHeaders {
|
|
|
|
|
settedHeaders = append(settedHeaders, k, v)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
node := appClient.NextWithNode(app, withNode, c.Request)
|
|
|
|
|
if node == nil {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
node.UsedTimes.Add(1)
|
2026-05-09 21:11:46 +08:00
|
|
|
tracker := timer.Start()
|
2026-05-05 09:42:15 +08:00
|
|
|
scheme := "http"
|
2026-05-09 21:11:46 +08:00
|
|
|
if hasCallInfo && callInfo.SSL {
|
2026-05-05 09:42:15 +08:00
|
|
|
scheme = "https"
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 21:11:46 +08:00
|
|
|
timeout := 10 * time.Second
|
|
|
|
|
h2c := false
|
|
|
|
|
if hasCallInfo {
|
|
|
|
|
if callInfo.Timeout > 0 {
|
|
|
|
|
timeout = callInfo.Timeout
|
|
|
|
|
}
|
|
|
|
|
h2c = callInfo.Http2 && !callInfo.SSL
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hc := c.discoverer.getHttpClient(app, timeout, h2c)
|
2026-05-05 09:42:15 +08:00
|
|
|
hc.NoBody = c.NoBody
|
|
|
|
|
|
|
|
|
|
var res *gohttp.Result
|
|
|
|
|
var wsConn *websocket.Conn
|
|
|
|
|
|
|
|
|
|
url := fmt.Sprintf("%s://%s%s", scheme, node.Addr, path)
|
|
|
|
|
|
|
|
|
|
if strings.ToUpper(method) == "WS" {
|
|
|
|
|
dialer := websocket.DefaultDialer
|
|
|
|
|
h := http.Header{}
|
|
|
|
|
for i := 1; i < len(settedHeaders); i += 2 {
|
|
|
|
|
h.Set(settedHeaders[i-1], settedHeaders[i])
|
|
|
|
|
}
|
|
|
|
|
if scheme == "https" {
|
|
|
|
|
scheme = "wss"
|
|
|
|
|
} else {
|
|
|
|
|
scheme = "ws"
|
|
|
|
|
}
|
|
|
|
|
wsUrl := fmt.Sprintf("%s://%s%s", scheme, node.Addr, path)
|
|
|
|
|
conn, resp, err := dialer.Dial(wsUrl, h)
|
|
|
|
|
wsConn = conn
|
|
|
|
|
res = &gohttp.Result{Error: err, Response: resp}
|
|
|
|
|
} else {
|
|
|
|
|
if c.Request != nil {
|
2026-05-05 13:59:03 +08:00
|
|
|
if manual {
|
2026-05-05 09:42:15 +08:00
|
|
|
res = hc.ManualDoByRequest(c.Request, method, url, data, settedHeaders...)
|
|
|
|
|
} else {
|
|
|
|
|
res = hc.DoByRequest(c.Request, method, url, data, settedHeaders...)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2026-05-05 13:59:03 +08:00
|
|
|
if manual {
|
2026-05-05 09:42:15 +08:00
|
|
|
res = hc.ManualDo(method, url, data, settedHeaders...)
|
|
|
|
|
} else {
|
|
|
|
|
res = hc.Do(method, url, data, settedHeaders...)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 21:11:46 +08:00
|
|
|
responseTime := tracker.Record("call")
|
2026-05-05 13:59:03 +08:00
|
|
|
usedTimeMs := float32(responseTime.Nanoseconds()) / 1e6
|
2026-05-05 14:27:15 +08:00
|
|
|
c.discoverer.settedLoadBalancer.Response(&appClient, node, res.Error, res.Response, responseTime)
|
2026-05-05 09:42:15 +08:00
|
|
|
|
|
|
|
|
if res.Error != nil || (res.Response != nil && res.Response.StatusCode >= 502 && res.Response.StatusCode <= 504) {
|
2026-05-05 13:59:03 +08:00
|
|
|
node.FailedTimes.Add(1)
|
2026-05-05 09:42:15 +08:00
|
|
|
errStr := ""
|
|
|
|
|
if res.Error != nil {
|
|
|
|
|
errStr = res.Error.Error()
|
|
|
|
|
} else {
|
|
|
|
|
errStr = res.Response.Status
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
c.logError(errStr, "app", app, "node", node.Addr, "path", path, "attempts", appClient.attempts)
|
|
|
|
|
appClient.Log(node.Addr, usedTimeMs, fmt.Errorf("%s", errStr))
|
2026-05-05 09:42:15 +08:00
|
|
|
|
2026-05-09 21:11:46 +08:00
|
|
|
if node.FailedTimes.Load() >= int32(c.discoverer.config.CallRetryTimes) {
|
2026-05-05 14:27:15 +08:00
|
|
|
c.discoverer.logError("node isolated locally due to high failures", "app", app, "node", node.Addr)
|
2026-05-05 09:42:15 +08:00
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:59:03 +08:00
|
|
|
node.FailedTimes.Store(0)
|
|
|
|
|
appClient.Log(node.Addr, usedTimeMs, nil)
|
2026-05-05 09:42:15 +08:00
|
|
|
if strings.ToUpper(method) == "WS" {
|
|
|
|
|
return wsConn, node.Addr
|
|
|
|
|
}
|
|
|
|
|
return res, node.Addr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &gohttp.Result{Error: fmt.Errorf("all nodes failed for %s %s", app, path)}, ""
|
|
|
|
|
}
|
2026-05-09 21:11:46 +08:00
|
|
|
|