Files
goyco/internal/server/router.go

159 lines
4.9 KiB
Go

package server
import (
"mime"
"net/http"
"path/filepath"
"strings"
"time"
"goyco/internal/config"
"goyco/internal/handlers"
"goyco/internal/middleware"
"github.com/go-chi/chi/v5"
httpSwagger "github.com/swaggo/http-swagger"
)
type RouterConfig struct {
AuthHandler *handlers.AuthHandler
PostHandler *handlers.PostHandler
VoteHandler *handlers.VoteHandler
UserHandler *handlers.UserHandler
APIHandler *handlers.APIHandler
AuthService middleware.TokenVerifier
PageHandler *handlers.PageHandler
StaticDir string
Debug bool
DisableCache bool
DisableCompression bool
DBMonitor middleware.DBMonitor
RateLimitConfig config.RateLimitConfig
}
func NewRouter(cfg RouterConfig) http.Handler {
middleware.SetTrustProxyHeaders(cfg.RateLimitConfig.TrustProxyHeaders)
router := chi.NewRouter()
router.Use(middleware.Logging(cfg.Debug))
router.Use(middleware.SecurityHeadersMiddleware())
router.Use(middleware.HSTSMiddleware())
router.Use(middleware.CORS)
if !cfg.DisableCompression {
router.Use(middleware.CompressionMiddleware())
router.Use(middleware.DecompressionMiddleware())
}
router.Use(middleware.DefaultRequestSizeLimitMiddleware())
if !cfg.DisableCache {
cache := middleware.NewInMemoryCache()
cacheConfig := middleware.DefaultCacheConfig()
router.Use(middleware.CacheMiddleware(cache, cacheConfig))
router.Use(middleware.CacheInvalidationMiddleware(cache))
}
var dbMonitor middleware.DBMonitor
if cfg.DBMonitor != nil {
dbMonitor = cfg.DBMonitor
} else {
dbMonitor = middleware.NewInMemoryDBMonitor()
}
router.Use(middleware.DBMonitoringMiddleware(dbMonitor, 100*time.Millisecond))
metricsCollector := middleware.NewMetricsCollector(dbMonitor)
router.Use(middleware.MetricsMiddleware(metricsCollector))
routeConfig := handlers.RouteModuleConfig{
AuthService: cfg.AuthService,
GeneralRateLimit: func(r chi.Router) chi.Router {
return r.With(middleware.GeneralRateLimitMiddlewareWithLimit(cfg.RateLimitConfig.GeneralLimit))
},
AuthRateLimit: func(r chi.Router) chi.Router {
return r.With(middleware.AuthRateLimitMiddlewareWithLimit(cfg.RateLimitConfig.AuthLimit))
},
CSRFMiddleware: middleware.CSRFMiddleware(),
AuthMiddleware: middleware.NewAuth(cfg.AuthService),
ValidationMiddleware: middleware.ValidationMiddleware(),
}
if cfg.PageHandler != nil {
cfg.PageHandler.MountRoutes(router, routeConfig)
}
if cfg.APIHandler != nil {
healthRateLimited := router.With(middleware.HealthRateLimitMiddleware(cfg.RateLimitConfig.HealthLimit))
healthRateLimited.Get("/health", cfg.APIHandler.GetHealth)
metricsRateLimited := router.With(middleware.MetricsRateLimitMiddleware(cfg.RateLimitConfig.MetricsLimit))
metricsRateLimited.Get("/metrics", cfg.APIHandler.GetMetrics)
}
swaggerRateLimited := router.With(middleware.GeneralRateLimitMiddlewareWithLimit(cfg.RateLimitConfig.GeneralLimit))
swaggerRateLimited.Get("/swagger/*", httpSwagger.Handler())
router.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, filepath.Join(cfg.StaticDir, "robots.txt"))
})
router.Route("/api", func(api chi.Router) {
modules := []handlers.RouteModule{}
if cfg.AuthHandler != nil {
modules = append(modules, cfg.AuthHandler)
}
if cfg.PostHandler != nil {
modules = append(modules, cfg.PostHandler)
}
if cfg.VoteHandler != nil {
modules = append(modules, cfg.VoteHandler)
}
if cfg.UserHandler != nil {
modules = append(modules, cfg.UserHandler)
}
if cfg.APIHandler != nil {
modules = append(modules, cfg.APIHandler)
}
for _, module := range modules {
module.MountRoutes(api, routeConfig)
}
})
staticDir := cfg.StaticDir
if staticDir == "" {
staticDir = "./internal/static/"
}
staticFileServer := http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))
router.Handle("/static/*", staticFileHandler(staticFileServer))
return router
}
func staticFileHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
ext := filepath.Ext(path)
if ext == ".css" {
w.Header().Set("Content-Type", "text/css; charset=utf-8")
} else if ext == ".js" {
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
} else if ext == ".json" {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
} else if ext == ".ico" {
w.Header().Set("Content-Type", "image/x-icon")
} else if strings.HasPrefix(mime.TypeByExtension(ext), "image/") {
w.Header().Set("Content-Type", mime.TypeByExtension(ext))
} else if strings.HasPrefix(mime.TypeByExtension(ext), "font/") {
w.Header().Set("Content-Type", mime.TypeByExtension(ext))
} else if mimeType := mime.TypeByExtension(ext); mimeType != "" {
w.Header().Set("Content-Type", mimeType)
}
next.ServeHTTP(w, r)
})
}