Refactor start.go to move server logic to internal server.go
This commit is contained in:
parent
4d29a58f94
commit
e5e9efd9b3
95
cmd/start.go
95
cmd/start.go
@ -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
107
internal/server/server.go
Normal 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user