Files
gf/net/ghttp/ghttp_server.go

702 lines
19 KiB
Go
Raw Permalink Normal View History

// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2017-12-29 16:03:30 +08:00
//
// 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.
2017-12-29 16:03:30 +08:00
2017-11-23 10:21:28 +08:00
package ghttp
import (
2019-06-19 09:06:52 +08:00
"bytes"
"context"
"fmt"
"net/http"
"os"
"runtime"
"strings"
"sync"
"time"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gset"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/container/gtype"
2021-11-13 23:23:55 +08:00
"github.com/gogf/gf/v2/debug/gdebug"
"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/ghttp/internal/graceful"
2022-03-19 17:58:21 +08:00
"github.com/gogf/gf/v2/net/ghttp/internal/swaggerui"
2022-05-24 18:53:10 +08:00
"github.com/gogf/gf/v2/net/goai"
2023-03-08 14:12:51 +08:00
"github.com/gogf/gf/v2/net/gsvc"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/os/gcache"
2023-03-08 14:12:51 +08:00
"github.com/gogf/gf/v2/os/gctx"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gproc"
2021-11-13 23:23:55 +08:00
"github.com/gogf/gf/v2/os/gsession"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/os/gtimer"
"github.com/gogf/gf/v2/text/gregex"
2021-11-13 23:23:55 +08:00
"github.com/gogf/gf/v2/text/gstr"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/util/gconv"
2017-11-23 10:21:28 +08:00
)
func init() {
2022-03-19 17:58:21 +08:00
// Initialize the method map.
for _, v := range strings.Split(supportedHttpMethods, ",") {
2019-06-19 09:06:52 +08:00
methodsMap[v] = struct{}{}
}
}
// serverProcessInit initializes some process configurations, which can only be done once.
2019-02-20 16:07:11 +08:00
func serverProcessInit() {
var ctx = context.TODO()
if !serverProcessInitialized.Cas(false, true) {
2019-06-19 09:06:52 +08:00
return
}
2022-03-19 17:58:21 +08:00
// This means it is a restart server. It should kill its parent before starting its listening,
// to avoid duplicated port listening in two processes.
if !genv.Get(adminActionRestartEnvKey).IsEmpty() {
if p, err := os.FindProcess(gproc.PPid()); err == nil {
if err = p.Kill(); err != nil {
intlog.Errorf(ctx, `%+v`, err)
}
if _, err = p.Wait(); err != nil {
intlog.Errorf(ctx, `%+v`, err)
}
2019-06-19 09:06:52 +08:00
} else {
glog.Error(ctx, err)
2019-06-19 09:06:52 +08:00
}
}
// Process message handler.
2022-03-19 17:58:21 +08:00
// It enabled only a graceful feature is enabled.
2019-06-19 09:06:52 +08:00
if gracefulEnabled {
intlog.Printf(ctx, "pid[%d]: graceful reload feature is enabled", gproc.Pid())
2019-06-19 09:06:52 +08:00
go handleProcessMessage()
} else {
intlog.Printf(ctx, "pid[%d]: graceful reload feature is disabled", gproc.Pid())
2019-06-19 09:06:52 +08:00
}
// It's an ugly calling for better initializing the main package path
// in source development environment. It is useful only be used in main goroutine.
2022-03-19 17:58:21 +08:00
// It fails to retrieve the main package path in asynchronous goroutines.
2019-06-19 09:06:52 +08:00
gfile.MainPkgPath()
2018-05-10 23:52:09 +08:00
}
// GetServer creates and returns a server instance using given name and default configurations.
// Note that the parameter `name` should be unique for different servers. It returns an existing
// server instance if given `name` is already existing in the server mapping.
refactor: interface{} to any and reflect.Ptr to reflect.Pointer (#4395) This pull request standardizes the use of the Go 1.18+ `any` type alias instead of `interface{}` throughout the codebase. The change improves code readability and aligns with modern Go best practices. The update touches many files, including core data structures, code generation templates, logging utilities, and test data, ensuring consistency across all usages. **Type alias migration to `any`:** * Replaced all instances of `interface{}` with `any` in core data structures such as `garray` and in generated model structs (e.g., `TableUser`, `User1`, `User2`) to modernize type usage. [[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31) [[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19) [[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18) [[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19) [[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19) [[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19) * Updated function signatures, method parameters, and return types from `interface{}` to `any` in various parts of the codebase, including code generation, service logic, and logging utilities (e.g., `mlog`). [[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55) [[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74) [[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73) [[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41) [[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107) [[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121) [[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112) [[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36) [[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74) [[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96) **Generated code and templates:** * Adjusted generated files and code generation templates to output `any` instead of `interface{}` for relevant struct fields and function signatures, ensuring that new code generation aligns with the updated convention. [[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19) [[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18) [[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19) [[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19) [[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19) [[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55) [[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73) [[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41) **Container and utility updates:** * Refactored the `garray` container implementation and related constructors/methods to use `[]any` instead of `[]interface{}`, along with corresponding function signatures. [[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31) [[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52) [[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62) [[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86) [[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97) [[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114) [[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124) [[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143) [[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167) These changes collectively modernize the codebase and prepare it for future Go developments by using the idiomatic `any` type.
2025-08-28 16:53:19 +08:00
func GetServer(name ...any) *Server {
serverName := DefaultServerName
2019-07-28 17:37:13 +08:00
if len(name) > 0 && name[0] != "" {
2019-09-04 19:23:19 +08:00
serverName = gconv.String(name[0])
2019-06-19 09:06:52 +08:00
}
feat(instance): migrate instance containers to type-safe generics (#4617) ### 变更说明 本次重构将项目中用于**实例管理的容器**从 `StrAnyMap`/`IntAnyMap` 迁移到类型安全的泛型实现 `KVMapWithChecker`,同时将相关的 `glist.List` 和 `gqueue.Queue` 替换为对应的泛型版本,以提高实例管理的类型安全性。并且减少原先代码中的大量类型断言,提高性能。 ### 前因 目前`goframe`中大量使用了包含`any`的容器,然后通过断言去转换类型,麻烦且影响性能,尤其是对`gdb/gredis/glog`等需要高频获取`instance`实例的组件影响较大。最近几个版本中gf完成了数据结构容器的泛型化改造,以及我最近解决了其中几个泛型容器对于`typed nil`过滤的问题,所以可以逐步迁移这些实例容器到泛型容器,减少断言优化性能 ### 主要改进 #### 1. 实例容器泛型化 以下模块的实例管理容器已迁移到泛型实现: **核心实例管理**: - `database/gdb`: 数据库实例容器 → `KVMap[string, DB]` - `database/gredis`: Redis 实例容器 → `KVMap[string, *Redis]` - `database/gredis`: Redis 配置容器 → `KVMap[string, *Config]` - `os/gcfg`: 配置实例容器 → `KVMap[string, *Config]` - `os/glog`: 日志实例容器 → `KVMap[string, *Logger]` - `os/gview`: 视图实例容器 → `KVMap[string, *View]` - `i18n/gi18n`: 国际化实例容器 → `KVMap[string, *Manager]` **网络服务实例**: - `net/ghttp`: HTTP 服务器容器 → `KVMap[string, *Server]` - `net/gtcp`: TCP 服务器容器 → `KVMap[any, *Server]` - `net/gudp`: UDP 服务器容器 → `KVMap[string, *Server]` **其他实例容器**: - `os/gres`: 资源实例容器 → `KVMap[string, *Resource]` - `os/gfpool`: 文件池容器 → `KVMap[string, *Pool]` - `os/gspath`: 路径搜索容器 → `KVMap[string, *SPath]` - `net/gtcp`: 连接池容器 → `KVMap[string, *gpool.Pool]` #### 2. 相关数据结构泛型化 - `os/gfsnotify`: 回调列表 → `TList[*Callback]`,事件队列 → `TQueue[*Event]` - `os/grpool`: 任务队列 → `TList[*localPoolItem]` - `os/gcache`: 事件队列 → `TList[*adapterMemoryEvent]` - `net/ghttp`: 解析项列表 → `TList[*HandlerItemParsed]` - `os/gproc`: 消息队列 → `TQueue[*MsgRequest]` - `os/gmlock`: 锁映射 → `KVMap[string, *sync.RWMutex]` ### 技术实现 1. **引入检查器函数**: 为每个实例容器添加 `checker` 函数用于空值检测 2. **消除类型断言**: 实例获取时无需 `v.(*Type)` 转换 3. **明确函数签名**: `GetOrSetFuncLock` 的回调从 `func() any` 改为 `func() T` ### 使用示例 #### 实例容器的变更 **变更前**: ```go // 旧的实例管理方式 var instances = gmap.NewStrAnyMap(true) func Instance(name string) *Logger { v := instances.GetOrSetFuncLock(name, func() any { return New() }) return v.(*Logger) // 需要类型断言 } ``` **变更后**: ```go // 新的泛型实例容器 var ( checker = func(v *Logger) bool { return v == nil } instances = gmap.NewKVMapWithChecker[string, *Logger](checker, true) ) func Instance(name string) *Logger { return instances.GetOrSetFuncLock(name, New) // 直接返回,无需断言 } ``` #### 队列容器的变更 **变更前**: ```go // 旧的队列方式 events := gqueue.New() events.Push(&Event{Path: "/tmp/file"}) if v := events.Pop(); v != nil { event := v.(*Event) // 需要类型断言 handleEvent(event) } ``` **变更后**: ```go // 新的泛型队列 events := gqueue.NewTQueue[*Event]() events.Push(&Event{Path: "/tmp/file"}) if event := events.Pop(); event != nil { handleEvent(event) // event 已是 *Event 类型 } ``` ### 收益 - ✅ **编译时类型安全**: 实例容器的类型错误在编译期捕获 - ✅ **消除运行时断言**: 避免类型断言带来的 panic 风险 - ✅ **提升代码可读性**: 实例管理逻辑更清晰 - ✅ **改善开发体验**: IDE 类型提示和代码补全更准确 ### 性能权衡 **编译时**: - 泛型实例化会增加编译时间和二进制体积 - 预估编译时间增加 5-15%,二进制体积增加约 1-2MB **运行时**: - 减少类型断言的反射开销 - 提升实例获取等热点路径的性能
2026-01-16 15:23:13 +08:00
return serverMapping.GetOrSetFuncLock(serverName, func() *Server {
2023-03-08 14:12:51 +08:00
s := &Server{
instance: serverName,
plugins: make([]Plugin, 0),
servers: make([]*graceful.Server, 0),
2023-03-08 14:12:51 +08:00
closeChan: make(chan struct{}, 10000),
serverCount: gtype.NewInt(),
statusHandlerMap: make(map[string][]HandlerFunc),
refactor: interface{} to any and reflect.Ptr to reflect.Pointer (#4395) This pull request standardizes the use of the Go 1.18+ `any` type alias instead of `interface{}` throughout the codebase. The change improves code readability and aligns with modern Go best practices. The update touches many files, including core data structures, code generation templates, logging utilities, and test data, ensuring consistency across all usages. **Type alias migration to `any`:** * Replaced all instances of `interface{}` with `any` in core data structures such as `garray` and in generated model structs (e.g., `TableUser`, `User1`, `User2`) to modernize type usage. [[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31) [[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19) [[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18) [[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19) [[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19) [[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19) * Updated function signatures, method parameters, and return types from `interface{}` to `any` in various parts of the codebase, including code generation, service logic, and logging utilities (e.g., `mlog`). [[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55) [[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74) [[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73) [[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41) [[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107) [[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121) [[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112) [[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36) [[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74) [[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96) **Generated code and templates:** * Adjusted generated files and code generation templates to output `any` instead of `interface{}` for relevant struct fields and function signatures, ensuring that new code generation aligns with the updated convention. [[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19) [[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18) [[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19) [[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19) [[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19) [[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55) [[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73) [[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41) **Container and utility updates:** * Refactored the `garray` container implementation and related constructors/methods to use `[]any` instead of `[]interface{}`, along with corresponding function signatures. [[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31) [[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52) [[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62) [[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86) [[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97) [[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114) [[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124) [[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143) [[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167) These changes collectively modernize the codebase and prepare it for future Go developments by using the idiomatic `any` type.
2025-08-28 16:53:19 +08:00
serveTree: make(map[string]any),
2023-03-08 14:12:51 +08:00
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
})
2017-12-07 14:57:16 +08:00
}
// 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 {
2023-03-08 14:12:51 +08:00
var ctx = gctx.GetInitCtx()
2021-09-27 21:27:24 +08:00
// Swagger UI.
if s.config.SwaggerPath != "" {
2021-10-13 22:28:49 +08:00
swaggerui.Init()
s.AddStaticPath(s.config.SwaggerPath, swaggerUIPackedPath)
s.BindHookHandler(s.config.SwaggerPath+"/*", HookBeforeServe, s.swaggerUI)
}
// OpenApi specification json producing handler.
if s.config.OpenApiPath != "" {
s.BindHandler(s.config.OpenApiPath, s.openapiSpec)
}
// Register group routes.
2021-09-27 21:27:24 +08:00
s.handlePreBindItems(ctx)
// Server process initialization, which can only be initialized once.
2019-06-19 09:06:52 +08:00
serverProcessInit()
// Server can only be run once.
2020-12-14 13:26:48 +08:00
if s.Status() == ServerStatusRunning {
return gerror.NewCode(gcode.CodeInvalidOperation, "server is already running")
2019-06-19 09:06:52 +08:00
}
// Logging path setting check.
if s.config.LogPath != "" && s.config.LogPath != s.config.Logger.GetPath() {
if err := s.config.Logger.SetPath(s.config.LogPath); err != nil {
return err
}
2019-06-19 09:06:52 +08:00
}
// Default session storage.
if s.config.SessionStorage == nil {
sessionStoragePath := ""
if s.config.SessionPath != "" {
sessionStoragePath = gfile.Join(s.config.SessionPath, s.config.Name)
if !gfile.Exists(sessionStoragePath) {
if err := gfile.Mkdir(sessionStoragePath); err != nil {
return gerror.Wrapf(err, `mkdir failed for "%s"`, sessionStoragePath)
}
}
}
s.config.SessionStorage = gsession.NewStorageFile(sessionStoragePath, s.config.SessionMaxAge)
}
// Initialize session manager when start running.
s.sessionManager = gsession.New(
s.config.SessionMaxAge,
s.config.SessionStorage,
)
// PProf feature.
if s.config.PProfEnabled {
s.EnablePProf(s.config.PProfPattern)
}
// Default HTTP handler.
2019-06-19 09:06:52 +08:00
if s.config.Handler == nil {
s.config.Handler = s.ServeHTTP
2019-06-19 09:06:52 +08:00
}
// Install external plugins.
for _, p := range s.plugins {
if err := p.Install(s); err != nil {
s.Logger().Fatalf(ctx, `%+v`, err)
}
}
// Check the group routes again for internally registered routes.
2021-09-27 21:27:24 +08:00
s.handlePreBindItems(ctx)
2022-03-19 17:58:21 +08:00
// If there's no route registered and no static service enabled,
// it then returns an error of invalid usage of server.
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
2021-07-20 23:02:02 +08:00
return gerror.NewCode(
gcode.CodeInvalidOperation,
2021-07-20 23:02:02 +08:00
`there's no route set or static feature enabled, did you forget import the router?`,
)
}
// ================================================================================================
// Start the HTTP server.
// ================================================================================================
2019-06-19 09:06:52 +08:00
reloaded := false
fdMapStr := genv.Get(adminActionReloadEnvKey).String()
2019-06-19 09:06:52 +08:00
if len(fdMapStr) > 0 {
sfm := bufferToServerFdMap([]byte(fdMapStr))
if v, ok := sfm[s.config.Name]; ok {
2019-06-19 09:06:52 +08:00
s.startServer(v)
reloaded = true
}
}
if !reloaded {
s.startServer(nil)
}
// Swagger UI info.
if s.config.SwaggerPath != "" {
s.Logger().Infof(
ctx,
`swagger ui is serving at address: %s%s/`,
s.getLocalListenedAddress(),
s.config.SwaggerPath,
)
}
// OpenApi specification info.
if s.config.OpenApiPath != "" {
s.Logger().Infof(
ctx,
`openapi specification is serving at address: %s%s`,
s.getLocalListenedAddress(),
s.config.OpenApiPath,
)
} else {
if s.config.SwaggerPath != "" {
s.Logger().Warning(
ctx,
`openapi specification is disabled but swagger ui is serving, which might make no sense`,
)
} else {
s.Logger().Info(
ctx,
`openapi specification is disabled`,
)
}
}
// If this is a child process, it then notifies its parent exit.
2019-06-19 09:06:52 +08:00
if gproc.IsChild() {
var gracefulTimeout = time.Duration(s.config.GracefulTimeout) * time.Second
gtimer.SetTimeout(ctx, gracefulTimeout, func(ctx context.Context) {
intlog.Printf(
ctx,
`pid[%d]: notice parent server graceful shuttingdown, ppid: %d`,
gproc.Pid(), gproc.PPid(),
)
2020-12-15 20:16:17 +08:00
if err := gproc.Send(gproc.PPid(), []byte("exit"), adminGProcCommGroup); err != nil {
intlog.Errorf(ctx, `server error in process communication: %+v`, err)
2019-06-19 09:06:52 +08:00
}
})
}
s.initOpenApi()
s.doServiceRegister()
s.doRouterMapDump()
2019-06-19 09:06:52 +08:00
return nil
}
func (s *Server) getLocalListenedAddress() string {
return fmt.Sprintf(`http://127.0.0.1:%d`, s.GetListenedPort())
}
// doRouterMapDump checks and dumps the router map to the log.
func (s *Server) doRouterMapDump() {
if !s.config.DumpRouterMap {
return
}
2021-09-27 21:27:24 +08:00
var (
2021-12-18 12:36:34 +08:00
ctx = context.TODO()
routes = s.GetRoutes()
2021-12-18 12:36:34 +08:00
isJustDefaultServerAndDomain = true
headers = []string{
"SERVER", "DOMAIN", "ADDRESS", "METHOD", "ROUTE", "HANDLER", "MIDDLEWARE",
}
2021-09-27 21:27:24 +08:00
)
for _, item := range routes {
2021-12-18 12:36:34 +08:00
if item.Server != DefaultServerName || item.Domain != DefaultDomainName {
isJustDefaultServerAndDomain = false
break
}
}
if isJustDefaultServerAndDomain {
headers = []string{"ADDRESS", "METHOD", "ROUTE", "HANDLER", "MIDDLEWARE"}
}
if len(routes) > 0 {
buffer := bytes.NewBuffer(nil)
table := tablewriter.NewTable(buffer,
tablewriter.WithRenderer(renderer.NewBlueprint(
tw.Rendition{
Settings: tw.Settings{
Separators: tw.Separators{BetweenRows: tw.On},
},
Symbols: tw.NewSymbolCustom("HTTP").WithCenter("|"),
})),
)
table.Header(headers)
2018-10-17 16:56:50 +08:00
for _, item := range routes {
var (
data = make([]string, 0)
handlerName = gstr.TrimRightStr(item.Handler.Name, "-fm")
middlewares = gstr.SplitAndTrim(item.Middleware, ",")
)
// No printing special internal middleware that may lead confused.
if gstr.SubStrFromREx(handlerName, ".") == noPrintInternalRoute {
continue
}
for k, v := range middlewares {
middlewares[k] = gstr.TrimRightStr(v, "-fm")
}
item.Middleware = gstr.Join(middlewares, "\n")
2021-12-18 12:36:34 +08:00
if isJustDefaultServerAndDomain {
data = append(
data,
item.Address,
item.Method,
item.Route,
handlerName,
2021-12-18 12:36:34 +08:00
item.Middleware,
)
} else {
data = append(
data,
item.Server,
item.Domain,
item.Address,
item.Method,
item.Route,
handlerName,
2021-12-18 12:36:34 +08:00
item.Middleware,
)
}
2025-09-01 15:33:50 +08:00
_ = table.Append(data)
}
2025-09-01 15:33:50 +08:00
_ = table.Render()
2021-09-27 21:27:24 +08:00
s.config.Logger.Header(false).Printf(ctx, "\n%s", buffer.String())
2019-06-19 09:06:52 +08:00
}
}
2019-06-19 09:06:52 +08:00
// GetOpenApi returns the OpenApi specification management object of the current server.
func (s *Server) GetOpenApi() *goai.OpenApiV3 {
return s.openapi
}
// GetRoutes retrieves and returns the router array.
func (s *Server) GetRoutes() []RouterItem {
var (
m = make(map[string]*garray.SortedArray)
routeFilterSet = gset.NewStrSet()
address = s.GetListenedAddress()
)
if s.config.HTTPSAddr != "" {
if len(address) > 0 {
address += ","
}
address += "tls" + s.config.HTTPSAddr
}
2022-05-06 20:25:21 +08:00
for k, handlerItems := range s.routesMap {
2019-06-19 09:06:52 +08:00
array, _ := gregex.MatchString(`(.*?)%([A-Z]+):(.+)@(.+)`, k)
for index := len(handlerItems) - 1; index >= 0; index-- {
var (
handlerItem = handlerItems[index]
item = RouterItem{
Server: s.config.Name,
Address: address,
Domain: array[4],
Type: handlerItem.Type,
Middleware: array[1],
Method: array[2],
Route: array[3],
Priority: index,
Handler: handlerItem,
}
)
switch item.Handler.Type {
case HandlerTypeObject, HandlerTypeHandler:
item.IsServiceHandler = true
case HandlerTypeMiddleware:
item.Middleware = "GLOBAL MIDDLEWARE"
}
// Repeated route filtering for dump.
var setKey = fmt.Sprintf(
`%s|%s|%s|%s`,
item.Method, item.Route, item.Domain, item.Type,
)
if !routeFilterSet.AddIfNotExist(setKey) {
continue
}
if len(item.Handler.Middleware) > 0 {
for _, v := range item.Handler.Middleware {
if item.Middleware != "" {
2021-05-02 08:10:35 +08:00
item.Middleware += ","
}
item.Middleware += gdebug.FuncName(v)
}
2019-06-19 09:06:52 +08:00
}
2021-05-01 09:04:16 +08:00
// If the domain does not exist in the dump map, it creates the map.
// The value of the map is a custom sorted array.
if _, ok := m[item.Domain]; !ok {
// Sort in ASC order.
refactor: interface{} to any and reflect.Ptr to reflect.Pointer (#4395) This pull request standardizes the use of the Go 1.18+ `any` type alias instead of `interface{}` throughout the codebase. The change improves code readability and aligns with modern Go best practices. The update touches many files, including core data structures, code generation templates, logging utilities, and test data, ensuring consistency across all usages. **Type alias migration to `any`:** * Replaced all instances of `interface{}` with `any` in core data structures such as `garray` and in generated model structs (e.g., `TableUser`, `User1`, `User2`) to modernize type usage. [[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31) [[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19) [[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18) [[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19) [[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19) [[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19) * Updated function signatures, method parameters, and return types from `interface{}` to `any` in various parts of the codebase, including code generation, service logic, and logging utilities (e.g., `mlog`). [[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55) [[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74) [[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73) [[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41) [[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107) [[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121) [[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112) [[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36) [[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74) [[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96) **Generated code and templates:** * Adjusted generated files and code generation templates to output `any` instead of `interface{}` for relevant struct fields and function signatures, ensuring that new code generation aligns with the updated convention. [[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19) [[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18) [[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19) [[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19) [[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19) [[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55) [[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73) [[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41) **Container and utility updates:** * Refactored the `garray` container implementation and related constructors/methods to use `[]any` instead of `[]interface{}`, along with corresponding function signatures. [[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31) [[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52) [[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62) [[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86) [[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97) [[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114) [[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124) [[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143) [[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167) These changes collectively modernize the codebase and prepare it for future Go developments by using the idiomatic `any` type.
2025-08-28 16:53:19 +08:00
m[item.Domain] = garray.NewSortedArray(func(v1, v2 any) int {
item1 := v1.(RouterItem)
item2 := v2.(RouterItem)
2019-06-19 09:06:52 +08:00
r := 0
if r = strings.Compare(item1.Domain, item2.Domain); r == 0 {
if r = strings.Compare(item1.Route, item2.Route); r == 0 {
if r = strings.Compare(item1.Method, item2.Method); r == 0 {
if item1.Handler.Type == HandlerTypeMiddleware && item2.Handler.Type != HandlerTypeMiddleware {
return -1
} else if item1.Handler.Type == HandlerTypeMiddleware && item2.Handler.Type == HandlerTypeMiddleware {
return 1
} else if r = strings.Compare(item1.Middleware, item2.Middleware); r == 0 {
r = item2.Priority - item1.Priority
2019-06-19 09:06:52 +08:00
}
}
}
}
return r
})
2019-06-19 09:06:52 +08:00
}
m[item.Domain].Add(item)
2019-06-19 09:06:52 +08:00
}
}
routerArray := make([]RouterItem, 0, 128)
for _, array := range m {
for _, v := range array.Slice() {
routerArray = append(routerArray, v.(RouterItem))
2019-06-19 09:06:52 +08:00
}
}
return routerArray
}
// Run starts server listening in blocking way.
// It's commonly used for single server situation.
func (s *Server) Run() {
var ctx = context.TODO()
2019-06-19 09:06:52 +08:00
if err := s.Start(); err != nil {
s.Logger().Fatalf(ctx, `%+v`, err)
2019-06-19 09:06:52 +08:00
}
// Signal handler in asynchronous way.
go handleProcessSignal()
// Blocking using the channel for graceful restart.
2019-06-19 09:06:52 +08:00
<-s.closeChan
// Shutdown the server
_ = s.Shutdown()
2018-05-10 19:16:41 +08:00
}
2018-05-10 17:48:47 +08:00
// Wait blocks to wait for all servers done.
// It's commonly used in multiple server situation.
func Wait() {
var ctx = context.TODO()
// Signal handler in asynchronous way.
go handleProcessSignal()
<-allShutdownChan
// Remove plugins.
feat(instance): migrate instance containers to type-safe generics (#4617) ### 变更说明 本次重构将项目中用于**实例管理的容器**从 `StrAnyMap`/`IntAnyMap` 迁移到类型安全的泛型实现 `KVMapWithChecker`,同时将相关的 `glist.List` 和 `gqueue.Queue` 替换为对应的泛型版本,以提高实例管理的类型安全性。并且减少原先代码中的大量类型断言,提高性能。 ### 前因 目前`goframe`中大量使用了包含`any`的容器,然后通过断言去转换类型,麻烦且影响性能,尤其是对`gdb/gredis/glog`等需要高频获取`instance`实例的组件影响较大。最近几个版本中gf完成了数据结构容器的泛型化改造,以及我最近解决了其中几个泛型容器对于`typed nil`过滤的问题,所以可以逐步迁移这些实例容器到泛型容器,减少断言优化性能 ### 主要改进 #### 1. 实例容器泛型化 以下模块的实例管理容器已迁移到泛型实现: **核心实例管理**: - `database/gdb`: 数据库实例容器 → `KVMap[string, DB]` - `database/gredis`: Redis 实例容器 → `KVMap[string, *Redis]` - `database/gredis`: Redis 配置容器 → `KVMap[string, *Config]` - `os/gcfg`: 配置实例容器 → `KVMap[string, *Config]` - `os/glog`: 日志实例容器 → `KVMap[string, *Logger]` - `os/gview`: 视图实例容器 → `KVMap[string, *View]` - `i18n/gi18n`: 国际化实例容器 → `KVMap[string, *Manager]` **网络服务实例**: - `net/ghttp`: HTTP 服务器容器 → `KVMap[string, *Server]` - `net/gtcp`: TCP 服务器容器 → `KVMap[any, *Server]` - `net/gudp`: UDP 服务器容器 → `KVMap[string, *Server]` **其他实例容器**: - `os/gres`: 资源实例容器 → `KVMap[string, *Resource]` - `os/gfpool`: 文件池容器 → `KVMap[string, *Pool]` - `os/gspath`: 路径搜索容器 → `KVMap[string, *SPath]` - `net/gtcp`: 连接池容器 → `KVMap[string, *gpool.Pool]` #### 2. 相关数据结构泛型化 - `os/gfsnotify`: 回调列表 → `TList[*Callback]`,事件队列 → `TQueue[*Event]` - `os/grpool`: 任务队列 → `TList[*localPoolItem]` - `os/gcache`: 事件队列 → `TList[*adapterMemoryEvent]` - `net/ghttp`: 解析项列表 → `TList[*HandlerItemParsed]` - `os/gproc`: 消息队列 → `TQueue[*MsgRequest]` - `os/gmlock`: 锁映射 → `KVMap[string, *sync.RWMutex]` ### 技术实现 1. **引入检查器函数**: 为每个实例容器添加 `checker` 函数用于空值检测 2. **消除类型断言**: 实例获取时无需 `v.(*Type)` 转换 3. **明确函数签名**: `GetOrSetFuncLock` 的回调从 `func() any` 改为 `func() T` ### 使用示例 #### 实例容器的变更 **变更前**: ```go // 旧的实例管理方式 var instances = gmap.NewStrAnyMap(true) func Instance(name string) *Logger { v := instances.GetOrSetFuncLock(name, func() any { return New() }) return v.(*Logger) // 需要类型断言 } ``` **变更后**: ```go // 新的泛型实例容器 var ( checker = func(v *Logger) bool { return v == nil } instances = gmap.NewKVMapWithChecker[string, *Logger](checker, true) ) func Instance(name string) *Logger { return instances.GetOrSetFuncLock(name, New) // 直接返回,无需断言 } ``` #### 队列容器的变更 **变更前**: ```go // 旧的队列方式 events := gqueue.New() events.Push(&Event{Path: "/tmp/file"}) if v := events.Pop(); v != nil { event := v.(*Event) // 需要类型断言 handleEvent(event) } ``` **变更后**: ```go // 新的泛型队列 events := gqueue.NewTQueue[*Event]() events.Push(&Event{Path: "/tmp/file"}) if event := events.Pop(); event != nil { handleEvent(event) // event 已是 *Event 类型 } ``` ### 收益 - ✅ **编译时类型安全**: 实例容器的类型错误在编译期捕获 - ✅ **消除运行时断言**: 避免类型断言带来的 panic 风险 - ✅ **提升代码可读性**: 实例管理逻辑更清晰 - ✅ **改善开发体验**: IDE 类型提示和代码补全更准确 ### 性能权衡 **编译时**: - 泛型实例化会增加编译时间和二进制体积 - 预估编译时间增加 5-15%,二进制体积增加约 1-2MB **运行时**: - 减少类型断言的反射开销 - 提升实例获取等热点路径的性能
2026-01-16 15:23:13 +08:00
serverMapping.Iterator(func(k string, v *Server) bool {
if len(v.plugins) > 0 {
for _, p := range v.plugins {
2021-09-27 21:27:24 +08:00
intlog.Printf(ctx, `remove plugin: %s`, p.Name())
if err := p.Remove(); err != nil {
intlog.Errorf(ctx, `%+v`, err)
2021-09-27 21:27:24 +08:00
}
}
}
return true
})
glog.Infof(ctx, "pid[%d]: all servers shutdown", gproc.Pid())
}
// startServer starts the underlying server listening.
2018-05-10 23:52:09 +08:00
func (s *Server) startServer(fdMap listenerFdMap) {
2021-09-27 21:27:24 +08:00
var (
ctx = context.TODO()
httpsEnabled bool
)
// HTTPS
if s.config.TLSConfig != nil || (s.config.HTTPSCertPath != "" && s.config.HTTPSKeyPath != "") {
2019-06-19 09:06:52 +08:00
if len(s.config.HTTPSAddr) == 0 {
if len(s.config.Address) > 0 {
s.config.HTTPSAddr = s.config.Address
s.config.Address = ""
2019-06-19 09:06:52 +08:00
} else {
s.config.HTTPSAddr = defaultHttpsAddr
2019-06-19 09:06:52 +08:00
}
}
httpsEnabled = len(s.config.HTTPSAddr) > 0
var array []string
if v, ok := fdMap["https"]; ok && len(v) > 0 {
array = strings.Split(v, ",")
} else {
array = strings.Split(s.config.HTTPSAddr, ",")
}
for _, v := range array {
if len(v) == 0 {
continue
}
2022-01-13 20:57:13 +08:00
var (
fd = 0
itemFunc = v
addrAndFd = strings.Split(v, "#")
)
if len(addrAndFd) > 1 {
itemFunc = addrAndFd[0]
2021-09-27 21:27:24 +08:00
// The Windows OS does not support socket file descriptor passing
// from parent process.
2019-06-19 09:06:52 +08:00
if runtime.GOOS != "windows" {
2022-01-13 20:57:13 +08:00
fd = gconv.Int(addrAndFd[1])
2019-06-19 09:06:52 +08:00
}
}
if fd > 0 {
s.servers = append(s.servers, s.newGracefulServer(itemFunc, fd))
2019-06-19 09:06:52 +08:00
} else {
s.servers = append(s.servers, s.newGracefulServer(itemFunc, 0))
2019-06-19 09:06:52 +08:00
}
s.servers[len(s.servers)-1].SetIsHttps(true)
2019-06-19 09:06:52 +08:00
}
}
// HTTP
if !httpsEnabled && len(s.config.Address) == 0 {
s.config.Address = defaultHttpAddr
2019-06-19 09:06:52 +08:00
}
var array []string
if v, ok := fdMap["http"]; ok && len(v) > 0 {
array = gstr.SplitAndTrim(v, ",")
2019-06-19 09:06:52 +08:00
} else {
array = gstr.SplitAndTrim(s.config.Address, ",")
2019-06-19 09:06:52 +08:00
}
for _, v := range array {
if len(v) == 0 {
continue
}
2022-01-13 20:57:13 +08:00
var (
fd = 0
itemFunc = v
addrAndFd = strings.Split(v, "#")
)
if len(addrAndFd) > 1 {
itemFunc = addrAndFd[0]
2022-03-19 17:58:21 +08:00
// The Window OS does not support socket file descriptor passing
// from the parent process.
2019-06-19 09:06:52 +08:00
if runtime.GOOS != "windows" {
2022-01-13 20:57:13 +08:00
fd = gconv.Int(addrAndFd[1])
2019-06-19 09:06:52 +08:00
}
}
if fd > 0 {
s.servers = append(s.servers, s.newGracefulServer(itemFunc, fd))
2019-06-19 09:06:52 +08:00
} else {
s.servers = append(s.servers, s.newGracefulServer(itemFunc, 0))
2019-06-19 09:06:52 +08:00
}
}
2021-05-15 18:13:51 +08:00
// Start listening asynchronously.
2019-06-19 09:06:52 +08:00
serverRunning.Add(1)
var wg = &sync.WaitGroup{}
for _, gs := range s.servers {
wg.Add(1)
go s.startGracefulServer(ctx, wg, gs)
2019-06-19 09:06:52 +08:00
}
wg.Wait()
2018-08-01 13:04:15 +08:00
}
2018-04-23 19:22:59 +08:00
func (s *Server) startGracefulServer(ctx context.Context, wg *sync.WaitGroup, server *graceful.Server) {
s.serverCount.Add(1)
var err error
// Create listener.
if server.IsHttps() {
err = server.CreateListenerTLS(
s.config.HTTPSCertPath, s.config.HTTPSKeyPath, s.config.TLSConfig,
)
} else {
err = server.CreateListener()
}
if err != nil {
s.Logger().Fatalf(ctx, `%+v`, err)
}
wg.Done()
// Start listening and serving in blocking way.
err = server.Serve(ctx)
// The process exits if the server is closed with none closing error.
if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) {
s.Logger().Fatalf(ctx, `%+v`, err)
}
// If all the underlying servers' shutdown, the process exits.
if s.serverCount.Add(-1) < 1 {
s.closeChan <- struct{}{}
if serverRunning.Add(-1) < 1 {
serverMapping.Remove(s.instance)
allShutdownChan <- struct{}{}
}
}
}
// Status retrieves and returns the server status.
func (s *Server) Status() ServerStatus {
2019-06-19 09:06:52 +08:00
if serverRunning.Val() == 0 {
2020-12-14 13:26:48 +08:00
return ServerStatusStopped
2019-06-19 09:06:52 +08:00
}
// If any underlying server is running, the server status is running.
2019-06-19 09:06:52 +08:00
for _, v := range s.servers {
if v.Status() == ServerStatusRunning {
2020-12-14 13:26:48 +08:00
return ServerStatusRunning
2019-06-19 09:06:52 +08:00
}
}
2020-12-14 13:26:48 +08:00
return ServerStatusStopped
}
// getListenerFdMap retrieves and returns the socket file descriptors.
// The key of the returned map is "http" and "https".
2018-05-10 23:52:09 +08:00
func (s *Server) getListenerFdMap() map[string]string {
2019-06-19 09:06:52 +08:00
m := map[string]string{
"https": "",
"http": "",
}
for _, v := range s.servers {
str := v.GetAddress() + "#" + gconv.String(v.Fd()) + ","
if v.IsHttps() {
if len(m["https"]) > 0 {
m["https"] += ","
}
2019-06-19 09:06:52 +08:00
m["https"] += str
} else {
if len(m["http"]) > 0 {
m["http"] += ","
}
2019-06-19 09:06:52 +08:00
m["http"] += str
}
}
return m
2018-05-10 19:16:41 +08:00
}
// GetListenedPort returns a port currently listened to by the server.
// It prioritizes the HTTP port if both HTTP and HTTPS are enabled.
func (s *Server) GetListenedPort() int {
for _, server := range s.servers {
if !server.IsHttps() {
return server.GetListenedPort()
}
}
for _, server := range s.servers {
if server.IsHttps() {
return server.GetListenedPort()
}
}
return -1
}
// GetListenedHTTPSPort retrieves and returns one port which is listened using TLS by current server.
func (s *Server) GetListenedHTTPSPort() int {
for _, server := range s.servers {
if server.IsHttps() {
return server.GetListenedPort()
}
}
return -1
}
// GetListenedPorts retrieves and returns the ports which are listened by current server.
func (s *Server) GetListenedPorts() []int {
ports := make([]int, 0)
for _, server := range s.servers {
ports = append(ports, server.GetListenedPort())
}
return ports
}
// GetListenedAddress retrieves and returns the address string which are listened by current server.
func (s *Server) GetListenedAddress() string {
if !gstr.Contains(s.config.Address, FreePortAddress) {
return s.config.Address
}
var (
address = s.config.Address
listenedPorts = s.GetListenedPorts()
)
for _, listenedPort := range listenedPorts {
address = gstr.Replace(address, FreePortAddress, fmt.Sprintf(`:%d`, listenedPort), 1)
}
return address
}