monorepo/cloud/maplepress-backend/internal/interface/http/server.go

490 lines
21 KiB
Go

package http
import (
"context"
"fmt"
"net/http"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config"
httpmw "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/http/middleware"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/admin"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/gateway"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/healthcheck"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/plugin"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/site"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/tenant"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/user"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/middleware"
)
// Server represents the HTTP server
type Server struct {
server *http.Server
logger *zap.Logger
jwtMiddleware *httpmw.JWTMiddleware
apikeyMiddleware *httpmw.APIKeyMiddleware
rateLimitMiddlewares *httpmw.RateLimitMiddlewares // CWE-770: Registration and auth endpoints rate limiting
securityHeadersMiddleware *httpmw.SecurityHeadersMiddleware
requestSizeLimitMw *httpmw.RequestSizeLimitMiddleware
config *config.Config
healthHandler *healthcheck.Handler
registerHandler *gateway.RegisterHandler
loginHandler *gateway.LoginHandler
refreshTokenHandler *gateway.RefreshTokenHandler
helloHandler *gateway.HelloHandler
meHandler *gateway.MeHandler
createTenantHandler *tenant.CreateHandler
getTenantHandler *tenant.GetHandler
createUserHandler *user.CreateHandler
getUserHandler *user.GetHandler
createSiteHandler *site.CreateHandler
getSiteHandler *site.GetHandler
listSitesHandler *site.ListHandler
deleteSiteHandler *site.DeleteHandler
rotateSiteAPIKeyHandler *site.RotateAPIKeyHandler
verifySiteHandler *site.VerifySiteHandler
pluginStatusHandler *plugin.StatusHandler
pluginVerifyHandler *plugin.PluginVerifyHandler
pluginVersionHandler *plugin.VersionHandler
syncPagesHandler *plugin.SyncPagesHandler
searchPagesHandler *plugin.SearchPagesHandler
deletePagesHandler *plugin.DeletePagesHandler
syncStatusHandler *plugin.SyncStatusHandler
unlockAccountHandler *admin.UnlockAccountHandler
accountStatusHandler *admin.AccountStatusHandler
}
// ProvideServer creates a new HTTP server
func ProvideServer(
cfg *config.Config,
logger *zap.Logger,
jwtMiddleware *httpmw.JWTMiddleware,
apikeyMiddleware *httpmw.APIKeyMiddleware,
rateLimitMiddlewares *httpmw.RateLimitMiddlewares,
securityHeadersMiddleware *httpmw.SecurityHeadersMiddleware,
requestSizeLimitMw *httpmw.RequestSizeLimitMiddleware,
healthHandler *healthcheck.Handler,
registerHandler *gateway.RegisterHandler,
loginHandler *gateway.LoginHandler,
refreshTokenHandler *gateway.RefreshTokenHandler,
helloHandler *gateway.HelloHandler,
meHandler *gateway.MeHandler,
createTenantHandler *tenant.CreateHandler,
getTenantHandler *tenant.GetHandler,
createUserHandler *user.CreateHandler,
getUserHandler *user.GetHandler,
createSiteHandler *site.CreateHandler,
getSiteHandler *site.GetHandler,
listSitesHandler *site.ListHandler,
deleteSiteHandler *site.DeleteHandler,
rotateSiteAPIKeyHandler *site.RotateAPIKeyHandler,
verifySiteHandler *site.VerifySiteHandler,
pluginStatusHandler *plugin.StatusHandler,
pluginVerifyHandler *plugin.PluginVerifyHandler,
pluginVersionHandler *plugin.VersionHandler,
syncPagesHandler *plugin.SyncPagesHandler,
searchPagesHandler *plugin.SearchPagesHandler,
deletePagesHandler *plugin.DeletePagesHandler,
syncStatusHandler *plugin.SyncStatusHandler,
unlockAccountHandler *admin.UnlockAccountHandler,
accountStatusHandler *admin.AccountStatusHandler,
) *Server {
mux := http.NewServeMux()
s := &Server{
logger: logger,
jwtMiddleware: jwtMiddleware,
apikeyMiddleware: apikeyMiddleware,
rateLimitMiddlewares: rateLimitMiddlewares,
securityHeadersMiddleware: securityHeadersMiddleware,
requestSizeLimitMw: requestSizeLimitMw,
config: cfg,
healthHandler: healthHandler,
registerHandler: registerHandler,
loginHandler: loginHandler,
refreshTokenHandler: refreshTokenHandler,
helloHandler: helloHandler,
meHandler: meHandler,
createTenantHandler: createTenantHandler,
getTenantHandler: getTenantHandler,
createUserHandler: createUserHandler,
getUserHandler: getUserHandler,
createSiteHandler: createSiteHandler,
getSiteHandler: getSiteHandler,
listSitesHandler: listSitesHandler,
deleteSiteHandler: deleteSiteHandler,
rotateSiteAPIKeyHandler: rotateSiteAPIKeyHandler,
verifySiteHandler: verifySiteHandler,
pluginStatusHandler: pluginStatusHandler,
pluginVerifyHandler: pluginVerifyHandler,
pluginVersionHandler: pluginVersionHandler,
syncPagesHandler: syncPagesHandler,
searchPagesHandler: searchPagesHandler,
deletePagesHandler: deletePagesHandler,
syncStatusHandler: syncStatusHandler,
unlockAccountHandler: unlockAccountHandler,
accountStatusHandler: accountStatusHandler,
}
// Register routes
s.registerRoutes(mux)
// Create HTTP server
// CWE-770: Configure timeouts to prevent resource exhaustion
s.server = &http.Server{
Addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port),
Handler: s.applyMiddleware(mux),
ReadTimeout: cfg.HTTP.ReadTimeout,
WriteTimeout: cfg.HTTP.WriteTimeout,
IdleTimeout: cfg.HTTP.IdleTimeout,
}
logger.Info("✓ HTTP server configured",
zap.String("address", s.server.Addr),
zap.Duration("read_timeout", cfg.HTTP.ReadTimeout),
zap.Duration("write_timeout", cfg.HTTP.WriteTimeout),
zap.Int64("max_body_size", cfg.HTTP.MaxRequestBodySize))
return s
}
// registerRoutes registers all HTTP routes
func (s *Server) registerRoutes(mux *http.ServeMux) {
// ===== PUBLIC ROUTES (No authentication, no tenant) =====
// Health check
mux.HandleFunc("GET /health", s.healthHandler.Handle)
// Version endpoint - public API for checking backend version
mux.HandleFunc("GET /api/v1/version", s.pluginVersionHandler.Handle)
// Public gateway routes (registration, login, etc.)
// CWE-770: Apply request size limits and rate limiting
// Apply small size limit (1MB) for registration/login endpoints
if s.config.RateLimit.RegistrationEnabled {
mux.HandleFunc("POST /api/v1/register",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyRegistrationRateLimit(s.registerHandler.Handle)),
).ServeHTTP)
} else {
mux.HandleFunc("POST /api/v1/register",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.registerHandler.Handle),
).ServeHTTP)
}
mux.HandleFunc("POST /api/v1/login",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.loginHandler.Handle),
).ServeHTTP)
mux.HandleFunc("POST /api/v1/refresh",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.refreshTokenHandler.Handle),
).ServeHTTP)
// ===== AUTHENTICATED ROUTES (JWT only, no tenant context) =====
// Gateway routes
// CWE-770: Apply small size limit (1MB) and generic rate limiting for hello endpoint
if s.config.RateLimit.GenericEnabled {
mux.HandleFunc("POST /api/v1/hello",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthOnlyWithGenericRateLimit(s.helloHandler.Handle)),
).ServeHTTP)
} else {
mux.HandleFunc("POST /api/v1/hello",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthOnly(s.helloHandler.Handle)),
).ServeHTTP)
}
// CWE-770: Apply generic rate limiting to /me endpoint to prevent profile enumeration and DoS
if s.config.RateLimit.GenericEnabled {
mux.HandleFunc("GET /api/v1/me",
s.applyAuthOnlyWithGenericRateLimit(s.meHandler.Handle))
} else {
mux.HandleFunc("GET /api/v1/me", s.applyAuthOnly(s.meHandler.Handle))
}
// Tenant management routes - these operate at system/admin level
// CWE-770: Apply small size limit (1MB) and generic rate limiting for tenant creation
if s.config.RateLimit.GenericEnabled {
mux.HandleFunc("POST /api/v1/tenants",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthOnlyWithGenericRateLimit(s.createTenantHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("GET /api/v1/tenants/{id}", s.applyAuthOnlyWithGenericRateLimit(s.getTenantHandler.HandleByID))
mux.HandleFunc("GET /api/v1/tenants/slug/{slug}", s.applyAuthOnlyWithGenericRateLimit(s.getTenantHandler.HandleBySlug))
} else {
mux.HandleFunc("POST /api/v1/tenants",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthOnly(s.createTenantHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("GET /api/v1/tenants/{id}", s.applyAuthOnly(s.getTenantHandler.HandleByID))
mux.HandleFunc("GET /api/v1/tenants/slug/{slug}", s.applyAuthOnly(s.getTenantHandler.HandleBySlug))
}
// ===== TENANT-SCOPED ROUTES (JWT + Tenant context) =====
// User routes - these operate within a tenant context
// CWE-770: Apply small size limit (1MB) and generic rate limiting for user creation
if s.config.RateLimit.GenericEnabled {
mux.HandleFunc("POST /api/v1/users",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthAndTenantWithGenericRateLimit(s.createUserHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("GET /api/v1/users/{id}", s.applyAuthAndTenantWithGenericRateLimit(s.getUserHandler.Handle))
} else {
mux.HandleFunc("POST /api/v1/users",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthAndTenant(s.createUserHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("GET /api/v1/users/{id}", s.applyAuthAndTenant(s.getUserHandler.Handle))
}
// Site management routes - JWT authenticated, tenant-scoped
// CWE-770: Apply small size limit (1MB) and generic rate limiting for site management
if s.config.RateLimit.GenericEnabled {
mux.HandleFunc("POST /api/v1/sites",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthAndTenantWithGenericRateLimit(s.createSiteHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("GET /api/v1/sites", s.applyAuthAndTenantWithGenericRateLimit(s.listSitesHandler.Handle))
mux.HandleFunc("GET /api/v1/sites/{id}", s.applyAuthAndTenantWithGenericRateLimit(s.getSiteHandler.Handle))
mux.HandleFunc("DELETE /api/v1/sites/{id}", s.applyAuthAndTenantWithGenericRateLimit(s.deleteSiteHandler.Handle))
mux.HandleFunc("POST /api/v1/sites/{id}/rotate-api-key",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthAndTenantWithGenericRateLimit(s.rotateSiteAPIKeyHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("POST /api/v1/sites/{id}/verify",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthAndTenantWithGenericRateLimit(s.verifySiteHandler.Handle)),
).ServeHTTP)
} else {
mux.HandleFunc("POST /api/v1/sites",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthAndTenant(s.createSiteHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("GET /api/v1/sites", s.applyAuthAndTenant(s.listSitesHandler.Handle))
mux.HandleFunc("GET /api/v1/sites/{id}", s.applyAuthAndTenant(s.getSiteHandler.Handle))
mux.HandleFunc("DELETE /api/v1/sites/{id}", s.applyAuthAndTenant(s.deleteSiteHandler.Handle))
mux.HandleFunc("POST /api/v1/sites/{id}/rotate-api-key",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthAndTenant(s.rotateSiteAPIKeyHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("POST /api/v1/sites/{id}/verify",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthAndTenant(s.verifySiteHandler.Handle)),
).ServeHTTP)
}
// ===== ADMIN ROUTES (JWT authenticated) =====
// CWE-307: Admin endpoints for account lockout management
// CWE-770: Apply small size limit (1MB) and generic rate limiting for admin endpoints
if s.config.RateLimit.GenericEnabled {
mux.HandleFunc("POST /api/v1/admin/unlock-account",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthOnlyWithGenericRateLimit(s.unlockAccountHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("GET /api/v1/admin/account-status", s.applyAuthOnlyWithGenericRateLimit(s.accountStatusHandler.Handle))
} else {
mux.HandleFunc("POST /api/v1/admin/unlock-account",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAuthOnly(s.unlockAccountHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("GET /api/v1/admin/account-status", s.applyAuthOnly(s.accountStatusHandler.Handle))
}
// ===== WORDPRESS PLUGIN API ROUTES (API Key authentication) =====
// CWE-770: Apply lenient site-based rate limiting to protect core business endpoints
// Default: 1000 requests/hour per site (very lenient for high-volume legitimate traffic)
if s.config.RateLimit.PluginAPIEnabled {
// Plugin status/verification - with rate limiting
mux.HandleFunc("GET /api/v1/plugin/status", s.applyAPIKeyAuthWithPluginRateLimit(s.pluginStatusHandler.Handle))
// Plugin domain verification endpoint
mux.HandleFunc("POST /api/v1/plugin/verify",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAPIKeyAuthWithPluginRateLimit(s.pluginVerifyHandler.Handle)),
).ServeHTTP)
// Page sync and search routes
// CWE-770: Apply larger size limit (50MB) for page sync (bulk operations) + rate limiting
mux.HandleFunc("POST /api/v1/plugin/pages/sync",
s.requestSizeLimitMw.LimitLarge()(
http.HandlerFunc(s.applyAPIKeyAuthWithPluginRateLimit(s.syncPagesHandler.Handle)),
).ServeHTTP)
// Apply medium limit (5MB) for search and delete operations + rate limiting
mux.HandleFunc("POST /api/v1/plugin/pages/search",
s.requestSizeLimitMw.LimitMedium()(
http.HandlerFunc(s.applyAPIKeyAuthWithPluginRateLimit(s.searchPagesHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("DELETE /api/v1/plugin/pages",
s.requestSizeLimitMw.LimitMedium()(
http.HandlerFunc(s.applyAPIKeyAuthWithPluginRateLimit(s.deletePagesHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("DELETE /api/v1/plugin/pages/all", s.applyAPIKeyAuthWithPluginRateLimit(s.deletePagesHandler.HandleDeleteAll))
mux.HandleFunc("GET /api/v1/plugin/pages/status", s.applyAPIKeyAuthWithPluginRateLimit(s.syncStatusHandler.Handle))
mux.HandleFunc("GET /api/v1/plugin/pages/{page_id}", s.applyAPIKeyAuthWithPluginRateLimit(s.syncStatusHandler.HandleGetPageDetails))
} else {
// Plugin endpoints without rate limiting (not recommended for production)
mux.HandleFunc("GET /api/v1/plugin/status", s.applyAPIKeyAuth(s.pluginStatusHandler.Handle))
// Plugin domain verification endpoint
mux.HandleFunc("POST /api/v1/plugin/verify",
s.requestSizeLimitMw.LimitSmall()(
http.HandlerFunc(s.applyAPIKeyAuth(s.pluginVerifyHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("POST /api/v1/plugin/pages/sync",
s.requestSizeLimitMw.LimitLarge()(
http.HandlerFunc(s.applyAPIKeyAuth(s.syncPagesHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("POST /api/v1/plugin/pages/search",
s.requestSizeLimitMw.LimitMedium()(
http.HandlerFunc(s.applyAPIKeyAuth(s.searchPagesHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("DELETE /api/v1/plugin/pages",
s.requestSizeLimitMw.LimitMedium()(
http.HandlerFunc(s.applyAPIKeyAuth(s.deletePagesHandler.Handle)),
).ServeHTTP)
mux.HandleFunc("DELETE /api/v1/plugin/pages/all", s.applyAPIKeyAuth(s.deletePagesHandler.HandleDeleteAll))
mux.HandleFunc("GET /api/v1/plugin/pages/status", s.applyAPIKeyAuth(s.syncStatusHandler.Handle))
mux.HandleFunc("GET /api/v1/plugin/pages/{page_id}", s.applyAPIKeyAuth(s.syncStatusHandler.HandleGetPageDetails))
}
}
// applyMiddleware applies global middleware to all routes
func (s *Server) applyMiddleware(handler http.Handler) http.Handler {
// Apply middleware in order (innermost to outermost)
// 1. Logger middleware (logging)
// 2. Security headers middleware (CWE-693: Protection Mechanism Failure)
handler = middleware.LoggerMiddleware(s.logger)(handler)
handler = s.securityHeadersMiddleware.Handler(handler)
return handler
}
// applyAuthOnly applies only JWT authentication middleware (no tenant)
func (s *Server) applyAuthOnly(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Chain: JWT validation -> Auth check -> Handler
s.jwtMiddleware.Handler(
s.jwtMiddleware.RequireAuth(
http.HandlerFunc(handler),
),
).ServeHTTP(w, r)
}
}
// applyAuthOnlyWithGenericRateLimit applies JWT authentication + generic rate limiting (CWE-770)
// Used for authenticated CRUD endpoints (tenant/user/site management, admin, /me, /hello)
// Applies user-based rate limiting (extracted from JWT context)
func (s *Server) applyAuthOnlyWithGenericRateLimit(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Chain: JWT validation -> Auth check -> Generic rate limit (user-based) -> Handler
s.jwtMiddleware.Handler(
s.jwtMiddleware.RequireAuth(
s.rateLimitMiddlewares.Generic.HandlerWithUserKey(
http.HandlerFunc(handler),
),
),
).ServeHTTP(w, r)
}
}
// applyAuthAndTenant applies JWT authentication + tenant middleware
func (s *Server) applyAuthAndTenant(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Chain: JWT validation -> Auth check -> Tenant -> Handler
s.jwtMiddleware.Handler(
s.jwtMiddleware.RequireAuth(
middleware.TenantMiddleware()(
http.HandlerFunc(handler),
),
),
).ServeHTTP(w, r)
}
}
// applyAuthAndTenantWithGenericRateLimit applies JWT authentication + tenant + generic rate limiting (CWE-770)
// Used for tenant-scoped CRUD endpoints (user/site management)
// Applies user-based rate limiting (extracted from JWT context)
func (s *Server) applyAuthAndTenantWithGenericRateLimit(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Chain: JWT validation -> Auth check -> Tenant -> Generic rate limit (user-based) -> Handler
s.jwtMiddleware.Handler(
s.jwtMiddleware.RequireAuth(
middleware.TenantMiddleware()(
s.rateLimitMiddlewares.Generic.HandlerWithUserKey(
http.HandlerFunc(handler),
),
),
),
).ServeHTTP(w, r)
}
}
// applyAPIKeyAuth applies API key authentication middleware (for WordPress plugin)
func (s *Server) applyAPIKeyAuth(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Chain: API key validation -> Require API key -> Handler
s.apikeyMiddleware.Handler(
s.apikeyMiddleware.RequireAPIKey(
http.HandlerFunc(handler),
),
).ServeHTTP(w, r)
}
}
// applyAPIKeyAuthWithPluginRateLimit applies API key authentication + plugin API rate limiting (CWE-770)
// Used for WordPress Plugin API endpoints (core business endpoints)
// Applies site-based rate limiting (extracted from API key context)
func (s *Server) applyAPIKeyAuthWithPluginRateLimit(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Chain: API key validation -> Require API key -> Plugin rate limit (site-based) -> Handler
s.apikeyMiddleware.Handler(
s.apikeyMiddleware.RequireAPIKey(
s.rateLimitMiddlewares.PluginAPI.HandlerWithSiteKey(
http.HandlerFunc(handler),
),
),
).ServeHTTP(w, r)
}
}
// applyRegistrationRateLimit applies rate limiting middleware for registration (CWE-770)
func (s *Server) applyRegistrationRateLimit(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Chain: Rate limit check -> Handler
s.rateLimitMiddlewares.Registration.Handler(
http.HandlerFunc(handler),
).ServeHTTP(w, r)
}
}
// Start starts the HTTP server
func (s *Server) Start() error {
s.logger.Info("")
s.logger.Info("🚀 MaplePress Backend is ready!")
s.logger.Info("",
zap.String("address", s.server.Addr),
zap.String("url", fmt.Sprintf("http://localhost:%s", s.server.Addr[len(s.server.Addr)-4:])))
s.logger.Info("")
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return fmt.Errorf("failed to start server: %w", err)
}
return nil
}
// Shutdown gracefully shuts down the HTTP server
func (s *Server) Shutdown(ctx context.Context) error {
s.logger.Info("shutting down HTTP server")
if err := s.server.Shutdown(ctx); err != nil {
return fmt.Errorf("failed to shutdown server: %w", err)
}
s.logger.Info("HTTP server shut down successfully")
return nil
}