2026-05-08 07:27:06 +08:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
2026-05-12 23:53:16 +08:00
|
|
|
"apigo.cc/go/config"
|
2026-05-09 17:20:32 +08:00
|
|
|
"apigo.cc/go/discover"
|
2026-05-08 07:27:06 +08:00
|
|
|
"apigo.cc/go/log"
|
2026-05-10 12:44:25 +08:00
|
|
|
"apigo.cc/go/redis"
|
2026-05-09 22:51:14 +08:00
|
|
|
"apigo.cc/go/safe"
|
2026-05-12 23:18:31 +08:00
|
|
|
"apigo.cc/go/starter"
|
2026-06-05 11:38:44 +08:00
|
|
|
"apigo.cc/go/watch"
|
2026-05-08 07:27:06 +08:00
|
|
|
"context"
|
2026-05-09 17:20:32 +08:00
|
|
|
"fmt"
|
|
|
|
|
"golang.org/x/net/http2"
|
|
|
|
|
"golang.org/x/net/http2/h2c"
|
2026-05-08 07:27:06 +08:00
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
2026-06-04 18:16:46 +08:00
|
|
|
"path/filepath"
|
|
|
|
|
"reflect"
|
|
|
|
|
"sort"
|
2026-05-09 17:20:32 +08:00
|
|
|
"strings"
|
2026-06-04 18:16:46 +08:00
|
|
|
"sync"
|
2026-05-08 07:27:06 +08:00
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-06 08:59:14 +08:00
|
|
|
type staticType struct {
|
|
|
|
|
path string
|
|
|
|
|
rootPath *string
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
type webServer struct {
|
|
|
|
|
Config ServiceConfig
|
2026-05-09 17:20:32 +08:00
|
|
|
server *http.Server
|
|
|
|
|
listener net.Listener
|
|
|
|
|
Addr string
|
|
|
|
|
useDiscover bool
|
2026-05-09 21:11:50 +08:00
|
|
|
discoverer *discover.Discoverer
|
2026-05-12 23:53:16 +08:00
|
|
|
logger *log.Logger
|
2026-06-04 18:16:46 +08:00
|
|
|
|
|
|
|
|
// 运行时状态
|
|
|
|
|
serverId string
|
|
|
|
|
serverAddr string
|
|
|
|
|
running bool
|
|
|
|
|
|
|
|
|
|
// Web 服务注册 (按 Host 隔离)
|
|
|
|
|
webServices map[string]map[string]*webServiceType
|
|
|
|
|
regexWebServices map[string][]*webServiceType
|
|
|
|
|
webServicesLock sync.RWMutex
|
|
|
|
|
webServicesList []*webServiceType
|
|
|
|
|
|
|
|
|
|
websocketServices map[string]map[string]*websocketServiceType
|
|
|
|
|
websocketServicesLock sync.RWMutex
|
|
|
|
|
websocketServicesList []*websocketServiceType
|
|
|
|
|
|
|
|
|
|
// 路由策略 (按 Host 隔离)
|
|
|
|
|
hostRewrites map[string][]*rewriteType
|
|
|
|
|
hostProxies map[string][]*proxyType
|
|
|
|
|
|
|
|
|
|
codeProxies map[string][]*proxyType
|
|
|
|
|
fileProxies map[string][]*proxyType
|
|
|
|
|
dynamicProxies map[string][]*proxyType
|
|
|
|
|
|
|
|
|
|
codeRewrites map[string][]*rewriteType
|
|
|
|
|
fileRewrites map[string][]*rewriteType
|
|
|
|
|
dynamicRewrites map[string][]*rewriteType
|
|
|
|
|
|
|
|
|
|
hostPoliciesLock sync.RWMutex
|
|
|
|
|
|
|
|
|
|
// 静态文件服务
|
|
|
|
|
statics map[string]*string
|
|
|
|
|
staticsByHost map[string]map[string]*string
|
|
|
|
|
codeStatics map[string]map[string]*string
|
|
|
|
|
fileStatics map[string]map[string]*string
|
|
|
|
|
dynamicStatics map[string]map[string]*string
|
2026-06-06 08:59:14 +08:00
|
|
|
hostStatics map[string][]*staticType
|
2026-06-04 18:16:46 +08:00
|
|
|
staticsByHostLock sync.RWMutex
|
|
|
|
|
|
|
|
|
|
// 过滤器与拦截器
|
|
|
|
|
inFilters []func(*map[string]any, *Request, *Response, *log.Logger) any
|
|
|
|
|
outFilters []func(map[string]any, *Request, *Response, any, *log.Logger) (any, bool)
|
|
|
|
|
errorHandle func(any, *Request, *Response) any
|
|
|
|
|
webAuthChecker func(int, *log.Logger, *string, map[string]any, *Request, *Response, *WebServiceOptions) (pass bool, object any)
|
|
|
|
|
webAuthCheckers map[int]func(int, *log.Logger, *string, map[string]any, *Request, *Response, *WebServiceOptions) (pass bool, object any)
|
|
|
|
|
|
|
|
|
|
// 注入点
|
|
|
|
|
injectObjects map[reflect.Type]any
|
|
|
|
|
injectFunctions map[reflect.Type]func() any
|
|
|
|
|
|
|
|
|
|
// 客户端标识
|
|
|
|
|
usedDeviceIdKey string
|
|
|
|
|
usedClientAppKey string
|
|
|
|
|
usedSessionIdKey string
|
|
|
|
|
sessionIdMaker func() string
|
2026-06-05 11:31:33 +08:00
|
|
|
|
|
|
|
|
// 停机钩子
|
|
|
|
|
shutdownHooks []func()
|
|
|
|
|
shutdownHooksLock sync.Mutex
|
|
|
|
|
|
|
|
|
|
// 性能优化:标记是否有输出过滤器
|
|
|
|
|
hasOutFilter bool
|
2026-06-05 11:38:44 +08:00
|
|
|
|
|
|
|
|
// Web 开发模式配置
|
|
|
|
|
webDevEnabled bool
|
|
|
|
|
webDevConfig watch.Config
|
2026-06-04 18:16:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DefaultServer 全局单例服务实例
|
|
|
|
|
var DefaultServer = newWebServer()
|
|
|
|
|
|
|
|
|
|
// Config 全局配置对象 (指向 DefaultServer.Config)
|
|
|
|
|
var Config = &DefaultServer.Config
|
|
|
|
|
|
|
|
|
|
func newWebServer() *webServer {
|
|
|
|
|
ws := &webServer{
|
|
|
|
|
webServices: make(map[string]map[string]*webServiceType),
|
|
|
|
|
regexWebServices: make(map[string][]*webServiceType),
|
|
|
|
|
webServicesList: make([]*webServiceType, 0),
|
|
|
|
|
websocketServices: make(map[string]map[string]*websocketServiceType),
|
|
|
|
|
websocketServicesList: make([]*websocketServiceType, 0),
|
|
|
|
|
hostRewrites: make(map[string][]*rewriteType),
|
|
|
|
|
hostProxies: make(map[string][]*proxyType),
|
|
|
|
|
codeProxies: make(map[string][]*proxyType),
|
|
|
|
|
fileProxies: make(map[string][]*proxyType),
|
|
|
|
|
dynamicProxies: make(map[string][]*proxyType),
|
|
|
|
|
codeRewrites: make(map[string][]*rewriteType),
|
|
|
|
|
fileRewrites: make(map[string][]*rewriteType),
|
|
|
|
|
dynamicRewrites: make(map[string][]*rewriteType),
|
|
|
|
|
statics: make(map[string]*string),
|
|
|
|
|
staticsByHost: make(map[string]map[string]*string),
|
|
|
|
|
codeStatics: make(map[string]map[string]*string),
|
|
|
|
|
fileStatics: make(map[string]map[string]*string),
|
|
|
|
|
dynamicStatics: make(map[string]map[string]*string),
|
2026-06-06 08:59:14 +08:00
|
|
|
hostStatics: make(map[string][]*staticType),
|
2026-06-04 18:16:46 +08:00
|
|
|
webAuthCheckers: make(map[int]func(int, *log.Logger, *string, map[string]any, *Request, *Response, *WebServiceOptions) (pass bool, object any)),
|
|
|
|
|
injectObjects: make(map[reflect.Type]any),
|
|
|
|
|
injectFunctions: make(map[reflect.Type]func() any),
|
|
|
|
|
}
|
|
|
|
|
return ws
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetDiscovererForTest 提供给测试用例使用的后门方法,用于模拟断开或重置服务发现
|
|
|
|
|
func SetDiscovererForTest(d *discover.Discoverer) {
|
|
|
|
|
DefaultServer.discoverer = d
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplyConfig 将 ServiceConfig 中的路由策略应用到内部的文件级策略中
|
|
|
|
|
func (ws *webServer) ApplyConfig() {
|
|
|
|
|
ws.hostPoliciesLock.Lock()
|
|
|
|
|
defer ws.hostPoliciesLock.Unlock()
|
|
|
|
|
|
|
|
|
|
// 1. Proxies KV 解析
|
|
|
|
|
ws.fileProxies = make(map[string][]*proxyType)
|
|
|
|
|
for host, kv := range ws.Config.Proxies {
|
|
|
|
|
h := host
|
|
|
|
|
if h == "*" {
|
|
|
|
|
h = ""
|
|
|
|
|
}
|
|
|
|
|
rules := make([]*proxyType, 0, len(kv))
|
|
|
|
|
for path, val := range kv {
|
|
|
|
|
if to, ok := val.(string); ok {
|
|
|
|
|
rules = append(rules, parseProxyRule(0, path, "", "", to))
|
|
|
|
|
} else {
|
|
|
|
|
// 对象模式
|
|
|
|
|
m := make(map[string]any)
|
|
|
|
|
if tm, ok := val.(map[string]any); ok {
|
|
|
|
|
m = tm
|
|
|
|
|
}
|
|
|
|
|
rules = append(rules, parseProxyRule(
|
|
|
|
|
int(reflect.ValueOf(m["Auth"]).Int()), // Simplified
|
|
|
|
|
path,
|
|
|
|
|
fmt.Sprint(m["ToApp"]),
|
|
|
|
|
fmt.Sprint(m["ToPath"]),
|
|
|
|
|
fmt.Sprint(m["To"]),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ws.fileProxies[h] = rules
|
|
|
|
|
ws.rebuildProxiesUnderLock(h)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Rewrites KV 解析
|
|
|
|
|
ws.fileRewrites = make(map[string][]*rewriteType)
|
|
|
|
|
for host, kv := range ws.Config.Rewrites {
|
|
|
|
|
h := host
|
|
|
|
|
if h == "*" {
|
|
|
|
|
h = ""
|
|
|
|
|
}
|
|
|
|
|
rules := make([]*rewriteType, 0, len(kv))
|
|
|
|
|
for path, val := range kv {
|
|
|
|
|
if to, ok := val.(string); ok {
|
|
|
|
|
rules = append(rules, parseRewriteRule(path, "", to))
|
|
|
|
|
} else {
|
|
|
|
|
m := make(map[string]any)
|
|
|
|
|
if tm, ok := val.(map[string]any); ok {
|
|
|
|
|
m = tm
|
|
|
|
|
}
|
|
|
|
|
rules = append(rules, parseRewriteRule(
|
|
|
|
|
path,
|
|
|
|
|
fmt.Sprint(m["ToPath"]),
|
|
|
|
|
fmt.Sprint(m["To"]),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ws.fileRewrites[h] = rules
|
|
|
|
|
ws.rebuildRewritesUnderLock(h)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ws.staticsByHostLock.Lock()
|
|
|
|
|
defer ws.staticsByHostLock.Unlock()
|
|
|
|
|
ws.fileStatics = make(map[string]map[string]*string)
|
|
|
|
|
|
|
|
|
|
for host, config := range ws.Config.Statics {
|
|
|
|
|
h := host
|
|
|
|
|
if h == "*" {
|
|
|
|
|
h = ""
|
|
|
|
|
}
|
|
|
|
|
newStatics := make(map[string]*string, len(config))
|
|
|
|
|
for path, rootPath := range config {
|
|
|
|
|
rp := rootPath
|
|
|
|
|
if !filepath.IsAbs(rp) {
|
|
|
|
|
if absPath, err := filepath.Abs(rp); err == nil {
|
|
|
|
|
rp = absPath
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
newStatics[path] = &rp
|
|
|
|
|
}
|
|
|
|
|
ws.fileStatics[h] = newStatics
|
|
|
|
|
ws.rebuildStaticsUnderLock(h)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 始终重新构建默认 Host 的静态路由,以合并代码定义的路由
|
|
|
|
|
ws.rebuildStaticsUnderLock("")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ws *webServer) rebuildProxiesUnderLock(host string) {
|
|
|
|
|
combined := make([]*proxyType, 0)
|
|
|
|
|
combined = append(combined, ws.codeProxies[host]...)
|
|
|
|
|
combined = append(combined, ws.fileProxies[host]...)
|
|
|
|
|
combined = append(combined, ws.dynamicProxies[host]...)
|
|
|
|
|
|
|
|
|
|
sort.Slice(combined, func(i, j int) bool {
|
|
|
|
|
return len(combined[i].fromPath) > len(combined[j].fromPath)
|
|
|
|
|
})
|
|
|
|
|
ws.hostProxies[host] = combined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ws *webServer) rebuildRewritesUnderLock(host string) {
|
|
|
|
|
combined := make([]*rewriteType, 0)
|
|
|
|
|
combined = append(combined, ws.codeRewrites[host]...)
|
|
|
|
|
combined = append(combined, ws.fileRewrites[host]...)
|
|
|
|
|
combined = append(combined, ws.dynamicRewrites[host]...)
|
|
|
|
|
|
|
|
|
|
sort.Slice(combined, func(i, j int) bool {
|
|
|
|
|
return len(combined[i].fromPath) > len(combined[j].fromPath)
|
|
|
|
|
})
|
|
|
|
|
ws.hostRewrites[host] = combined
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
func (ws *webServer) rebuildStaticsUnderLock(host string) {
|
|
|
|
|
combined := make(map[string]*string)
|
|
|
|
|
for k, v := range ws.codeStatics[host] {
|
|
|
|
|
combined[k] = v
|
|
|
|
|
}
|
|
|
|
|
for k, v := range ws.fileStatics[host] {
|
|
|
|
|
combined[k] = v
|
|
|
|
|
}
|
|
|
|
|
for k, v := range ws.dynamicStatics[host] {
|
|
|
|
|
combined[k] = v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if host == "" {
|
|
|
|
|
ws.statics = combined
|
|
|
|
|
} else {
|
|
|
|
|
ws.staticsByHost[host] = combined
|
|
|
|
|
}
|
2026-06-06 08:59:14 +08:00
|
|
|
|
|
|
|
|
// 构造有序的静态路由列表 (按路径长度降序排列,实现最长匹配)
|
|
|
|
|
sorted := make([]*staticType, 0, len(combined))
|
|
|
|
|
for k, v := range combined {
|
|
|
|
|
sorted = append(sorted, &staticType{path: k, rootPath: v})
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
|
|
|
return len(sorted[i].path) > len(sorted[j].path)
|
|
|
|
|
})
|
|
|
|
|
ws.hostStatics[host] = sorted
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
// Start 启动服务,实现 starter.Service 接口
|
2026-06-04 18:16:46 +08:00
|
|
|
func (ws *webServer) Start(ctx context.Context, logger *log.Logger) error {
|
2026-05-12 23:53:16 +08:00
|
|
|
if logger == nil {
|
|
|
|
|
logger = log.DefaultLogger
|
|
|
|
|
}
|
|
|
|
|
ws.logger = logger
|
2026-06-04 13:34:40 +08:00
|
|
|
|
|
|
|
|
// 初始加载配置
|
2026-06-04 18:16:46 +08:00
|
|
|
if err := config.Load(&ws.Config, "service"); err != nil {
|
2026-06-04 13:34:40 +08:00
|
|
|
logger.Error("failed to load config during start", "error", err.Error())
|
|
|
|
|
}
|
2026-06-04 18:16:46 +08:00
|
|
|
ws.ApplyConfig()
|
|
|
|
|
|
|
|
|
|
listenStr := ws.Config.Listen
|
2026-05-12 23:18:31 +08:00
|
|
|
ws.useDiscover = false
|
2026-05-09 17:20:32 +08:00
|
|
|
|
|
|
|
|
if listenStr == "" {
|
|
|
|
|
listenStr = ":0,h2c"
|
2026-05-12 23:18:31 +08:00
|
|
|
ws.useDiscover = true
|
2026-05-09 17:20:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
part := strings.Split(listenStr, "|")[0]
|
|
|
|
|
addr, opts, _ := strings.Cut(part, ",")
|
|
|
|
|
|
2026-05-13 12:03:00 +08:00
|
|
|
protocol := ""
|
2026-05-09 17:20:32 +08:00
|
|
|
for _, opt := range strings.Split(opts, ",") {
|
|
|
|
|
opt = strings.ToLower(strings.TrimSpace(opt))
|
2026-05-13 12:03:00 +08:00
|
|
|
if opt == "h2c" || opt == "h2" || opt == "http" || opt == "https" {
|
2026-05-09 17:20:32 +08:00
|
|
|
protocol = opt
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-04 18:16:46 +08:00
|
|
|
|
2026-05-13 12:03:00 +08:00
|
|
|
if protocol == "" {
|
2026-06-04 18:16:46 +08:00
|
|
|
protocol = "http"
|
2026-05-13 12:03:00 +08:00
|
|
|
}
|
2026-05-09 17:20:32 +08:00
|
|
|
|
|
|
|
|
if !strings.Contains(addr, ":") {
|
|
|
|
|
addr = ":" + addr
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 11:38:44 +08:00
|
|
|
if ws.webDevEnabled {
|
|
|
|
|
ws.initWebDev(logger)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
appName := ws.Config.App
|
2026-06-04 13:34:40 +08:00
|
|
|
if appName == "" {
|
|
|
|
|
appName = GetDefaultName()
|
2026-06-04 18:16:46 +08:00
|
|
|
ws.Config.App = appName
|
2026-06-04 13:34:40 +08:00
|
|
|
}
|
2026-06-04 18:16:46 +08:00
|
|
|
if appName != "" || ws.Config.Register != "" {
|
2026-05-12 23:18:31 +08:00
|
|
|
ws.useDiscover = true
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
ws.serverId = IDMaker.Get8Bytes4KPerSecond()
|
2026-05-10 12:44:25 +08:00
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
if ws.Config.IdServer != "" {
|
|
|
|
|
rd := redis.GetRedis(ws.Config.IdServer, log.New(ws.serverId))
|
2026-05-10 12:44:25 +08:00
|
|
|
if rd.Error == nil {
|
|
|
|
|
IDMaker = redis.NewIDMaker(rd)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 17:20:32 +08:00
|
|
|
listener, err := net.Listen("tcp", addr)
|
2026-05-08 07:27:06 +08:00
|
|
|
if err != nil {
|
2026-05-12 23:18:31 +08:00
|
|
|
return fmt.Errorf("failed to listen on %s: %w", addr, err)
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
ws.listener = listener
|
|
|
|
|
ws.Addr = listener.Addr().String()
|
2026-06-04 18:16:46 +08:00
|
|
|
ws.serverAddr = ws.Addr
|
2026-05-08 07:27:06 +08:00
|
|
|
|
2026-05-09 17:20:32 +08:00
|
|
|
if addr == ":0" || strings.HasSuffix(addr, ":0") {
|
2026-05-12 23:18:31 +08:00
|
|
|
ws.useDiscover = true
|
2026-05-09 17:20:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h2s := &http2.Server{}
|
2026-06-04 18:16:46 +08:00
|
|
|
var handler http.Handler = &RouteHandler{ws: ws}
|
2026-05-09 17:20:32 +08:00
|
|
|
if protocol == "h2c" {
|
|
|
|
|
handler = h2c.NewHandler(handler, h2s)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
ws.server = &http.Server{
|
2026-05-10 12:44:25 +08:00
|
|
|
Handler: handler,
|
2026-06-04 18:16:46 +08:00
|
|
|
ReadTimeout: time.Duration(ws.Config.ReadTimeout) * time.Millisecond,
|
|
|
|
|
ReadHeaderTimeout: time.Duration(ws.Config.ReadHeaderTimeout) * time.Millisecond,
|
|
|
|
|
WriteTimeout: time.Duration(ws.Config.WriteTimeout) * time.Millisecond,
|
|
|
|
|
IdleTimeout: time.Duration(ws.Config.IdleTimeout) * time.Millisecond,
|
|
|
|
|
MaxHeaderBytes: ws.Config.MaxHeaderBytes,
|
2026-05-09 17:20:32 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
if ws.useDiscover {
|
|
|
|
|
_, port, _ := net.SplitHostPort(ws.Addr)
|
2026-05-09 17:20:32 +08:00
|
|
|
ip := GetServerIp()
|
|
|
|
|
discoverAddr := fmt.Sprintf("%s:%s", ip, port)
|
|
|
|
|
|
2026-05-09 22:51:14 +08:00
|
|
|
discConf := discover.Config{
|
2026-06-04 18:16:46 +08:00
|
|
|
Weight: ws.Config.Weight,
|
|
|
|
|
CallRetryTimes: 10,
|
2026-05-09 22:51:14 +08:00
|
|
|
Calls: make(map[string]discover.CallConfig),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if discConf.Weight <= 0 {
|
|
|
|
|
discConf.Weight = 100
|
|
|
|
|
}
|
2026-05-09 21:11:50 +08:00
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
for name, call := range ws.Config.Calls {
|
2026-05-09 22:51:14 +08:00
|
|
|
dc := discover.CallConfig{
|
|
|
|
|
Http2: call.Http2,
|
|
|
|
|
SSL: call.SSL,
|
|
|
|
|
}
|
|
|
|
|
if call.Timeout > 0 {
|
|
|
|
|
dc.Timeout = time.Duration(call.Timeout) * time.Millisecond
|
2026-06-04 18:16:46 +08:00
|
|
|
} else if ws.Config.RedirectTimeout > 0 {
|
|
|
|
|
dc.Timeout = time.Duration(ws.Config.RedirectTimeout) * time.Millisecond
|
2026-05-09 22:51:14 +08:00
|
|
|
}
|
|
|
|
|
if call.Token != "" {
|
|
|
|
|
dc.Token = safe.NewSafeBuf([]byte(call.Token))
|
|
|
|
|
}
|
|
|
|
|
discConf.Calls[name] = dc
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
registry := ws.Config.Register
|
2026-05-09 22:51:14 +08:00
|
|
|
if registry == "" {
|
|
|
|
|
registry = os.Getenv("DISCOVER_REGISTRY")
|
|
|
|
|
}
|
2026-05-09 21:11:50 +08:00
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
if registry != "" {
|
|
|
|
|
ws.discoverer = discover.Start(registry, appName, discoverAddr, logger, discConf)
|
|
|
|
|
if ws.discoverer != nil {
|
|
|
|
|
logger.Info("discover registered", "app", appName, "addr", discoverAddr)
|
|
|
|
|
}
|
2026-05-09 17:20:32 +08:00
|
|
|
}
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
errChan := make(chan error, 1)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
|
|
|
|
go func() {
|
2026-06-04 21:33:14 +08:00
|
|
|
logger.Info("starting listener", "addr", ws.Addr, "proto", protocol)
|
2026-06-04 18:16:46 +08:00
|
|
|
ws.running = true
|
|
|
|
|
if err := ws.server.Serve(ws.listener); err != nil && err != http.ErrServerClosed {
|
2026-05-12 23:18:31 +08:00
|
|
|
errChan <- err
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
2026-05-12 23:18:31 +08:00
|
|
|
close(errChan)
|
2026-05-08 07:27:06 +08:00
|
|
|
}()
|
2026-05-12 23:18:31 +08:00
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case err := <-errChan:
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
case <-time.After(100 * time.Millisecond):
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
// Stop 停止服务,实现 starter.Service 接口
|
2026-06-04 18:16:46 +08:00
|
|
|
func (ws *webServer) Stop(ctx context.Context) error {
|
|
|
|
|
ws.running = false
|
2026-06-05 11:31:33 +08:00
|
|
|
|
|
|
|
|
// 执行停机钩子 (反序)
|
|
|
|
|
ws.shutdownHooksLock.Lock()
|
|
|
|
|
for i := len(ws.shutdownHooks) - 1; i >= 0; i-- {
|
|
|
|
|
ws.shutdownHooks[i]()
|
|
|
|
|
}
|
|
|
|
|
ws.shutdownHooks = nil
|
|
|
|
|
ws.shutdownHooksLock.Unlock()
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
if ws.discoverer != nil {
|
|
|
|
|
ws.discoverer.Stop()
|
|
|
|
|
}
|
|
|
|
|
if ws.server != nil {
|
|
|
|
|
if err := ws.server.Shutdown(ctx); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 11:11:13 +08:00
|
|
|
// Status 检查服务健康状态,实现 starter.Service 接口
|
2026-06-04 18:16:46 +08:00
|
|
|
func (ws *webServer) Status() (string, error) {
|
|
|
|
|
if ws.server == nil || !ws.running {
|
2026-05-13 11:11:13 +08:00
|
|
|
return "", fmt.Errorf("server is not running")
|
2026-05-09 17:20:32 +08:00
|
|
|
}
|
2026-05-13 11:11:13 +08:00
|
|
|
return ws.Addr, nil
|
2026-05-12 23:18:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reload 实现配置重新加载,实现 starter.Reloader 接口
|
2026-06-04 18:16:46 +08:00
|
|
|
func (ws *webServer) Reload() error {
|
2026-05-12 23:53:16 +08:00
|
|
|
logger := ws.logger
|
|
|
|
|
if logger == nil {
|
|
|
|
|
logger = log.DefaultLogger
|
|
|
|
|
}
|
|
|
|
|
logger.Info("reloading configurations...")
|
2026-06-04 18:16:46 +08:00
|
|
|
|
|
|
|
|
if err := config.Load(&ws.Config, "service"); err != nil {
|
2026-05-12 23:53:16 +08:00
|
|
|
logger.Error("failed to load config during reload", "error", err.Error())
|
|
|
|
|
}
|
2026-06-04 18:16:46 +08:00
|
|
|
ws.ApplyConfig()
|
|
|
|
|
|
|
|
|
|
return ws.triggerReload()
|
2026-05-12 23:18:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AsyncServer 兼容旧版异步服务实例
|
|
|
|
|
type AsyncServer struct {
|
2026-06-04 18:16:46 +08:00
|
|
|
*webServer
|
2026-05-12 23:18:31 +08:00
|
|
|
}
|
2026-05-09 17:20:32 +08:00
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
// Stop 兼容旧版的无参数停止方法
|
|
|
|
|
func (as *AsyncServer) Stop() {
|
2026-06-04 18:16:46 +08:00
|
|
|
stopTimeout := time.Duration(as.Config.StopTimeout) * time.Millisecond
|
2026-05-10 22:40:31 +08:00
|
|
|
if stopTimeout <= 0 {
|
|
|
|
|
stopTimeout = 5 * time.Second
|
|
|
|
|
}
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), stopTimeout)
|
2026-05-08 07:27:06 +08:00
|
|
|
defer cancel()
|
2026-06-04 18:16:46 +08:00
|
|
|
_ = as.webServer.Stop(ctx)
|
2026-05-12 23:18:31 +08:00
|
|
|
}
|
2026-05-08 07:27:06 +08:00
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
// AsyncStart 兼容旧版的异步启动方法
|
|
|
|
|
func AsyncStart() *AsyncServer {
|
2026-06-04 18:16:46 +08:00
|
|
|
_ = DefaultServer.Start(context.Background(), log.DefaultLogger)
|
|
|
|
|
return &AsyncServer{webServer: DefaultServer}
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
// Wait 等待服务结束 (兼容旧版,直接阻塞)
|
2026-05-08 07:27:06 +08:00
|
|
|
func (as *AsyncServer) Wait() {
|
2026-05-12 23:18:31 +08:00
|
|
|
select {}
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 18:16:46 +08:00
|
|
|
var startOnce sync.Once
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
// Start 兼容旧版的同步启动方法 (通过内部注册 starter 实现)
|
2026-05-08 07:27:06 +08:00
|
|
|
func Start() {
|
2026-06-04 21:33:14 +08:00
|
|
|
if DefaultServer.running {
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-06-04 18:16:46 +08:00
|
|
|
startOnce.Do(func() {
|
|
|
|
|
stopTimeout := time.Duration(Config.StopTimeout) * time.Millisecond
|
|
|
|
|
if stopTimeout <= 0 {
|
|
|
|
|
stopTimeout = 5 * time.Second
|
|
|
|
|
}
|
|
|
|
|
starter.Register("web-server", DefaultServer, 100, 5*time.Second, stopTimeout)
|
|
|
|
|
starter.Run()
|
|
|
|
|
})
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|