feat(split): data-plane (публичный SQS API) и control-plane (админка/метрики/UI на localhost) — Шаг 2

This commit is contained in:
Repinoid
2026-10-10 16:00:57 +03:00
parent a66b908689
commit a26e6da8b7
2 changed files with 82 additions and 39 deletions
+49 -28
View File
@@ -28,8 +28,13 @@ import (
log "github.com/sirupsen/logrus"
)
// New — создаёт HTTP router с tenant auth и admin API
func New(tenantStore *tenant.TenantStore, adminToken string) http.Handler {
// NewDataPlane — публичный SQS API (data-plane): SQS actions, /health, статика, описание.
// НЕ содержит админ-консоль, /metrics и /ui — они в control-plane (NewControlPlane).
//
// Разбиение data-plane/control-plane (Шаг 2): публично доступен только этот роутер.
// Пока — два листенера в одном процессе (общее in-memory состояние); отдельные процессы
// станут возможны после выноса состояния в Redis (Шаг 3).
func NewDataPlane(tenantStore *tenant.TenantStore) http.Handler {
r := mux.NewRouter()
// Fix #3.5: глобальный лимит размера тела HTTP запроса — 3MB
@@ -42,37 +47,11 @@ func New(tenantStore *tenant.TenantStore, adminToken string) http.Handler {
// /static — логотип, favicon для публичных страниц
r.PathPrefix("/static").Handler(ui.StaticHandler()).Methods("GET")
// /metrics — Prometheus endpoint для Victoria Metrics scrape.
// Защищён Bearer admin-токеном: метрики содержат per-tenant данные
// (accessKey/число очередей и сообщений), которые нельзя светить наружу.
r.Handle("/metrics", metricsAuth(adminToken, promhttp.Handler())).Methods("GET")
// Страница примеров команд — публично
r.HandleFunc("/examples", func(w http.ResponseWriter, req *http.Request) {
ui.ExamplesHandler(models.Version).ServeHTTP(w, req)
}).Methods("GET")
// Админ-консоль — статика. Регистрируем ДО admin subrouter,
// иначе PathPrefix("/admin") с bearer-auth перехватит GET /admin.
r.HandleFunc("/admin", func(w http.ResponseWriter, req *http.Request) {
ui.AdminHandler(models.Version).ServeHTTP(w, req)
}).Methods("GET")
r.HandleFunc("/admin/", func(w http.ResponseWriter, req *http.Request) {
ui.AdminHandler(models.Version).ServeHTTP(w, req)
}).Methods("GET")
// Admin API — Bearer token auth, регистрируется через AdminHandler
adminHandler := admin.NewHandler(tenantStore, adminToken)
adminHandler.RegisterRoutes(r)
// UI public API — JWT auth, для встроенной console
// ВАЖНО: RegisterPublicRoutes ПЕРЕД static handler — иначе PathPrefix("/ui") перехватит /ui/api/*
adminHandler.RegisterPublicRoutes(r)
// UI console — встроенный SPA, публичный доступ
// НЕ ловит /ui/api/* — mux сначала проверит более специфичные маршруты выше
r.PathPrefix("/ui").Handler(http.StripPrefix("/ui", ui.Handler()))
// SQS API — tenant auth middleware оборачивает каждый handler отдельно.
// r.NewRoute().Subrouter() с Use() некорректно работает в gorilla/mux v1.8.0
// при пустом prefix — ответы теряются. Поэтому используем явную обёртку.
@@ -94,6 +73,48 @@ func New(tenantStore *tenant.TenantStore, adminToken string) http.Handler {
return r
}
// NewControlPlane — админка, метрики и UI (control-plane).
// ВАЖНО: этот роутер должен слушать ТОЛЬКО localhost (не проксируется наружу).
func NewControlPlane(tenantStore *tenant.TenantStore, adminToken string) http.Handler {
r := mux.NewRouter()
r.Use(requestBodyLimitMiddleware)
// /health — для проверки control-plane локально
r.HandleFunc("/health", health).Methods("GET")
// /static — нужен UI-консоли (логотип/favicon)
r.PathPrefix("/static").Handler(ui.StaticHandler()).Methods("GET")
// /metrics — Prometheus endpoint для Victoria Metrics scrape.
// Защищён Bearer admin-токеном: метрики содержат per-tenant данные
// (accessKey/число очередей и сообщений), которые нельзя светить наружу.
r.Handle("/metrics", metricsAuth(adminToken, promhttp.Handler())).Methods("GET")
// Админ-консоль — статика. Регистрируем ДО admin subrouter,
// иначе PathPrefix("/admin") с bearer-auth перехватит GET /admin.
r.HandleFunc("/admin", func(w http.ResponseWriter, req *http.Request) {
ui.AdminHandler(models.Version).ServeHTTP(w, req)
}).Methods("GET")
r.HandleFunc("/admin/", func(w http.ResponseWriter, req *http.Request) {
ui.AdminHandler(models.Version).ServeHTTP(w, req)
}).Methods("GET")
// Admin API — Bearer token auth, регистрируется через AdminHandler
adminHandler := admin.NewHandler(tenantStore, adminToken)
adminHandler.RegisterRoutes(r)
// UI public API — JWT auth, для встроенной console
// ВАЖНО: RegisterPublicRoutes ПЕРЕД static handler — иначе PathPrefix("/ui") перехватит /ui/api/*
adminHandler.RegisterPublicRoutes(r)
// UI console — встроенный SPA
// НЕ ловит /ui/api/* — mux сначала проверит более специфичные маршруты выше
r.PathPrefix("/ui").Handler(http.StripPrefix("/ui", ui.Handler()))
return r
}
// requestBodyLimitMiddleware — ограничивает размер тела HTTP запроса до 3MB.
// Защита от OOM при отправке гигантских messages/batch (Fix #3.5).
func requestBodyLimitMiddleware(next http.Handler) http.Handler {