Refactor module binding to rely on Type

This commit is contained in:
2025-11-18 16:11:13 +08:00
parent 347eb3adc5
commit fc2c46a9df
10 changed files with 46 additions and 68 deletions

View File

@@ -25,72 +25,49 @@ func normalizePath(_ *hooks.RequestContext, p string, rawQuery []byte) (string,
func cachePolicy(_ *hooks.RequestContext, locatorPath string, current hooks.CachePolicy) hooks.CachePolicy { func cachePolicy(_ *hooks.RequestContext, locatorPath string, current hooks.CachePolicy) hooks.CachePolicy {
clean := canonicalPath(locatorPath) clean := canonicalPath(locatorPath)
switch { if strings.Contains(clean, "/by-hash/") || strings.Contains(clean, "/pool/") {
case isAptIndexPath(clean):
// 索引类Release/Packages需要 If-None-Match/If-Modified-Since 再验证。
current.AllowCache = true
current.AllowStore = true
current.RequireRevalidate = true
case isAptImmutablePath(clean):
// pool/*.deb 与 by-hash 路径视为不可变,直接缓存后续不再 HEAD。 // pool/*.deb 与 by-hash 路径视为不可变,直接缓存后续不再 HEAD。
current.AllowCache = true current.AllowCache = true
current.AllowStore = true current.AllowStore = true
current.RequireRevalidate = false current.RequireRevalidate = false
default: return current
current.AllowCache = false
current.AllowStore = false
current.RequireRevalidate = false
} }
if strings.Contains(clean, "/dists/") {
// 索引类Release/Packages/Contents需要 If-None-Match/If-Modified-Since 再验证。
if strings.HasSuffix(clean, "/release") ||
strings.HasSuffix(clean, "/inrelease") ||
strings.HasSuffix(clean, "/release.gpg") {
current.AllowCache = true
current.AllowStore = true
current.RequireRevalidate = true
return current
}
}
current.AllowCache = false
current.AllowStore = false
current.RequireRevalidate = false
return current return current
} }
func contentType(_ *hooks.RequestContext, locatorPath string) string { func contentType(_ *hooks.RequestContext, locatorPath string) string {
clean := canonicalPath(locatorPath)
switch { switch {
case strings.HasSuffix(locatorPath, ".gz"): case strings.HasSuffix(clean, ".gz"):
return "application/gzip" return "application/gzip"
case strings.HasSuffix(locatorPath, ".xz"): case strings.HasSuffix(clean, ".xz"):
return "application/x-xz" return "application/x-xz"
case strings.HasSuffix(locatorPath, "Release.gpg"): case strings.HasSuffix(clean, "release.gpg"):
return "application/pgp-signature" return "application/pgp-signature"
case isAptIndexPath(locatorPath): case strings.Contains(clean, "/dists/") &&
(strings.HasSuffix(clean, "/release") || strings.HasSuffix(clean, "/inrelease")):
return "text/plain" return "text/plain"
default: default:
return "" return ""
} }
} }
func isAptIndexPath(p string) bool {
clean := canonicalPath(p)
if isByHashPath(clean) {
return false
}
if strings.Contains(clean, "/dists/") {
if strings.HasSuffix(clean, "/release") ||
strings.HasSuffix(clean, "/inrelease") ||
strings.HasSuffix(clean, "/release.gpg") {
return true
}
}
return false
}
func isAptImmutablePath(p string) bool {
clean := canonicalPath(p)
if isByHashPath(clean) {
return true
}
if strings.Contains(clean, "/pool/") {
return true
}
return false
}
func isByHashPath(p string) bool {
clean := canonicalPath(p)
return strings.Contains(clean, "/by-hash/")
}
func canonicalPath(p string) string { func canonicalPath(p string) string {
if p == "" { if p == "" {
return "/" return "/"

View File

@@ -35,7 +35,7 @@ func RegisterModuleHandler(key string, handler server.ProxyHandler) {
MustRegisterModule(ModuleRegistration{Key: key, Handler: handler}) MustRegisterModule(ModuleRegistration{Key: key, Handler: handler})
} }
// Handle 实现 server.ProxyHandler根据 route.ModuleKey 选择 handler。 // Handle 实现 server.ProxyHandler根据 route.Module.Key 选择 handler。
func (f *Forwarder) Handle(c fiber.Ctx, route *server.HubRoute) error { func (f *Forwarder) Handle(c fiber.Ctx, route *server.HubRoute) error {
requestID := server.RequestID(c) requestID := server.RequestID(c)
handler := f.lookup(route) handler := f.lookup(route)
@@ -90,7 +90,7 @@ func (f *Forwarder) logModuleError(route *server.HubRoute, code string, err erro
func (f *Forwarder) lookup(route *server.HubRoute) server.ProxyHandler { func (f *Forwarder) lookup(route *server.HubRoute) server.ProxyHandler {
if route != nil { if route != nil {
if handler := lookupModuleHandler(route.ModuleKey); handler != nil { if handler := lookupModuleHandler(route.Module.Key); handler != nil {
return handler return handler
} }
} }
@@ -131,7 +131,7 @@ func (f *Forwarder) routeFields(route *server.HubRoute, requestID string) logrus
route.Config.Domain, route.Config.Domain,
route.Config.Type, route.Config.Type,
route.Config.AuthMode(), route.Config.AuthMode(),
route.ModuleKey, route.Module.Key,
false, false,
) )
if requestID != "" { if requestID != "" {

View File

@@ -10,6 +10,7 @@ import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"github.com/any-hub/any-hub/internal/config" "github.com/any-hub/any-hub/internal/config"
"github.com/any-hub/any-hub/internal/hubmodule"
"github.com/any-hub/any-hub/internal/server" "github.com/any-hub/any-hub/internal/server"
) )
@@ -102,6 +103,8 @@ func testRouteWithModule(moduleKey string) *server.HubRoute {
Domain: "test.local", Domain: "test.local",
Type: "custom", Type: "custom",
}, },
ModuleKey: moduleKey, Module: hubmodule.ModuleMetadata{
Key: moduleKey,
},
} }
} }

View File

@@ -65,7 +65,7 @@ func buildHookContext(route *server.HubRoute, c fiber.Ctx) *hooks.RequestContext
HubName: route.Config.Name, HubName: route.Config.Name,
Domain: route.Config.Domain, Domain: route.Config.Domain,
HubType: route.Config.Type, HubType: route.Config.Type,
ModuleKey: route.ModuleKey, ModuleKey: route.Module.Key,
UpstreamHost: baseHost, UpstreamHost: baseHost,
Method: c.Method(), Method: c.Method(),
} }
@@ -83,7 +83,7 @@ func hasHook(def hooks.Hooks) bool {
func (h *Handler) Handle(c fiber.Ctx, route *server.HubRoute) error { func (h *Handler) Handle(c fiber.Ctx, route *server.HubRoute) error {
started := time.Now() started := time.Now()
requestID := server.RequestID(c) requestID := server.RequestID(c)
hooksDef, ok := hooks.Fetch(route.ModuleKey) hooksDef, ok := hooks.Fetch(route.Module.Key)
hookCtx := buildHookContext(route, c) hookCtx := buildHookContext(route, c)
rawQuery := append([]byte(nil), c.Request().URI().QueryString()...) rawQuery := append([]byte(nil), c.Request().URI().QueryString()...)
cleanPath := normalizeRequestPath(route, string(c.Request().URI().Path())) cleanPath := normalizeRequestPath(route, string(c.Request().URI().Path()))
@@ -120,7 +120,7 @@ func (h *Handler) Handle(c fiber.Ctx, route *server.HubRoute) error {
// miss, continue // miss, continue
default: default:
h.logger.WithError(err). h.logger.WithError(err).
WithFields(logrus.Fields{"hub": route.Config.Name, "module_key": route.ModuleKey}). WithFields(logrus.Fields{"hub": route.Config.Name, "module_key": route.Module.Key}).
Warn("cache_get_failed") Warn("cache_get_failed")
} }
} }
@@ -134,7 +134,7 @@ func (h *Handler) Handle(c fiber.Ctx, route *server.HubRoute) error {
fresh, err := h.isCacheFresh(c, route, locator, cached.Entry, &hookState) fresh, err := h.isCacheFresh(c, route, locator, cached.Entry, &hookState)
if err != nil { if err != nil {
h.logger.WithError(err). h.logger.WithError(err).
WithFields(logrus.Fields{"hub": route.Config.Name, "module_key": route.ModuleKey}). WithFields(logrus.Fields{"hub": route.Config.Name, "module_key": route.Module.Key}).
Warn("cache_revalidate_failed") Warn("cache_revalidate_failed")
serve = false serve = false
} else if !fresh { } else if !fresh {
@@ -517,7 +517,7 @@ func (h *Handler) logResult(
route.Config.Domain, route.Config.Domain,
route.Config.Type, route.Config.Type,
route.Config.AuthMode(), route.Config.AuthMode(),
route.ModuleKey, route.Module.Key,
cacheHit, cacheHit,
) )
fields["action"] = "proxy" fields["action"] = "proxy"
@@ -968,7 +968,7 @@ func (h *Handler) logAuthRetry(route *server.HubRoute, upstream string, requestI
route.Config.Domain, route.Config.Domain,
route.Config.Type, route.Config.Type,
route.Config.AuthMode(), route.Config.AuthMode(),
route.ModuleKey, route.Module.Key,
false, false,
) )
fields["action"] = "proxy_retry" fields["action"] = "proxy_retry"
@@ -987,7 +987,7 @@ func (h *Handler) logAuthFailure(route *server.HubRoute, upstream string, reques
route.Config.Domain, route.Config.Domain,
route.Config.Type, route.Config.Type,
route.Config.AuthMode(), route.Config.AuthMode(),
route.ModuleKey, route.Module.Key,
false, false,
) )
fields["action"] = "proxy" fields["action"] = "proxy"

View File

@@ -25,9 +25,8 @@ type HubRoute struct {
// UpstreamURL/ProxyURL 在构造 Registry 时提前解析完成,便于后续请求快速复用。 // UpstreamURL/ProxyURL 在构造 Registry 时提前解析完成,便于后续请求快速复用。
UpstreamURL *url.URL UpstreamURL *url.URL
ProxyURL *url.URL ProxyURL *url.URL
// ModuleKey/Module 记录当前 hub 选用的模块及其元数据,便于日志与观测。 // Module 记录当前 hub 选用的模块元数据,便于日志与观测。
ModuleKey string Module hubmodule.ModuleMetadata
Module hubmodule.ModuleMetadata
// CacheStrategy 代表模块默认策略与 hub 覆盖后的最终结果。 // CacheStrategy 代表模块默认策略与 hub 覆盖后的最终结果。
CacheStrategy hubmodule.CacheStrategyProfile CacheStrategy hubmodule.CacheStrategyProfile
} }
@@ -134,7 +133,6 @@ func buildHubRoute(cfg *config.Config, hub config.HubConfig) (*HubRoute, error)
CacheTTL: effectiveTTL, CacheTTL: effectiveTTL,
UpstreamURL: upstreamURL, UpstreamURL: upstreamURL,
ProxyURL: proxyURL, ProxyURL: proxyURL,
ModuleKey: runtime.Module.Key,
Module: runtime.Module, Module: runtime.Module,
CacheStrategy: runtime.CacheStrategy, CacheStrategy: runtime.CacheStrategy,
}, nil }, nil

View File

@@ -54,8 +54,8 @@ func TestHubRegistryLookupByHost(t *testing.T) {
if route.CacheStrategy.ValidationMode == "" { if route.CacheStrategy.ValidationMode == "" {
t.Fatalf("cache strategy validation mode should not be empty") t.Fatalf("cache strategy validation mode should not be empty")
} }
if route.ModuleKey != "docker" { if route.Module.Key != "docker" {
t.Fatalf("expected docker module, got %s", route.ModuleKey) t.Fatalf("expected docker module, got %s", route.Module.Key)
} }
if route.UpstreamURL.String() != "https://registry-1.docker.io" { if route.UpstreamURL.String() != "https://registry-1.docker.io" {

View File

@@ -113,7 +113,7 @@ func encodeHubBindings(routes []server.HubRoute) []hubBindingPayload {
for _, route := range routes { for _, route := range routes {
result = append(result, hubBindingPayload{ result = append(result, hubBindingPayload{
HubName: route.Config.Name, HubName: route.Config.Name,
ModuleKey: route.ModuleKey, ModuleKey: route.Module.Key,
Domain: route.Config.Domain, Domain: route.Config.Domain,
Port: route.ListenPort, Port: route.ListenPort,
}) })

View File

@@ -14,7 +14,7 @@
- **Proxy Dispatcher** - **Proxy Dispatcher**
- Attributes: handler map (module_key → handler), default handler fallback. - Attributes: handler map (module_key → handler), default handler fallback.
- Behavior: Lookup by route.ModuleKey and invoke handler; wraps errors/logging. - Behavior: Lookup by the hub's module key (derived from Type / route.Module.Key) and invoke handler; wraps errors/logging.
- Constraints: If handler missing, return 5xx with observable logging. - Constraints: If handler missing, return 5xx with observable logging.
- **Cache Policy** - **Cache Policy**

View File

@@ -27,7 +27,7 @@
## Relationships ## Relationships
- Hub module 注册时同时在 HookRegistry 与 Forwarder handler map 建立关联。 - Hub module 注册时同时在 HookRegistry 与 Forwarder handler map 建立关联。
- ProxyDispatcher 在请求进入后根据 route.ModuleKey 查询 Hook + handler。 - ProxyDispatcher 在请求进入后根据 route.Module.Key(来自 Hub Type查询 Hook + handler。
- Diagnostics 依赖 HookRegistry 与 HubRegistry 联合输出状态。 - Diagnostics 依赖 HookRegistry 与 HubRegistry 联合输出状态。
## Lifecycle ## Lifecycle

View File

@@ -95,6 +95,6 @@ type moduleRecorder struct {
func (p *moduleRecorder) Handle(c fiber.Ctx, route *server.HubRoute) error { func (p *moduleRecorder) Handle(c fiber.Ctx, route *server.HubRoute) error {
p.routeName = route.Config.Name p.routeName = route.Config.Name
p.moduleKey = route.ModuleKey p.moduleKey = route.Module.Key
return c.SendStatus(fiber.StatusNoContent) return c.SendStatus(fiber.StatusNoContent)
} }