feat: 004/phase 1
This commit is contained in:
@@ -5,10 +5,13 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/any-hub/any-hub/internal/hubmodule"
|
||||
)
|
||||
|
||||
// Load 读取并解析 TOML 配置文件,同时注入默认值与校验逻辑。
|
||||
@@ -86,6 +89,14 @@ func applyHubDefaults(h *HubConfig) {
|
||||
if h.CacheTTL.DurationValue() < 0 {
|
||||
h.CacheTTL = Duration(0)
|
||||
}
|
||||
if trimmed := strings.TrimSpace(h.Module); trimmed == "" {
|
||||
h.Module = hubmodule.DefaultModuleKey()
|
||||
} else {
|
||||
h.Module = strings.ToLower(trimmed)
|
||||
}
|
||||
if h.ValidationMode == "" {
|
||||
h.ValidationMode = string(hubmodule.ValidationModeETag)
|
||||
}
|
||||
}
|
||||
|
||||
func durationDecodeHook() mapstructure.DecodeHookFunc {
|
||||
|
||||
3
internal/config/modules.go
Normal file
3
internal/config/modules.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package config
|
||||
|
||||
import _ "github.com/any-hub/any-hub/internal/hubmodule/legacy"
|
||||
25
internal/config/runtime.go
Normal file
25
internal/config/runtime.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/any-hub/any-hub/internal/hubmodule"
|
||||
)
|
||||
|
||||
// HubRuntime 将 Hub 配置与模块元数据合并,方便运行时快速取用策略。
|
||||
type HubRuntime struct {
|
||||
Config HubConfig
|
||||
Module hubmodule.ModuleMetadata
|
||||
CacheStrategy hubmodule.CacheStrategyProfile
|
||||
}
|
||||
|
||||
// BuildHubRuntime 根据 Hub 配置和模块元数据创建运行时描述。
|
||||
func BuildHubRuntime(cfg HubConfig, meta hubmodule.ModuleMetadata) HubRuntime {
|
||||
strategy := hubmodule.ResolveStrategy(meta, hubmodule.StrategyOptions{
|
||||
TTLOverride: cfg.CacheTTL.DurationValue(),
|
||||
ValidationOverride: hubmodule.ValidationMode(cfg.ValidationMode),
|
||||
})
|
||||
return HubRuntime{
|
||||
Config: cfg,
|
||||
Module: meta,
|
||||
CacheStrategy: strategy,
|
||||
}
|
||||
}
|
||||
@@ -67,9 +67,11 @@ type HubConfig struct {
|
||||
Upstream string `mapstructure:"Upstream"`
|
||||
Proxy string `mapstructure:"Proxy"`
|
||||
Type string `mapstructure:"Type"`
|
||||
Module string `mapstructure:"Module"`
|
||||
Username string `mapstructure:"Username"`
|
||||
Password string `mapstructure:"Password"`
|
||||
CacheTTL Duration `mapstructure:"CacheTTL"`
|
||||
ValidationMode string `mapstructure:"ValidationMode"`
|
||||
EnableHeadCheck bool `mapstructure:"EnableHeadCheck"`
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/any-hub/any-hub/internal/hubmodule"
|
||||
)
|
||||
|
||||
var supportedHubTypes = map[string]struct{}{
|
||||
@@ -74,6 +76,24 @@ func (c *Config) Validate() error {
|
||||
}
|
||||
hub.Type = normalizedType
|
||||
|
||||
moduleKey := strings.ToLower(strings.TrimSpace(hub.Module))
|
||||
if moduleKey == "" {
|
||||
moduleKey = hubmodule.DefaultModuleKey()
|
||||
}
|
||||
if _, ok := hubmodule.Resolve(moduleKey); !ok {
|
||||
return newFieldError(hubField(hub.Name, "Module"), fmt.Sprintf("未注册模块: %s", moduleKey))
|
||||
}
|
||||
hub.Module = moduleKey
|
||||
if hub.ValidationMode != "" {
|
||||
mode := strings.ToLower(strings.TrimSpace(hub.ValidationMode))
|
||||
switch mode {
|
||||
case string(hubmodule.ValidationModeETag), string(hubmodule.ValidationModeLastModified), string(hubmodule.ValidationModeNever):
|
||||
hub.ValidationMode = mode
|
||||
default:
|
||||
return newFieldError(hubField(hub.Name, "ValidationMode"), "仅支持 etag/last-modified/never")
|
||||
}
|
||||
}
|
||||
|
||||
if (hub.Username == "") != (hub.Password == "") {
|
||||
return newFieldError(hubField(hub.Name, "Username/Password"), "必须同时提供或同时留空")
|
||||
}
|
||||
|
||||
32
internal/hubmodule/README.md
Normal file
32
internal/hubmodule/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# hubmodule
|
||||
|
||||
集中定义和实现 Any-Hub 的“代理 + 缓存”模块体系。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
internal/hubmodule/
|
||||
├── doc.go # 包级说明与约束
|
||||
├── README.md # 本文件
|
||||
├── registry.go # 模块注册/发现入口(后续任务)
|
||||
└── <module-key>/ # 各仓类型模块,例如 legacy、npm、docker
|
||||
```
|
||||
|
||||
## 模块约束
|
||||
- **单一接口**:每个模块需要同时实现代理与缓存接口,避免跨包耦合。
|
||||
- **注册流程**:在模块 `init()` 中调用 `hubmodule.Register(ModuleMetadata{...})`,注册失败必须 panic 以阻止启动。
|
||||
- **缓存布局**:一律使用 `StoragePath/<Hub>/<path>.body`,如需附加目录需在 `ModuleMetadata` 中声明迁移策略。
|
||||
- **配置注入**:模块仅通过依赖注入获取 `HubConfigEntry` 和全局参数,禁止直接读取文件或环境变量。
|
||||
- **可观测性**:所有模块必须输出 `module_key`、命中/回源状态等日志字段,并在返回错误时附带 Hub 名称。
|
||||
|
||||
## 开发流程
|
||||
1. 复制 `internal/hubmodule/template/`(由 T010 提供)作为起点。
|
||||
2. 填写模块特有逻辑与缓存策略,并确保包含中文注释解释设计。
|
||||
3. 在模块目录添加 `module_test.go`,使用 `httptest.Server` 与 `t.TempDir()` 复现真实流量。
|
||||
4. 运行 `make modules-test` 验证模块单元测试。
|
||||
5. 更新 `config.toml` 中对应 `[[Hub]].Module` 字段,验证集成测试后再提交。
|
||||
|
||||
## 术语
|
||||
- **Module Key**:模块唯一标识(如 `legacy`、`npm-tarball`)。
|
||||
- **Cache Strategy Profile**:定义 TTL、验证策略、磁盘布局等策略元数据。
|
||||
- **Legacy Adapter**:包装当前共享实现,确保迁移期间仍可运行。
|
||||
9
internal/hubmodule/doc.go
Normal file
9
internal/hubmodule/doc.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Package hubmodule 聚合任意仓类型的代理 + 缓存模块,并提供统一的注册入口。
|
||||
//
|
||||
// 模块作者需要:
|
||||
// 1. 在 internal/hubmodule/<module-key>/ 目录下实现代理与缓存接口;
|
||||
// 2. 通过本包暴露的 Register 函数在 init() 中注册模块元数据;
|
||||
// 3. 保证缓存写入仍遵循 StoragePath/<Hub>/<path>.body 布局,并补充中文注释说明实现细节。
|
||||
//
|
||||
// 该包同时负责提供模块发现、可观测信息以及迁移状态的对外查询能力。
|
||||
package hubmodule
|
||||
44
internal/hubmodule/interfaces.go
Normal file
44
internal/hubmodule/interfaces.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package hubmodule
|
||||
|
||||
import "time"
|
||||
|
||||
// MigrationState 描述模块上线阶段,方便观测端区分 legacy/beta/ga。
|
||||
type MigrationState string
|
||||
|
||||
const (
|
||||
MigrationStateLegacy MigrationState = "legacy"
|
||||
MigrationStateBeta MigrationState = "beta"
|
||||
MigrationStateGA MigrationState = "ga"
|
||||
)
|
||||
|
||||
// ValidationMode 描述缓存再验证的默认策略。
|
||||
type ValidationMode string
|
||||
|
||||
const (
|
||||
ValidationModeETag ValidationMode = "etag"
|
||||
ValidationModeLastModified ValidationMode = "last-modified"
|
||||
ValidationModeNever ValidationMode = "never"
|
||||
)
|
||||
|
||||
// CacheStrategyProfile 描述模块的缓存读写策略及其默认值。
|
||||
type CacheStrategyProfile struct {
|
||||
TTLHint time.Duration
|
||||
ValidationMode ValidationMode
|
||||
DiskLayout string
|
||||
RequiresMetadataFile bool
|
||||
SupportsStreamingWrite bool
|
||||
}
|
||||
|
||||
// ModuleMetadata 记录一个模块的静态信息,供配置校验和诊断端使用。
|
||||
type ModuleMetadata struct {
|
||||
Key string
|
||||
Description string
|
||||
MigrationState MigrationState
|
||||
SupportedProtocols []string
|
||||
CacheStrategy CacheStrategyProfile
|
||||
}
|
||||
|
||||
// DefaultModuleKey 返回内置 legacy 模块的键值。
|
||||
func DefaultModuleKey() string {
|
||||
return defaultModuleKey
|
||||
}
|
||||
20
internal/hubmodule/legacy/legacy_module.go
Normal file
20
internal/hubmodule/legacy/legacy_module.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package legacy
|
||||
|
||||
import "github.com/any-hub/any-hub/internal/hubmodule"
|
||||
|
||||
// 模块描述:包装当前共享的代理 + 缓存实现,供未迁移的 Hub 使用。
|
||||
func init() {
|
||||
hubmodule.MustRegister(hubmodule.ModuleMetadata{
|
||||
Key: hubmodule.DefaultModuleKey(),
|
||||
Description: "Legacy proxy + cache implementation bundled with any-hub",
|
||||
MigrationState: hubmodule.MigrationStateLegacy,
|
||||
SupportedProtocols: []string{
|
||||
"docker", "npm", "go", "pypi",
|
||||
},
|
||||
CacheStrategy: hubmodule.CacheStrategyProfile{
|
||||
DiskLayout: ".body",
|
||||
ValidationMode: hubmodule.ValidationModeETag,
|
||||
SupportsStreamingWrite: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
117
internal/hubmodule/registry.go
Normal file
117
internal/hubmodule/registry.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package hubmodule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const defaultModuleKey = "legacy"
|
||||
|
||||
var globalRegistry = newRegistry()
|
||||
|
||||
type registry struct {
|
||||
mu sync.RWMutex
|
||||
modules map[string]ModuleMetadata
|
||||
}
|
||||
|
||||
func newRegistry() *registry {
|
||||
return ®istry{modules: make(map[string]ModuleMetadata)}
|
||||
}
|
||||
|
||||
// Register 将模块元数据加入全局注册表,重复键会返回错误。
|
||||
func Register(meta ModuleMetadata) error {
|
||||
return globalRegistry.register(meta)
|
||||
}
|
||||
|
||||
// MustRegister 在注册失败时 panic,适合模块 init() 中调用。
|
||||
func MustRegister(meta ModuleMetadata) {
|
||||
if err := Register(meta); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve 返回指定键的模块元数据。
|
||||
func Resolve(key string) (ModuleMetadata, bool) {
|
||||
return globalRegistry.resolve(key)
|
||||
}
|
||||
|
||||
// List 返回按键排序的模块元数据列表。
|
||||
func List() []ModuleMetadata {
|
||||
return globalRegistry.list()
|
||||
}
|
||||
|
||||
// Keys 返回所有已注册模块的键值,供调试或诊断使用。
|
||||
func Keys() []string {
|
||||
items := List()
|
||||
result := make([]string, len(items))
|
||||
for i, meta := range items {
|
||||
result[i] = meta.Key
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *registry) normalizeKey(key string) string {
|
||||
return strings.ToLower(strings.TrimSpace(key))
|
||||
}
|
||||
|
||||
func (r *registry) register(meta ModuleMetadata) error {
|
||||
if meta.Key == "" {
|
||||
return fmt.Errorf("module key is required")
|
||||
}
|
||||
key := r.normalizeKey(meta.Key)
|
||||
if key == "" {
|
||||
return fmt.Errorf("module key is required")
|
||||
}
|
||||
meta.Key = key
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if _, exists := r.modules[key]; exists {
|
||||
return fmt.Errorf("module %s already registered", key)
|
||||
}
|
||||
r.modules[key] = meta
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *registry) mustRegister(meta ModuleMetadata) {
|
||||
if err := r.register(meta); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) resolve(key string) (ModuleMetadata, bool) {
|
||||
if key == "" {
|
||||
return ModuleMetadata{}, false
|
||||
}
|
||||
normalized := r.normalizeKey(key)
|
||||
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
meta, ok := r.modules[normalized]
|
||||
return meta, ok
|
||||
}
|
||||
|
||||
func (r *registry) list() []ModuleMetadata {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if len(r.modules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(r.modules))
|
||||
for key := range r.modules {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
result := make([]ModuleMetadata, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
result = append(result, r.modules[key])
|
||||
}
|
||||
return result
|
||||
}
|
||||
49
internal/hubmodule/registry_test.go
Normal file
49
internal/hubmodule/registry_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package hubmodule
|
||||
|
||||
import "testing"
|
||||
|
||||
func replaceRegistry(t *testing.T) func() {
|
||||
t.Helper()
|
||||
prev := globalRegistry
|
||||
globalRegistry = newRegistry()
|
||||
return func() { globalRegistry = prev }
|
||||
}
|
||||
|
||||
func TestRegisterResolveAndList(t *testing.T) {
|
||||
cleanup := replaceRegistry(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := Register(ModuleMetadata{Key: "beta", MigrationState: MigrationStateBeta}); err != nil {
|
||||
t.Fatalf("register beta failed: %v", err)
|
||||
}
|
||||
if err := Register(ModuleMetadata{Key: "gamma", MigrationState: MigrationStateGA}); err != nil {
|
||||
t.Fatalf("register gamma failed: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := Resolve("beta"); !ok {
|
||||
t.Fatalf("expected beta to resolve")
|
||||
}
|
||||
if _, ok := Resolve("BETA"); !ok {
|
||||
t.Fatalf("resolve should be case-insensitive")
|
||||
}
|
||||
|
||||
list := List()
|
||||
if len(list) != 2 {
|
||||
t.Fatalf("list length mismatch: %d", len(list))
|
||||
}
|
||||
if list[0].Key != "beta" || list[1].Key != "gamma" {
|
||||
t.Fatalf("unexpected order: %+v", list)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterDuplicateFails(t *testing.T) {
|
||||
cleanup := replaceRegistry(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := Register(ModuleMetadata{Key: "legacy"}); err != nil {
|
||||
t.Fatalf("first registration should succeed: %v", err)
|
||||
}
|
||||
if err := Register(ModuleMetadata{Key: "legacy"}); err == nil {
|
||||
t.Fatalf("duplicate registration should fail")
|
||||
}
|
||||
}
|
||||
21
internal/hubmodule/strategy.go
Normal file
21
internal/hubmodule/strategy.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package hubmodule
|
||||
|
||||
import "time"
|
||||
|
||||
// StrategyOptions 描述来自 Hub Config 的 override。
|
||||
type StrategyOptions struct {
|
||||
TTLOverride time.Duration
|
||||
ValidationOverride ValidationMode
|
||||
}
|
||||
|
||||
// ResolveStrategy 将模块的默认策略与 hub 级覆盖合并。
|
||||
func ResolveStrategy(meta ModuleMetadata, opts StrategyOptions) CacheStrategyProfile {
|
||||
strategy := meta.CacheStrategy
|
||||
if opts.TTLOverride > 0 {
|
||||
strategy.TTLHint = opts.TTLOverride
|
||||
}
|
||||
if opts.ValidationOverride != "" {
|
||||
strategy.ValidationMode = opts.ValidationOverride
|
||||
}
|
||||
return strategy
|
||||
}
|
||||
13
internal/hubmodule/template/module.go
Normal file
13
internal/hubmodule/template/module.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package template
|
||||
|
||||
import "github.com/any-hub/any-hub/internal/hubmodule"
|
||||
|
||||
// Package template 提供编写新模块时可复制的骨架示例。
|
||||
//
|
||||
// 使用方式:复制整个目录到 internal/hubmodule/<module-key>/ 并替换字段。
|
||||
// - 将 TemplateModule 重命名为实际模块类型。
|
||||
// - 在 init() 中调用 hubmodule.MustRegister,注册新的 ModuleMetadata。
|
||||
// - 在模块目录中实现自定义代理/缓存逻辑,然后在 main 中调用 proxy.RegisterModuleHandler。
|
||||
//
|
||||
// 注意:本文件仅示例 metadata 注册写法,不会参与编译。
|
||||
var _ = hubmodule.ModuleMetadata{}
|
||||
@@ -11,12 +11,13 @@ func BaseFields(action, configPath string) logrus.Fields {
|
||||
}
|
||||
|
||||
// RequestFields 提供 hub/domain/命中状态字段,供代理请求日志复用。
|
||||
func RequestFields(hub, domain, hubType, authMode string, cacheHit bool) logrus.Fields {
|
||||
func RequestFields(hub, domain, hubType, authMode, moduleKey string, cacheHit bool) logrus.Fields {
|
||||
return logrus.Fields{
|
||||
"hub": hub,
|
||||
"domain": domain,
|
||||
"hub_type": hubType,
|
||||
"auth_mode": authMode,
|
||||
"cache_hit": cacheHit,
|
||||
"hub": hub,
|
||||
"domain": domain,
|
||||
"hub_type": hubType,
|
||||
"auth_mode": authMode,
|
||||
"cache_hit": cacheHit,
|
||||
"module_key": moduleKey,
|
||||
}
|
||||
}
|
||||
|
||||
68
internal/proxy/forwarder.go
Normal file
68
internal/proxy/forwarder.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
|
||||
"github.com/any-hub/any-hub/internal/server"
|
||||
)
|
||||
|
||||
// Forwarder 根据 HubRoute 的 module_key 选择对应的 ProxyHandler,默认回退到构造时注入的 handler。
|
||||
type Forwarder struct {
|
||||
defaultHandler server.ProxyHandler
|
||||
}
|
||||
|
||||
// NewForwarder 创建 Forwarder,defaultHandler 不能为空。
|
||||
func NewForwarder(defaultHandler server.ProxyHandler) *Forwarder {
|
||||
return &Forwarder{defaultHandler: defaultHandler}
|
||||
}
|
||||
|
||||
var (
|
||||
moduleHandlers sync.Map
|
||||
)
|
||||
|
||||
// RegisterModuleHandler 将特定 module_key 映射到 ProxyHandler,重复注册会覆盖旧值。
|
||||
func RegisterModuleHandler(key string, handler server.ProxyHandler) {
|
||||
normalized := normalizeModuleKey(key)
|
||||
if normalized == "" || handler == nil {
|
||||
return
|
||||
}
|
||||
moduleHandlers.Store(normalized, handler)
|
||||
}
|
||||
|
||||
// Handle 实现 server.ProxyHandler,根据 route.ModuleKey 选择 handler。
|
||||
func (f *Forwarder) Handle(c fiber.Ctx, route *server.HubRoute) error {
|
||||
handler := f.lookup(route)
|
||||
if handler == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "proxy handler unavailable")
|
||||
}
|
||||
return handler.Handle(c, route)
|
||||
}
|
||||
|
||||
func (f *Forwarder) lookup(route *server.HubRoute) server.ProxyHandler {
|
||||
if route != nil {
|
||||
if handler := lookupModuleHandler(route.ModuleKey); handler != nil {
|
||||
return handler
|
||||
}
|
||||
}
|
||||
return f.defaultHandler
|
||||
}
|
||||
|
||||
func lookupModuleHandler(key string) server.ProxyHandler {
|
||||
normalized := normalizeModuleKey(key)
|
||||
if normalized == "" {
|
||||
return nil
|
||||
}
|
||||
if value, ok := moduleHandlers.Load(normalized); ok {
|
||||
if handler, ok := value.(server.ProxyHandler); ok {
|
||||
return handler
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeModuleKey(key string) string {
|
||||
return strings.ToLower(strings.TrimSpace(key))
|
||||
}
|
||||
@@ -49,7 +49,10 @@ func (h *Handler) Handle(c fiber.Ctx, route *server.HubRoute) error {
|
||||
policy := determineCachePolicy(route, locator, c.Method())
|
||||
|
||||
if err := ensureProxyHubType(route); err != nil {
|
||||
h.logger.WithField("hub", route.Config.Name).WithError(err).Error("hub_type_unsupported")
|
||||
h.logger.WithFields(logrus.Fields{
|
||||
"hub": route.Config.Name,
|
||||
"module_key": route.ModuleKey,
|
||||
}).WithError(err).Error("hub_type_unsupported")
|
||||
return h.writeError(c, fiber.StatusNotImplemented, "hub_type_unsupported")
|
||||
}
|
||||
|
||||
@@ -67,7 +70,9 @@ func (h *Handler) Handle(c fiber.Ctx, route *server.HubRoute) error {
|
||||
case errors.Is(err, cache.ErrNotFound):
|
||||
// miss, continue
|
||||
default:
|
||||
h.logger.WithError(err).WithField("hub", route.Config.Name).Warn("cache_get_failed")
|
||||
h.logger.WithError(err).
|
||||
WithFields(logrus.Fields{"hub": route.Config.Name, "module_key": route.ModuleKey}).
|
||||
Warn("cache_get_failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +81,9 @@ func (h *Handler) Handle(c fiber.Ctx, route *server.HubRoute) error {
|
||||
if policy.requireRevalidate {
|
||||
fresh, err := h.isCacheFresh(c, route, locator, cached.Entry)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).WithField("hub", route.Config.Name).Warn("cache_revalidate_failed")
|
||||
h.logger.WithError(err).
|
||||
WithFields(logrus.Fields{"hub": route.Config.Name, "module_key": route.ModuleKey}).
|
||||
Warn("cache_revalidate_failed")
|
||||
serve = false
|
||||
} else if !fresh {
|
||||
serve = false
|
||||
@@ -316,7 +323,7 @@ func (h *Handler) writeError(c fiber.Ctx, status int, code string) error {
|
||||
}
|
||||
|
||||
func (h *Handler) logResult(route *server.HubRoute, upstream string, requestID string, status int, cacheHit bool, started time.Time, err error) {
|
||||
fields := logging.RequestFields(route.Config.Name, route.Config.Domain, route.Config.Type, route.Config.AuthMode(), cacheHit)
|
||||
fields := logging.RequestFields(route.Config.Name, route.Config.Domain, route.Config.Type, route.Config.AuthMode(), route.ModuleKey, cacheHit)
|
||||
fields["action"] = "proxy"
|
||||
fields["upstream"] = upstream
|
||||
fields["upstream_status"] = status
|
||||
@@ -800,7 +807,7 @@ func isAuthFailure(status int) bool {
|
||||
}
|
||||
|
||||
func (h *Handler) logAuthRetry(route *server.HubRoute, upstream string, requestID string, status int) {
|
||||
fields := logging.RequestFields(route.Config.Name, route.Config.Domain, route.Config.Type, route.Config.AuthMode(), false)
|
||||
fields := logging.RequestFields(route.Config.Name, route.Config.Domain, route.Config.Type, route.Config.AuthMode(), route.ModuleKey, false)
|
||||
fields["action"] = "proxy_retry"
|
||||
fields["upstream"] = upstream
|
||||
fields["upstream_status"] = status
|
||||
@@ -812,7 +819,7 @@ func (h *Handler) logAuthRetry(route *server.HubRoute, upstream string, requestI
|
||||
}
|
||||
|
||||
func (h *Handler) logAuthFailure(route *server.HubRoute, upstream string, requestID string, status int) {
|
||||
fields := logging.RequestFields(route.Config.Name, route.Config.Domain, route.Config.Type, route.Config.AuthMode(), false)
|
||||
fields := logging.RequestFields(route.Config.Name, route.Config.Domain, route.Config.Type, route.Config.AuthMode(), route.ModuleKey, false)
|
||||
fields["action"] = "proxy"
|
||||
fields["upstream"] = upstream
|
||||
fields["upstream_status"] = status
|
||||
|
||||
15
internal/server/bootstrap.go
Normal file
15
internal/server/bootstrap.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/any-hub/any-hub/internal/config"
|
||||
"github.com/any-hub/any-hub/internal/hubmodule"
|
||||
)
|
||||
|
||||
func moduleMetadataForHub(hub config.HubConfig) (hubmodule.ModuleMetadata, error) {
|
||||
if meta, ok := hubmodule.Resolve(hub.Module); ok {
|
||||
return meta, nil
|
||||
}
|
||||
return hubmodule.ModuleMetadata{}, fmt.Errorf("module %s is not registered", hub.Module)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/any-hub/any-hub/internal/config"
|
||||
"github.com/any-hub/any-hub/internal/hubmodule"
|
||||
)
|
||||
|
||||
// HubRoute 将 Hub 配置与派生属性(如缓存 TTL、解析后的 Upstream/Proxy URL)
|
||||
@@ -24,6 +25,9 @@ type HubRoute struct {
|
||||
// UpstreamURL/ProxyURL 在构造 Registry 时提前解析完成,便于后续请求快速复用。
|
||||
UpstreamURL *url.URL
|
||||
ProxyURL *url.URL
|
||||
// ModuleKey/Module 记录当前 hub 选用的模块及其元数据,便于日志与观测。
|
||||
ModuleKey string
|
||||
Module hubmodule.ModuleMetadata
|
||||
}
|
||||
|
||||
// HubRegistry 提供 Host/Host:port 到 HubRoute 的查询能力,所有 Hub 共享同一个监听端口。
|
||||
@@ -96,6 +100,11 @@ func (r *HubRegistry) List() []HubRoute {
|
||||
}
|
||||
|
||||
func buildHubRoute(cfg *config.Config, hub config.HubConfig) (*HubRoute, error) {
|
||||
meta, err := moduleMetadataForHub(hub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hub %s: %w", hub.Name, err)
|
||||
}
|
||||
|
||||
upstreamURL, err := url.Parse(hub.Upstream)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid upstream for hub %s: %w", hub.Name, err)
|
||||
@@ -115,6 +124,8 @@ func buildHubRoute(cfg *config.Config, hub config.HubConfig) (*HubRoute, error)
|
||||
CacheTTL: cfg.EffectiveCacheTTL(hub),
|
||||
UpstreamURL: upstreamURL,
|
||||
ProxyURL: proxyURL,
|
||||
ModuleKey: meta.Key,
|
||||
Module: meta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user