Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
038e14f3d8 | ||
|
|
a7c08cdf26 | ||
|
|
c891d37fad | ||
|
|
fbf7e6475c |
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,16 @@
|
|||||||
# CHANGELOG - go/service
|
# CHANGELOG - go/service
|
||||||
|
|
||||||
|
## v1.5.12 (2026-06-07)
|
||||||
|
- **基础设施对齐: 切换至 starter v1.5.3 编排模式**:
|
||||||
|
- 弃用已废弃的 `starter.Run()`,全面转向 `starter.Start() / starter.Wait()`。
|
||||||
|
|
||||||
|
## v1.5.11 (2026-06-06)
|
||||||
|
- **修复: 路由与静态文件匹配鲁棒性增强**:
|
||||||
|
- **路径参数提取**: 修复了正则匹配路由(如 `{name}`)无法正确提取并注入路径参数到业务函数的问题。
|
||||||
|
- **静态文件匹配**: 引入 `hostStatics` 有序路由表,实现“最长前缀匹配”策略,解决在复杂或重叠的静态目录配置下的匹配歧义问题。
|
||||||
|
- **URL 兼容性**: 针对包含空格、中文字符及特殊符号的复杂 URL,在路由与静态文件匹配阶段统一进行 Robust 处理,彻底解决 404 隐患。
|
||||||
|
- **Host 匹配增强**: 验证并明确了 Host 匹配的灵活性,支持 `hostname:port`, `hostname`, `:port` 的自动降级匹配。
|
||||||
|
|
||||||
## v1.5.10 (2026-06-05)
|
## v1.5.10 (2026-06-05)
|
||||||
- **修复: Static 服务 URL 解码**:
|
- **修复: Static 服务 URL 解码**:
|
||||||
- 修复了 \`service.Static()\` 在处理包含空格或特殊字符(如 \`%20\`)的请求路径时,因未解码导致文件匹配失败的问题。
|
- 修复了 \`service.Static()\` 在处理包含空格或特殊字符(如 \`%20\`)的请求路径时,因未解码导致文件匹配失败的问题。
|
||||||
|
|||||||
@ -24,7 +24,7 @@ func MakeDocument() []Api {
|
|||||||
return DefaultServer.MakeDocument()
|
return DefaultServer.MakeDocument()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) MakeDocument() []Api {
|
func (ws *WebServer) MakeDocument() []Api {
|
||||||
out := make([]Api, 0)
|
out := make([]Api, 0)
|
||||||
|
|
||||||
// 1. Rewrite & Proxy
|
// 1. Rewrite & Proxy
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -13,7 +13,7 @@ require (
|
|||||||
apigo.cc/go/log v1.5.5
|
apigo.cc/go/log v1.5.5
|
||||||
apigo.cc/go/redis v1.5.0
|
apigo.cc/go/redis v1.5.0
|
||||||
apigo.cc/go/safe v1.5.0
|
apigo.cc/go/safe v1.5.0
|
||||||
apigo.cc/go/starter v1.5.2
|
apigo.cc/go/starter v1.5.3
|
||||||
apigo.cc/go/timer v1.5.0
|
apigo.cc/go/timer v1.5.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
golang.org/x/net v0.54.0
|
golang.org/x/net v0.54.0
|
||||||
|
|||||||
6
go.sum
6
go.sum
@ -16,8 +16,7 @@ apigo.cc/go/id v1.5.0 h1:MjNWPhBhDsoXaLeJDv/0wfJmVMU9EvOs8pWYfsTQ6e8=
|
|||||||
apigo.cc/go/id v1.5.0/go.mod h1:qhu4a1/KLc/XcBpcsRu+mXZt7U7Wvd9zMcPs4VspuPA=
|
apigo.cc/go/id v1.5.0/go.mod h1:qhu4a1/KLc/XcBpcsRu+mXZt7U7Wvd9zMcPs4VspuPA=
|
||||||
apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec=
|
apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec=
|
||||||
apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
||||||
apigo.cc/go/log v1.5.4 h1:LNyU4v09gfcnZOY53ctnXoKzo45FHoEcPR33lk6PBaY=
|
apigo.cc/go/log v1.5.5 h1:AFU7d7AQxkpgDHl7SnlEwd6yzGSFAlnrrjbrNDQnQHI=
|
||||||
apigo.cc/go/log v1.5.4/go.mod h1:Djy+I5aLhGB/EjwRz4KHqkVEz584IAD55FAFiIfInuo=
|
|
||||||
apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E=
|
apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E=
|
||||||
apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk=
|
apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk=
|
||||||
apigo.cc/go/redis v1.5.0 h1:VXNDqzKj87BchF7ubDEH+T6lp8NrjeK0izU4ooo7u1A=
|
apigo.cc/go/redis v1.5.0 h1:VXNDqzKj87BchF7ubDEH+T6lp8NrjeK0izU4ooo7u1A=
|
||||||
@ -26,8 +25,7 @@ apigo.cc/go/safe v1.5.0 h1:W1NblmcU8cex1f9Y5z8mNLUJOzZTE1s6fszb3FbhGnk=
|
|||||||
apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM=
|
apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM=
|
||||||
apigo.cc/go/shell v1.5.0 h1:WLDMMqUU0INeaBDmQsTPr0h/NfB2RknAtiJ5NL467+Q=
|
apigo.cc/go/shell v1.5.0 h1:WLDMMqUU0INeaBDmQsTPr0h/NfB2RknAtiJ5NL467+Q=
|
||||||
apigo.cc/go/shell v1.5.0/go.mod h1:rYHA77d5hEsQHcJrbAWf1pHy0sxayeJ0gU55LA/JWQk=
|
apigo.cc/go/shell v1.5.0/go.mod h1:rYHA77d5hEsQHcJrbAWf1pHy0sxayeJ0gU55LA/JWQk=
|
||||||
apigo.cc/go/starter v1.5.2 h1:bSNByK9uU+4+Rw8a83TNhQnUUjCPfJj7DVUpEuCD2wg=
|
apigo.cc/go/starter v1.5.3 h1:kakDapul+l63w3Ah1pnBxD1mup9Fbt821omWCiaGwCE=
|
||||||
apigo.cc/go/starter v1.5.2/go.mod h1:iu3AnpqpriJBiTJC0MJyeFxGmmFto+SU+S2d96XhRco=
|
|
||||||
apigo.cc/go/timer v1.5.0 h1:iPo/IQn+iuhBRI1/MR1txwZnamef/RBBfOiIlBiqkgk=
|
apigo.cc/go/timer v1.5.0 h1:iPo/IQn+iuhBRI1/MR1txwZnamef/RBBfOiIlBiqkgk=
|
||||||
apigo.cc/go/timer v1.5.0/go.mod h1:kOnqTTX+zA4AH7SfC+LpUm4ZvS+DVyWWMqul/V5QWJs=
|
apigo.cc/go/timer v1.5.0/go.mod h1:kOnqTTX+zA4AH7SfC+LpUm4ZvS+DVyWWMqul/V5QWJs=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||||
|
|||||||
20
handler.go
20
handler.go
@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RouteHandler struct {
|
type RouteHandler struct {
|
||||||
ws *webServer
|
ws *WebServer
|
||||||
webRequestingNum int64
|
webRequestingNum int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ func (rh *RouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
goto filter
|
goto filter
|
||||||
}
|
}
|
||||||
|
|
||||||
s, wsc = ws.findService(r.Method, host, path)
|
s, wsc = ws.findService(r.Method, host, path, args)
|
||||||
|
|
||||||
// 4. 参数解析 (Form & Body)
|
// 4. 参数解析 (Form & Body)
|
||||||
parseRequestArgs(request, args)
|
parseRequestArgs(request, args)
|
||||||
@ -245,7 +245,7 @@ func hostOnly(host string) string {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) findService(method, host, path string) (*webServiceType, *websocketServiceType) {
|
func (ws *WebServer) findService(method, host, path string, args map[string]any) (*webServiceType, *websocketServiceType) {
|
||||||
ws.webServicesLock.RLock()
|
ws.webServicesLock.RLock()
|
||||||
defer ws.webServicesLock.RUnlock()
|
defer ws.webServicesLock.RUnlock()
|
||||||
|
|
||||||
@ -289,6 +289,14 @@ func (ws *webServer) findService(method, host, path string) (*webServiceType, *w
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if s.pathMatcher != nil && s.pathMatcher.MatchString(path) {
|
if s.pathMatcher != nil && s.pathMatcher.MatchString(path) {
|
||||||
|
matches := s.pathMatcher.FindStringSubmatch(path)
|
||||||
|
if len(matches) > 1 {
|
||||||
|
for i, name := range s.pathArgs {
|
||||||
|
if i+1 < len(matches) {
|
||||||
|
args[name] = matches[i+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,7 +339,7 @@ func parseRequestArgs(request *Request, args map[string]any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) checkAuth(authLevel int, options *WebServiceOptions, request *Request, response *Response, args map[string]any, logger *log.Logger) (bool, any) {
|
func (ws *WebServer) checkAuth(authLevel int, options *WebServiceOptions, request *Request, response *Response, args map[string]any, logger *log.Logger) (bool, any) {
|
||||||
ac := ws.webAuthCheckers[authLevel]
|
ac := ws.webAuthCheckers[authLevel]
|
||||||
if ac == nil {
|
if ac == nil {
|
||||||
ac = ws.webAuthChecker
|
ac = ws.webAuthChecker
|
||||||
@ -350,7 +358,7 @@ func (ws *webServer) checkAuth(authLevel int, options *WebServiceOptions, reques
|
|||||||
return pass, obj
|
return pass, obj
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) doWebService(service *webServiceType, request *Request, response *Response, args map[string]any,
|
func (ws *WebServer) doWebService(service *webServiceType, request *Request, response *Response, args map[string]any,
|
||||||
result any, logger *log.Logger, object any) any {
|
result any, logger *log.Logger, object any) any {
|
||||||
if result != nil {
|
if result != nil {
|
||||||
return result
|
return result
|
||||||
@ -428,7 +436,7 @@ func outputResult(response *Response, result any) {
|
|||||||
_, _ = response.Write(data)
|
_, _ = response.Write(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (ws *webServer) handleClientKeys(request *Request, response *Response) {
|
func (ws *WebServer) handleClientKeys(request *Request, response *Response) {
|
||||||
// SessionId
|
// SessionId
|
||||||
if ws.usedSessionIdKey != "" {
|
if ws.usedSessionIdKey != "" {
|
||||||
sessionId := request.Header().Get(ws.usedSessionIdKey)
|
sessionId := request.Header().Get(ws.usedSessionIdKey)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ func init() {
|
|||||||
"__exportFile": func() *jsUploadFile { return &jsUploadFile{} },
|
"__exportFile": func() *jsUploadFile { return &jsUploadFile{} },
|
||||||
|
|
||||||
// 功能函数
|
// 功能函数
|
||||||
"upgrade": Upgrade,
|
"Upgrade": Upgrade,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
proxy.go
10
proxy.go
@ -88,7 +88,7 @@ func (hc *HostContext) Proxy(authLevel int, path string, to string) *HostContext
|
|||||||
return hc
|
return hc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) findProxy(request *Request) (int, *string, *string, string) {
|
func (ws *WebServer) findProxy(request *Request) (int, *string, *string, string) {
|
||||||
host := request.Host
|
host := request.Host
|
||||||
hostOnly, port, _ := strings.Cut(host, ":")
|
hostOnly, port, _ := strings.Cut(host, ":")
|
||||||
hosts := []string{host}
|
hosts := []string{host}
|
||||||
@ -145,7 +145,7 @@ func (ws *webServer) findProxy(request *Request) (int, *string, *string, string)
|
|||||||
return 0, nil, nil, ""
|
return 0, nil, nil, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) processProxy(request *Request, response *Response, logger *log.Logger) bool {
|
func (ws *WebServer) processProxy(request *Request, response *Response, logger *log.Logger) bool {
|
||||||
authLevel, proxyToApp, proxyToPath, foundHost := ws.findProxy(request)
|
authLevel, proxyToApp, proxyToPath, foundHost := ws.findProxy(request)
|
||||||
|
|
||||||
if proxyToApp == nil || proxyToPath == nil || *proxyToApp == "" || *proxyToPath == "" {
|
if proxyToApp == nil || proxyToPath == nil || *proxyToApp == "" || *proxyToPath == "" {
|
||||||
@ -186,7 +186,7 @@ func (ws *webServer) processProxy(request *Request, response *Response, logger *
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) getHttpClient() *gohttp.Client {
|
func (ws *WebServer) getHttpClient() *gohttp.Client {
|
||||||
// 尝试从注入对象获取
|
// 尝试从注入对象获取
|
||||||
if obj := ws.GetInject(reflect.TypeOf(&gohttp.Client{})); obj != nil {
|
if obj := ws.GetInject(reflect.TypeOf(&gohttp.Client{})); obj != nil {
|
||||||
return obj.(*gohttp.Client)
|
return obj.(*gohttp.Client)
|
||||||
@ -198,7 +198,7 @@ func (ws *webServer) getHttpClient() *gohttp.Client {
|
|||||||
return gohttp.NewClient(timeout)
|
return gohttp.NewClient(timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) checkAuthForProxy(authLevel int, request *Request, response *Response, logger *log.Logger) (bool, any) {
|
func (ws *WebServer) checkAuthForProxy(authLevel int, request *Request, response *Response, logger *log.Logger) (bool, any) {
|
||||||
ac := ws.webAuthCheckers[authLevel]
|
ac := ws.webAuthCheckers[authLevel]
|
||||||
if ac == nil {
|
if ac == nil {
|
||||||
ac = ws.webAuthChecker
|
ac = ws.webAuthChecker
|
||||||
@ -245,7 +245,7 @@ func ReplaceProxies(host string, rules []ProxyRule) {
|
|||||||
DefaultServer.ReplaceProxies(host, rules)
|
DefaultServer.ReplaceProxies(host, rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) ReplaceProxies(host string, rules []ProxyRule) {
|
func (ws *WebServer) ReplaceProxies(host string, rules []ProxyRule) {
|
||||||
newProxies := make([]*proxyType, 0, len(rules))
|
newProxies := make([]*proxyType, 0, len(rules))
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
newProxies = append(newProxies, parseProxyRule(r.AuthLevel, r.Path, r.ToApp, r.ToPath, r.To))
|
newProxies = append(newProxies, parseProxyRule(r.AuthLevel, r.Path, r.ToApp, r.ToPath, r.To))
|
||||||
|
|||||||
@ -17,14 +17,14 @@ func OnReload(handler func() error) {
|
|||||||
DefaultServer.OnReload(handler)
|
DefaultServer.OnReload(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) OnReload(handler func() error) {
|
func (ws *WebServer) OnReload(handler func() error) {
|
||||||
globalReloadHook.lock.Lock()
|
globalReloadHook.lock.Lock()
|
||||||
defer globalReloadHook.lock.Unlock()
|
defer globalReloadHook.lock.Unlock()
|
||||||
globalReloadHook.hooks = append(globalReloadHook.hooks, handler)
|
globalReloadHook.hooks = append(globalReloadHook.hooks, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// triggerReload 触发所有注册的重新加载钩子
|
// triggerReload 触发所有注册的重新加载钩子
|
||||||
func (ws *webServer) triggerReload() error {
|
func (ws *WebServer) triggerReload() error {
|
||||||
globalReloadHook.lock.RLock()
|
globalReloadHook.lock.RLock()
|
||||||
hooks := make([]func() error, len(globalReloadHook.hooks))
|
hooks := make([]func() error, len(globalReloadHook.hooks))
|
||||||
copy(hooks, globalReloadHook.hooks)
|
copy(hooks, globalReloadHook.hooks)
|
||||||
|
|||||||
@ -19,7 +19,7 @@ type Response struct {
|
|||||||
dontLog200 bool `js:"-"`
|
dontLog200 bool `js:"-"`
|
||||||
dontLogArgs []string `js:"-"`
|
dontLogArgs []string `js:"-"`
|
||||||
ProxyHeader *http.Header `js:"-"`
|
ProxyHeader *http.Header `js:"-"`
|
||||||
server *webServer `js:"-"`
|
server *WebServer `js:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Response) SetCookie(cookie *Cookie) {
|
func (r *Response) SetCookie(cookie *Cookie) {
|
||||||
@ -38,7 +38,7 @@ func (r *Response) SetCookie(cookie *Cookie) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewResponse 创建 Response 包装
|
// NewResponse 创建 Response 包装
|
||||||
func NewResponse(writer http.ResponseWriter, server *webServer) *Response {
|
func NewResponse(writer http.ResponseWriter, server *WebServer) *Response {
|
||||||
return &Response{
|
return &Response{
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|||||||
@ -49,7 +49,7 @@ func (hc *HostContext) Rewrite(path string, to string) *HostContext {
|
|||||||
return hc
|
return hc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) processRewrite(request *Request, response *Response, logger *log.Logger) bool {
|
func (ws *WebServer) processRewrite(request *Request, response *Response, logger *log.Logger) bool {
|
||||||
host := request.Host
|
host := request.Host
|
||||||
hostOnly, port, _ := strings.Cut(host, ":")
|
hostOnly, port, _ := strings.Cut(host, ":")
|
||||||
hosts := []string{host}
|
hosts := []string{host}
|
||||||
@ -139,7 +139,7 @@ func ReplaceRewrites(host string, rules []RewriteRule) {
|
|||||||
DefaultServer.ReplaceRewrites(host, rules)
|
DefaultServer.ReplaceRewrites(host, rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) ReplaceRewrites(host string, rules []RewriteRule) {
|
func (ws *WebServer) ReplaceRewrites(host string, rules []RewriteRule) {
|
||||||
newRewrites := make([]*rewriteType, 0, len(rules))
|
newRewrites := make([]*rewriteType, 0, len(rules))
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
newRewrites = append(newRewrites, parseRewriteRule(r.Path, r.ToPath, r.To))
|
newRewrites = append(newRewrites, parseRewriteRule(r.Path, r.ToPath, r.To))
|
||||||
|
|||||||
114
robustness_test.go
Normal file
114
robustness_test.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStaticRobustness(t *testing.T) {
|
||||||
|
tempDir, _ := os.MkdirTemp("", "robustness_test")
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
// 创建复杂的目录结构
|
||||||
|
subDir := filepath.Join(tempDir, "The NPC Awakens", "The Loop", "scene", "M")
|
||||||
|
_ = os.MkdirAll(subDir, 0755)
|
||||||
|
|
||||||
|
fileName := "画面逐渐亮起,铁匠铺的铁锤在无人操作的情况下,机械地敲击着烧红的铁块。_large.webp"
|
||||||
|
testFile := filepath.Join(subDir, fileName)
|
||||||
|
content := []byte("fake webp content")
|
||||||
|
_ = os.WriteFile(testFile, content, 0644)
|
||||||
|
|
||||||
|
// 注册静态目录
|
||||||
|
ws := NewWebServer()
|
||||||
|
ws.Config.App = "test"
|
||||||
|
ws.Static("/img/", tempDir)
|
||||||
|
|
||||||
|
rh := &RouteHandler{ws: ws}
|
||||||
|
|
||||||
|
// 构造编码后的请求路径
|
||||||
|
encodedPath := "/img/" + url.PathEscape("The NPC Awakens/The Loop/scene/M/画面逐渐亮起,铁匠铺的铁锤在无人操作的情况下,机械地敲击着烧红的铁块。_large.webp")
|
||||||
|
|
||||||
|
// 测试静态文件访问
|
||||||
|
req := httptest.NewRequest("GET", encodedPath+"?v=1780317467305", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
rh.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("Expected 200 for complex static file, got %d. Path: %s", w.Code, encodedPath)
|
||||||
|
} else if string(w.Body.Bytes()) != string(content) {
|
||||||
|
t.Errorf("Content mismatch for complex static file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDynamicRobustness(t *testing.T) {
|
||||||
|
ws := NewWebServer()
|
||||||
|
ws.Config.App = "test"
|
||||||
|
|
||||||
|
pathPattern := "/api/scene/{name}"
|
||||||
|
ws.Host("*").GET(pathPattern, func(in struct{ Name string }) string {
|
||||||
|
return "Hello " + in.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
rh := &RouteHandler{ws: ws}
|
||||||
|
|
||||||
|
complexName := "画面逐渐亮起,铁匠铺的铁锤"
|
||||||
|
encodedPath := "/api/scene/" + url.PathEscape(complexName)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", encodedPath, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
rh.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("Expected 200 for complex dynamic path, got %d", w.Code)
|
||||||
|
}
|
||||||
|
expectedBody := "Hello " + complexName
|
||||||
|
if w.Body.String() != expectedBody {
|
||||||
|
t.Errorf("Got body: %s, expected: %s", w.Body.String(), expectedBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostMatching(t *testing.T) {
|
||||||
|
ws := NewWebServer()
|
||||||
|
ws.Config.App = "test"
|
||||||
|
|
||||||
|
// 1. 注册只带端口的 Host
|
||||||
|
ws.Host(":8080").GET("/port", func() string { return "port" })
|
||||||
|
// 2. 注册只带域名的 Host
|
||||||
|
ws.Host("localhost").GET("/host", func() string { return "host" })
|
||||||
|
// 3. 注册完整 Host
|
||||||
|
ws.Host("example.com:9000").GET("/full", func() string { return "full" })
|
||||||
|
|
||||||
|
rh := &RouteHandler{ws: ws}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
requestHost string
|
||||||
|
path string
|
||||||
|
expected string
|
||||||
|
code int
|
||||||
|
}{
|
||||||
|
{"localhost:8080", "/port", "port", http.StatusOK},
|
||||||
|
{"otherhost:8080", "/port", "port", http.StatusOK},
|
||||||
|
{"localhost:9999", "/host", "host", http.StatusOK},
|
||||||
|
{"example.com:9000", "/full", "full", http.StatusOK},
|
||||||
|
{"example.com:8080", "/port", "port", http.StatusOK},
|
||||||
|
{"localhost:8080", "/host", "host", http.StatusOK},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
req := httptest.NewRequest("GET", tt.path, nil)
|
||||||
|
req.Host = tt.requestHost
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
rh.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != tt.code {
|
||||||
|
t.Errorf("Host [%s] Path [%s] expected code %d, got %d", tt.requestHost, tt.path, tt.code, w.Code)
|
||||||
|
}
|
||||||
|
if tt.code == http.StatusOK && w.Body.String() != tt.expected {
|
||||||
|
t.Errorf("Host [%s] Path [%s] expected body %s, got %s", tt.requestHost, tt.path, tt.expected, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
server.go
51
server.go
@ -23,7 +23,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type webServer struct {
|
type staticType struct {
|
||||||
|
path string
|
||||||
|
rootPath *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebServer struct {
|
||||||
Config ServiceConfig
|
Config ServiceConfig
|
||||||
server *http.Server
|
server *http.Server
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
@ -67,6 +72,7 @@ type webServer struct {
|
|||||||
codeStatics map[string]map[string]*string
|
codeStatics map[string]map[string]*string
|
||||||
fileStatics map[string]map[string]*string
|
fileStatics map[string]map[string]*string
|
||||||
dynamicStatics map[string]map[string]*string
|
dynamicStatics map[string]map[string]*string
|
||||||
|
hostStatics map[string][]*staticType
|
||||||
staticsByHostLock sync.RWMutex
|
staticsByHostLock sync.RWMutex
|
||||||
|
|
||||||
// 过滤器与拦截器
|
// 过滤器与拦截器
|
||||||
@ -99,13 +105,13 @@ type webServer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultServer 全局单例服务实例
|
// DefaultServer 全局单例服务实例
|
||||||
var DefaultServer = newWebServer()
|
var DefaultServer = NewWebServer()
|
||||||
|
|
||||||
// Config 全局配置对象 (指向 DefaultServer.Config)
|
// Config 全局配置对象 (指向 DefaultServer.Config)
|
||||||
var Config = &DefaultServer.Config
|
var Config = &DefaultServer.Config
|
||||||
|
|
||||||
func newWebServer() *webServer {
|
func NewWebServer() *WebServer {
|
||||||
ws := &webServer{
|
ws := &WebServer{
|
||||||
webServices: make(map[string]map[string]*webServiceType),
|
webServices: make(map[string]map[string]*webServiceType),
|
||||||
regexWebServices: make(map[string][]*webServiceType),
|
regexWebServices: make(map[string][]*webServiceType),
|
||||||
webServicesList: make([]*webServiceType, 0),
|
webServicesList: make([]*webServiceType, 0),
|
||||||
@ -124,6 +130,7 @@ func newWebServer() *webServer {
|
|||||||
codeStatics: make(map[string]map[string]*string),
|
codeStatics: make(map[string]map[string]*string),
|
||||||
fileStatics: make(map[string]map[string]*string),
|
fileStatics: make(map[string]map[string]*string),
|
||||||
dynamicStatics: make(map[string]map[string]*string),
|
dynamicStatics: make(map[string]map[string]*string),
|
||||||
|
hostStatics: make(map[string][]*staticType),
|
||||||
webAuthCheckers: make(map[int]func(int, *log.Logger, *string, map[string]any, *Request, *Response, *WebServiceOptions) (pass bool, object any)),
|
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),
|
injectObjects: make(map[reflect.Type]any),
|
||||||
injectFunctions: make(map[reflect.Type]func() any),
|
injectFunctions: make(map[reflect.Type]func() any),
|
||||||
@ -137,7 +144,7 @@ func SetDiscovererForTest(d *discover.Discoverer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyConfig 将 ServiceConfig 中的路由策略应用到内部的文件级策略中
|
// ApplyConfig 将 ServiceConfig 中的路由策略应用到内部的文件级策略中
|
||||||
func (ws *webServer) ApplyConfig() {
|
func (ws *WebServer) ApplyConfig() {
|
||||||
ws.hostPoliciesLock.Lock()
|
ws.hostPoliciesLock.Lock()
|
||||||
defer ws.hostPoliciesLock.Unlock()
|
defer ws.hostPoliciesLock.Unlock()
|
||||||
|
|
||||||
@ -225,7 +232,7 @@ func (ws *webServer) ApplyConfig() {
|
|||||||
ws.rebuildStaticsUnderLock("")
|
ws.rebuildStaticsUnderLock("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) rebuildProxiesUnderLock(host string) {
|
func (ws *WebServer) rebuildProxiesUnderLock(host string) {
|
||||||
combined := make([]*proxyType, 0)
|
combined := make([]*proxyType, 0)
|
||||||
combined = append(combined, ws.codeProxies[host]...)
|
combined = append(combined, ws.codeProxies[host]...)
|
||||||
combined = append(combined, ws.fileProxies[host]...)
|
combined = append(combined, ws.fileProxies[host]...)
|
||||||
@ -237,7 +244,7 @@ func (ws *webServer) rebuildProxiesUnderLock(host string) {
|
|||||||
ws.hostProxies[host] = combined
|
ws.hostProxies[host] = combined
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) rebuildRewritesUnderLock(host string) {
|
func (ws *WebServer) rebuildRewritesUnderLock(host string) {
|
||||||
combined := make([]*rewriteType, 0)
|
combined := make([]*rewriteType, 0)
|
||||||
combined = append(combined, ws.codeRewrites[host]...)
|
combined = append(combined, ws.codeRewrites[host]...)
|
||||||
combined = append(combined, ws.fileRewrites[host]...)
|
combined = append(combined, ws.fileRewrites[host]...)
|
||||||
@ -249,7 +256,7 @@ func (ws *webServer) rebuildRewritesUnderLock(host string) {
|
|||||||
ws.hostRewrites[host] = combined
|
ws.hostRewrites[host] = combined
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) rebuildStaticsUnderLock(host string) {
|
func (ws *WebServer) rebuildStaticsUnderLock(host string) {
|
||||||
combined := make(map[string]*string)
|
combined := make(map[string]*string)
|
||||||
for k, v := range ws.codeStatics[host] {
|
for k, v := range ws.codeStatics[host] {
|
||||||
combined[k] = v
|
combined[k] = v
|
||||||
@ -266,10 +273,20 @@ func (ws *webServer) rebuildStaticsUnderLock(host string) {
|
|||||||
} else {
|
} else {
|
||||||
ws.staticsByHost[host] = combined
|
ws.staticsByHost[host] = combined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构造有序的静态路由列表 (按路径长度降序排列,实现最长匹配)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start 启动服务,实现 starter.Service 接口
|
// Start 启动服务,实现 starter.Service 接口
|
||||||
func (ws *webServer) Start(ctx context.Context, logger *log.Logger) error {
|
func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = log.DefaultLogger
|
logger = log.DefaultLogger
|
||||||
}
|
}
|
||||||
@ -425,7 +442,7 @@ func (ws *webServer) Start(ctx context.Context, logger *log.Logger) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop 停止服务,实现 starter.Service 接口
|
// Stop 停止服务,实现 starter.Service 接口
|
||||||
func (ws *webServer) Stop(ctx context.Context) error {
|
func (ws *WebServer) Stop(ctx context.Context) error {
|
||||||
ws.running = false
|
ws.running = false
|
||||||
|
|
||||||
// 执行停机钩子 (反序)
|
// 执行停机钩子 (反序)
|
||||||
@ -448,7 +465,7 @@ func (ws *webServer) Stop(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status 检查服务健康状态,实现 starter.Service 接口
|
// Status 检查服务健康状态,实现 starter.Service 接口
|
||||||
func (ws *webServer) Status() (string, error) {
|
func (ws *WebServer) Status() (string, error) {
|
||||||
if ws.server == nil || !ws.running {
|
if ws.server == nil || !ws.running {
|
||||||
return "", fmt.Errorf("server is not running")
|
return "", fmt.Errorf("server is not running")
|
||||||
}
|
}
|
||||||
@ -456,7 +473,7 @@ func (ws *webServer) Status() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reload 实现配置重新加载,实现 starter.Reloader 接口
|
// Reload 实现配置重新加载,实现 starter.Reloader 接口
|
||||||
func (ws *webServer) Reload() error {
|
func (ws *WebServer) Reload() error {
|
||||||
logger := ws.logger
|
logger := ws.logger
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = log.DefaultLogger
|
logger = log.DefaultLogger
|
||||||
@ -473,7 +490,7 @@ func (ws *webServer) Reload() error {
|
|||||||
|
|
||||||
// AsyncServer 兼容旧版异步服务实例
|
// AsyncServer 兼容旧版异步服务实例
|
||||||
type AsyncServer struct {
|
type AsyncServer struct {
|
||||||
*webServer
|
*WebServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop 兼容旧版的无参数停止方法
|
// Stop 兼容旧版的无参数停止方法
|
||||||
@ -484,13 +501,13 @@ func (as *AsyncServer) Stop() {
|
|||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), stopTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), stopTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_ = as.webServer.Stop(ctx)
|
_ = as.WebServer.Stop(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsyncStart 兼容旧版的异步启动方法
|
// AsyncStart 兼容旧版的异步启动方法
|
||||||
func AsyncStart() *AsyncServer {
|
func AsyncStart() *AsyncServer {
|
||||||
_ = DefaultServer.Start(context.Background(), log.DefaultLogger)
|
_ = DefaultServer.Start(context.Background(), log.DefaultLogger)
|
||||||
return &AsyncServer{webServer: DefaultServer}
|
return &AsyncServer{WebServer: DefaultServer}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait 等待服务结束 (兼容旧版,直接阻塞)
|
// Wait 等待服务结束 (兼容旧版,直接阻塞)
|
||||||
@ -511,6 +528,8 @@ func Start() {
|
|||||||
stopTimeout = 5 * time.Second
|
stopTimeout = 5 * time.Second
|
||||||
}
|
}
|
||||||
starter.Register("web-server", DefaultServer, 100, 5*time.Second, stopTimeout)
|
starter.Register("web-server", DefaultServer, 100, 5*time.Second, stopTimeout)
|
||||||
starter.Run()
|
if err := starter.Start(); err == nil {
|
||||||
|
starter.Wait()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
30
service.go
30
service.go
@ -61,7 +61,7 @@ func SetClientKeys(deviceIdKey, clientAppKey, sessionIdKey string) {
|
|||||||
DefaultServer.SetClientKeys(deviceIdKey, clientAppKey, sessionIdKey)
|
DefaultServer.SetClientKeys(deviceIdKey, clientAppKey, sessionIdKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) SetClientKeys(deviceIdKey, clientAppKey, sessionIdKey string) {
|
func (ws *WebServer) SetClientKeys(deviceIdKey, clientAppKey, sessionIdKey string) {
|
||||||
ws.usedDeviceIdKey = deviceIdKey
|
ws.usedDeviceIdKey = deviceIdKey
|
||||||
ws.usedClientAppKey = clientAppKey
|
ws.usedClientAppKey = clientAppKey
|
||||||
ws.usedSessionIdKey = sessionIdKey
|
ws.usedSessionIdKey = sessionIdKey
|
||||||
@ -72,7 +72,7 @@ func SetSessionIdMaker(maker func() string) {
|
|||||||
DefaultServer.SetSessionIdMaker(maker)
|
DefaultServer.SetSessionIdMaker(maker)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) SetSessionIdMaker(maker func() string) {
|
func (ws *WebServer) SetSessionIdMaker(maker func() string) {
|
||||||
ws.sessionIdMaker = maker
|
ws.sessionIdMaker = maker
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ func SetAuthChecker(authChecker func(authLevel int, logger *log.Logger, url *str
|
|||||||
DefaultServer.SetAuthChecker(authChecker)
|
DefaultServer.SetAuthChecker(authChecker)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) SetAuthChecker(authChecker func(authLevel int, logger *log.Logger, url *string, in map[string]any, request *Request, response *Response, options *WebServiceOptions) (pass bool, object any)) {
|
func (ws *WebServer) SetAuthChecker(authChecker func(authLevel int, logger *log.Logger, url *string, in map[string]any, request *Request, response *Response, options *WebServiceOptions) (pass bool, object any)) {
|
||||||
ws.webAuthChecker = authChecker
|
ws.webAuthChecker = authChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ func AddAuthChecker(authLevels []int, authChecker func(authLevel int, logger *lo
|
|||||||
DefaultServer.AddAuthChecker(authLevels, authChecker)
|
DefaultServer.AddAuthChecker(authLevels, authChecker)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) AddAuthChecker(authLevels []int, authChecker func(authLevel int, logger *log.Logger, url *string, in map[string]any, request *Request, response *Response, options *WebServiceOptions) (pass bool, object any)) {
|
func (ws *WebServer) AddAuthChecker(authLevels []int, authChecker func(authLevel int, logger *log.Logger, url *string, in map[string]any, request *Request, response *Response, options *WebServiceOptions) (pass bool, object any)) {
|
||||||
for _, al := range authLevels {
|
for _, al := range authLevels {
|
||||||
ws.webAuthCheckers[al] = authChecker
|
ws.webAuthCheckers[al] = authChecker
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ func SetInFilter(filter func(in *map[string]any, request *Request, response *Res
|
|||||||
DefaultServer.SetInFilter(filter)
|
DefaultServer.SetInFilter(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) SetInFilter(filter func(in *map[string]any, request *Request, response *Response, logger *log.Logger) (out any)) {
|
func (ws *WebServer) SetInFilter(filter func(in *map[string]any, request *Request, response *Response, logger *log.Logger) (out any)) {
|
||||||
ws.inFilters = append(ws.inFilters, filter)
|
ws.inFilters = append(ws.inFilters, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ func AddShutdownHook(hook func()) {
|
|||||||
DefaultServer.AddShutdownHook(hook)
|
DefaultServer.AddShutdownHook(hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) AddShutdownHook(hook func()) {
|
func (ws *WebServer) AddShutdownHook(hook func()) {
|
||||||
ws.shutdownHooksLock.Lock()
|
ws.shutdownHooksLock.Lock()
|
||||||
defer ws.shutdownHooksLock.Unlock()
|
defer ws.shutdownHooksLock.Unlock()
|
||||||
ws.shutdownHooks = append(ws.shutdownHooks, hook)
|
ws.shutdownHooks = append(ws.shutdownHooks, hook)
|
||||||
@ -121,7 +121,7 @@ func SetOutFilter(filter func(in map[string]any, request *Request, response *Res
|
|||||||
DefaultServer.SetOutFilter(filter)
|
DefaultServer.SetOutFilter(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) SetOutFilter(filter func(in map[string]any, request *Request, response *Response, out any, logger *log.Logger) (newOut any, isOver bool)) {
|
func (ws *WebServer) SetOutFilter(filter func(in map[string]any, request *Request, response *Response, out any, logger *log.Logger) (newOut any, isOver bool)) {
|
||||||
ws.webServicesLock.Lock()
|
ws.webServicesLock.Lock()
|
||||||
defer ws.webServicesLock.Unlock()
|
defer ws.webServicesLock.Unlock()
|
||||||
ws.outFilters = append(ws.outFilters, filter)
|
ws.outFilters = append(ws.outFilters, filter)
|
||||||
@ -130,7 +130,7 @@ func (ws *webServer) SetOutFilter(filter func(in map[string]any, request *Reques
|
|||||||
|
|
||||||
// HostContext 提供流式服务注册能力
|
// HostContext 提供流式服务注册能力
|
||||||
type HostContext struct {
|
type HostContext struct {
|
||||||
ws *webServer
|
ws *WebServer
|
||||||
host string
|
host string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ func Host(host string) *HostContext {
|
|||||||
return DefaultServer.Host(host)
|
return DefaultServer.Host(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) Host(host string) *HostContext {
|
func (ws *WebServer) Host(host string) *HostContext {
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = "*"
|
host = "*"
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ func Register(method, path string, serviceFunc any) *webServiceType {
|
|||||||
return DefaultServer.Register(method, path, serviceFunc)
|
return DefaultServer.Register(method, path, serviceFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) Register(method, path string, serviceFunc any) *webServiceType {
|
func (ws *WebServer) Register(method, path string, serviceFunc any) *webServiceType {
|
||||||
return ws.Host("*").Register(method, path, serviceFunc)
|
return ws.Host("*").Register(method, path, serviceFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ func RegisterWebsocket(path string, serviceFunc any) *websocketServiceType {
|
|||||||
return DefaultServer.RegisterWebsocket(path, serviceFunc)
|
return DefaultServer.RegisterWebsocket(path, serviceFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) RegisterWebsocket(path string, serviceFunc any) *websocketServiceType {
|
func (ws *WebServer) RegisterWebsocket(path string, serviceFunc any) *websocketServiceType {
|
||||||
return ws.Host("*").WebSocket(path, serviceFunc)
|
return ws.Host("*").WebSocket(path, serviceFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ func Proxy(authLevel int, path string, to string) {
|
|||||||
DefaultServer.Proxy(authLevel, path, to)
|
DefaultServer.Proxy(authLevel, path, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) Proxy(authLevel int, path string, to string) {
|
func (ws *WebServer) Proxy(authLevel int, path string, to string) {
|
||||||
ws.Host("*").Proxy(authLevel, path, to)
|
ws.Host("*").Proxy(authLevel, path, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ func Restful(authLevel int, path string, serviceStruct any) {
|
|||||||
DefaultServer.Restful(authLevel, path, serviceStruct)
|
DefaultServer.Restful(authLevel, path, serviceStruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) Restful(authLevel int, path string, serviceStruct any) {
|
func (ws *WebServer) Restful(authLevel int, path string, serviceStruct any) {
|
||||||
ws.Host("*").Restful(authLevel, path, serviceStruct)
|
ws.Host("*").Restful(authLevel, path, serviceStruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,7 +468,7 @@ func GetInject(dataType reflect.Type) any {
|
|||||||
return DefaultServer.GetInject(dataType)
|
return DefaultServer.GetInject(dataType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) GetInject(dataType reflect.Type) any {
|
func (ws *WebServer) GetInject(dataType reflect.Type) any {
|
||||||
if obj, exists := ws.injectObjects[dataType]; exists {
|
if obj, exists := ws.injectObjects[dataType]; exists {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
@ -497,7 +497,7 @@ func EnableWebDev(config watch.Config) {
|
|||||||
DefaultServer.webDevConfig = config
|
DefaultServer.webDevConfig = config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) initWebDev(logger *log.Logger) {
|
func (ws *WebServer) initWebDev(logger *log.Logger) {
|
||||||
webDevOnce.Do(func() {
|
webDevOnce.Do(func() {
|
||||||
logger.Warning("Web Development Mode Enabled. This should NOT be used in production environment.")
|
logger.Warning("Web Development Mode Enabled. This should NOT be used in production environment.")
|
||||||
onWatchConn := map[string]*WebSocketConn{}
|
onWatchConn := map[string]*WebSocketConn{}
|
||||||
|
|||||||
24
static.go
24
static.go
@ -26,7 +26,7 @@ func Static(path, rootPath string) {
|
|||||||
DefaultServer.Static(path, rootPath)
|
DefaultServer.Static(path, rootPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) Static(path, rootPath string) {
|
func (ws *WebServer) Static(path, rootPath string) {
|
||||||
ws.Host("*").Static(path, rootPath)
|
ws.Host("*").Static(path, rootPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ func StaticByHost(path, rootPath, host string) {
|
|||||||
DefaultServer.StaticByHost(path, rootPath, host)
|
DefaultServer.StaticByHost(path, rootPath, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) StaticByHost(path, rootPath, host string) {
|
func (ws *WebServer) StaticByHost(path, rootPath, host string) {
|
||||||
if !filepath.IsAbs(rootPath) {
|
if !filepath.IsAbs(rootPath) {
|
||||||
if absPath, err := filepath.Abs(rootPath); err == nil {
|
if absPath, err := filepath.Abs(rootPath); err == nil {
|
||||||
rootPath = absPath
|
rootPath = absPath
|
||||||
@ -57,7 +57,7 @@ func ReplaceStatics(host string, config map[string]string) {
|
|||||||
DefaultServer.ReplaceStatics(host, config)
|
DefaultServer.ReplaceStatics(host, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) ReplaceStatics(host string, config map[string]string) {
|
func (ws *WebServer) ReplaceStatics(host string, config map[string]string) {
|
||||||
newStatics := make(map[string]*string, len(config))
|
newStatics := make(map[string]*string, len(config))
|
||||||
for path, rootPath := range config {
|
for path, rootPath := range config {
|
||||||
rp := rootPath
|
rp := rootPath
|
||||||
@ -76,32 +76,30 @@ func (ws *webServer) ReplaceStatics(host string, config map[string]string) {
|
|||||||
ws.rebuildStaticsUnderLock(host)
|
ws.rebuildStaticsUnderLock(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) getStaticFilePath(requestPath, host string) string {
|
func (ws *WebServer) getStaticFilePath(requestPath, host string) string {
|
||||||
requestPath, _ = url.PathUnescape(requestPath)
|
requestPath, _ = url.PathUnescape(requestPath)
|
||||||
ws.staticsByHostLock.RLock()
|
ws.staticsByHostLock.RLock()
|
||||||
defer ws.staticsByHostLock.RUnlock()
|
defer ws.staticsByHostLock.RUnlock()
|
||||||
|
|
||||||
// 优先匹配指定域名的配置
|
// 优先匹配指定域名的配置
|
||||||
if hostConfig, exists := ws.staticsByHost[host]; exists {
|
if filePath := ws.findMatchedPathSorted(ws.hostStatics[host], requestPath); filePath != "" {
|
||||||
if filePath := ws.findMatchedPath(hostConfig, requestPath); filePath != "" {
|
|
||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 匹配全局配置
|
// 匹配全局配置
|
||||||
return ws.findMatchedPath(ws.statics, requestPath)
|
return ws.findMatchedPathSorted(ws.hostStatics[""], requestPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) findMatchedPath(config map[string]*string, requestPath string) string {
|
func (ws *WebServer) findMatchedPathSorted(config []*staticType, requestPath string) string {
|
||||||
for urlPath, rootPath := range config {
|
for _, rule := range config {
|
||||||
if strings.HasPrefix(requestPath, urlPath) {
|
if strings.HasPrefix(requestPath, rule.path) {
|
||||||
return filepath.Join(*rootPath, requestPath[len(urlPath):])
|
return filepath.Join(*rule.rootPath, requestPath[len(rule.path):])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) processStatic(requestPath string, request *Request, response *Response, logger *log.Logger) bool {
|
func (ws *WebServer) processStatic(requestPath string, request *Request, response *Response, logger *log.Logger) bool {
|
||||||
filePath := ws.getStaticFilePath(requestPath, request.Host)
|
filePath := ws.getStaticFilePath(requestPath, request.Host)
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -59,7 +59,7 @@ func Upgrade(response *Response, request *Request) (*WebSocketConn, error) {
|
|||||||
return &WebSocketConn{Conn: conn}, nil
|
return &WebSocketConn{Conn: conn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *webServer) doWebsocketService(wsc *websocketServiceType, request *Request, response *Response, logger *log.Logger, object any) {
|
func (ws *WebServer) doWebsocketService(wsc *websocketServiceType, request *Request, response *Response, logger *log.Logger, object any) {
|
||||||
wsConn, err := Upgrade(response, request)
|
wsConn, err := Upgrade(response, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("websocket upgrade failed", "error", err.Error())
|
logger.Error("websocket upgrade failed", "error", err.Error())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user