Middleware returns a middleware that resolves the host from X-Forwarded-Host, injects the host's database and Redis client into context, and returns 503 if the host is not found. When X-Forwarded-Host is absent (e.g. health checks, direct connections), defaultDB and defaultRDB are injected if non-ni
(registry *Registry, poolCache *PoolCache, redisCache *RedisCache, defaultDB *database.DB, defaultRDB *redis.Client, reloadSecret string)
| 43 | // injects the host's database and Redis client into context, and returns 503 if the host is not found. |
| 44 | // When X-Forwarded-Host is absent (e.g. health checks, direct connections), defaultDB and defaultRDB are injected if non-nil. |
| 45 | func Middleware(registry *Registry, poolCache *PoolCache, redisCache *RedisCache, defaultDB *database.DB, defaultRDB *redis.Client, reloadSecret string) func(http.Handler) http.Handler { |
| 46 | return func(next http.Handler) http.Handler { |
| 47 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 48 | host := strings.TrimSpace(r.Header.Get("X-Forwarded-Host")) |
| 49 | if host == "" { |
| 50 | ctx := r.Context() |
| 51 | if defaultDB != nil { |
| 52 | ctx = WithDB(ctx, defaultDB) |
| 53 | } |
| 54 | if defaultRDB != nil { |
| 55 | ctx = WithRedis(ctx, defaultRDB) |
| 56 | } |
| 57 | next.ServeHTTP(w, r.WithContext(ctx)) |
| 58 | return |
| 59 | } |
| 60 | |
| 61 | if registry == nil || poolCache == nil { |
| 62 | next.ServeHTTP(w, r) |
| 63 | return |
| 64 | } |
| 65 | |
| 66 | db, err := poolCache.GetOrCreate(r.Context(), host) |
| 67 | if err != nil { |
| 68 | w.Header().Set("Content-Type", "application/json") |
| 69 | w.WriteHeader(http.StatusServiceUnavailable) |
| 70 | _ = json.NewEncoder(w).Encode(map[string]string{ |
| 71 | "error": "service temporarily unavailable", |
| 72 | "code": "db_error", |
| 73 | }) |
| 74 | return |
| 75 | } |
| 76 | if db == nil { |
| 77 | w.Header().Set("Content-Type", "application/json") |
| 78 | w.WriteHeader(http.StatusServiceUnavailable) |
| 79 | _ = json.NewEncoder(w).Encode(map[string]string{ |
| 80 | "error": "host not found or suspended", |
| 81 | "code": "host_not_found", |
| 82 | }) |
| 83 | return |
| 84 | } |
| 85 | |
| 86 | ctx := WithDB(r.Context(), db) |
| 87 | |
| 88 | // Inject host registry entry so handlers can check package limits (MaxUsers, MaxHosts, Modules). |
| 89 | if entry := registry.GetByHost(host); entry != nil { |
| 90 | ctx = WithEntry(ctx, entry) |
| 91 | } |
| 92 | |
| 93 | // Resolve per-host Redis; falls back to defaultRDB if no Redis credentials in registry. |
| 94 | if redisCache != nil { |
| 95 | rdb, rdbErr := redisCache.GetOrCreate(r.Context(), host) |
| 96 | if rdbErr != nil { |
| 97 | // Log but don't block - fall back to default Redis rather than returning 503. |
| 98 | rdb = redisCache.Default() |
| 99 | } |
| 100 | if rdb != nil { |
| 101 | ctx = WithRedis(ctx, rdb) |
| 102 | } |