Refactor start.go to move server logic to internal server.go

This commit is contained in:
Christian Galo 2025-05-11 04:32:54 -05:00
parent 4d29a58f94
commit e5e9efd9b3
2 changed files with 118 additions and 84 deletions

View File

@ -3,14 +3,9 @@ package cmd
import ( import (
"context" "context"
"log/slog" "log/slog"
"net"
"net/http"
"time"
"git.coopcloud.tech/wiki-cafe/member-console/internal/auth"
"git.coopcloud.tech/wiki-cafe/member-console/internal/logging" "git.coopcloud.tech/wiki-cafe/member-console/internal/logging"
"git.coopcloud.tech/wiki-cafe/member-console/internal/middleware" "git.coopcloud.tech/wiki-cafe/member-console/internal/server"
"github.com/rs/cors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -30,6 +25,7 @@ var startCmd = &cobra.Command{
// Retrieve the configuration values from Viper // Retrieve the configuration values from Viper
port := viper.GetString("port") port := viper.GetString("port")
env := viper.GetString("env") env := viper.GetString("env")
csrfSecret := viper.GetString("csrf-secret")
// Set up structured logging // Set up structured logging
logger := logging.SetupLogger(env) logger := logging.SetupLogger(env)
@ -37,86 +33,17 @@ var startCmd = &cobra.Command{
// Store logger in context // Store logger in context
ctx = logging.WithContext(ctx, logger) ctx = logging.WithContext(ctx, logger)
// Create a new HTTP request router // Create server config
httpRequestRouter := http.NewServeMux() serverConfig := server.Config{
Port: port,
// Set up authentication Env: env,
authConfig, err := auth.Setup() CSRFSecret: csrfSecret,
if err != nil { Logger: logger,
logger.Error("failed to set up authentication", slog.Any("error", err))
return
} }
// Register auth handlers // Start the server
authConfig.RegisterHandlers(httpRequestRouter) if err := server.Start(ctx, serverConfig); err != nil {
logger.Error("server failed to start", slog.Any("error", err))
// Create CORS configuration with default options
corsOptions := cors.Options{
// Define minimal defaults - GET method is required
AllowedMethods: []string{"GET"},
}
// Create empty CSRF configuration with default values
var csrfConfig middleware.CSRFConfig
// Get and validate CSRF secret from config
csrfSecret := viper.GetString("csrf-secret")
csrfKey, err := middleware.ParseCSRFKey(csrfSecret)
if err != nil {
logger.Error("invalid csrf-secret",
slog.String("error", err.Error()),
slog.String("hint", "must be exactly 32 bytes and persist across restarts"))
return
}
csrfConfig.Secret = csrfKey
// Only override specific settings when needed
if env == "development" {
// In development, cookies often need to work without HTTPS
csrfConfig.Cookie.Secure = false
}
// Create middleware stack
stack := middleware.CreateStack(
middleware.RequestID(), // Generate a unique request ID
middleware.Logging(), // Log requests with structured logging
middleware.Recovery(), // Catch all panics
middleware.Timeout(32*time.Second), // Set request timeout
middleware.MaxBodySize(1024*1024), // 1MB size limit
middleware.SecureHeaders(), // Set secure headers
middleware.CORS(corsOptions), // CORS configuration
middleware.CSRF(csrfConfig), // CSRF protection
middleware.Compress(), // Response compression
authConfig.Middleware(), // OIDC authentication middleware
)
// Create HTTP server
server := http.Server{
Addr: ":" + port,
Handler: stack(httpRequestRouter),
ReadTimeout: 4 * time.Second,
WriteTimeout: 8 * time.Second,
IdleTimeout: 16 * time.Second,
MaxHeaderBytes: 1024 * 1024, // 1MB
BaseContext: func(_ net.Listener) context.Context { return ctx }, // Pass base context to all requests
}
// Serve the templates and static files
// Serve templates from the "templates" directory
httpRequestRouter.Handle("/", http.FileServer(http.Dir("./templates")))
// Serve static files from the "static" directory
httpRequestRouter.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
// Log server startup with structured logging
logger.Info("starting server",
slog.String("port", port),
slog.String("environment", env),
slog.String("address", "http://localhost:"+port))
// Start server and log any errors
if err := server.ListenAndServe(); err != nil {
logger.Error("server error", slog.Any("error", err))
} }
}, },
} }

107
internal/server/server.go Normal file
View File

@ -0,0 +1,107 @@
package server
import (
"context"
"log/slog"
"net"
"net/http"
"time"
"git.coopcloud.tech/wiki-cafe/member-console/internal/auth"
"git.coopcloud.tech/wiki-cafe/member-console/internal/middleware"
"github.com/rs/cors"
)
// Config holds the configuration for the server.
type Config struct {
Port string
Env string
CSRFSecret string
Logger *slog.Logger
}
// Start initializes and starts the HTTP server.
func Start(ctx context.Context, cfg Config) error {
// Create a new HTTP request router
httpRequestRouter := http.NewServeMux()
// Set up authentication
authConfig, err := auth.Setup()
if err != nil {
cfg.Logger.Error("failed to set up authentication", slog.Any("error", err))
return err
}
// Register auth handlers
authConfig.RegisterHandlers(httpRequestRouter)
// Create CORS configuration with default options
corsOptions := cors.Options{
// Define minimal defaults - GET method is required
AllowedMethods: []string{"GET"},
}
// Create empty CSRF configuration with default values
var csrfConfig middleware.CSRFConfig
// Get and validate CSRF secret from config
csrfKey, err := middleware.ParseCSRFKey(cfg.CSRFSecret)
if err != nil {
cfg.Logger.Error("invalid csrf-secret",
slog.String("error", err.Error()),
slog.String("hint", "must be exactly 32 bytes and persist across restarts"))
return err
}
csrfConfig.Secret = csrfKey
// Only override specific settings when needed
if cfg.Env == "development" {
// In development, cookies often need to work without HTTPS
csrfConfig.Cookie.Secure = false
}
// Create middleware stack
stack := middleware.CreateStack(
middleware.RequestID(), // Generate a unique request ID
middleware.Logging(), // Log requests with structured logging
middleware.Recovery(), // Catch all panics
middleware.Timeout(32*time.Second), // Set request timeout
middleware.MaxBodySize(1024*1024), // 1MB size limit
middleware.SecureHeaders(), // Set secure headers
middleware.CORS(corsOptions), // CORS configuration
middleware.CSRF(csrfConfig), // CSRF protection
middleware.Compress(), // Response compression
authConfig.Middleware(), // OIDC authentication middleware
)
// Create HTTP server
server := http.Server{
Addr: ":" + cfg.Port,
Handler: stack(httpRequestRouter),
ReadTimeout: 4 * time.Second,
WriteTimeout: 8 * time.Second,
IdleTimeout: 16 * time.Second,
MaxHeaderBytes: 1024 * 1024, // 1MB
BaseContext: func(_ net.Listener) context.Context { return ctx }, // Pass base context to all requests
}
// Serve the templates and static files
// Serve templates from the "templates" directory
httpRequestRouter.Handle("/", http.FileServer(http.Dir("./templates")))
// Serve static files from the "static" directory
httpRequestRouter.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
// Log server startup with structured logging
cfg.Logger.Info("starting server",
slog.String("port", cfg.Port),
slog.String("environment", cfg.Env),
slog.String("address", "http://localhost:"+cfg.Port))
// Start server and log any errors
if err := server.ListenAndServe(); err != nil {
cfg.Logger.Error("server error", slog.Any("error", err))
return err
}
return nil
}