diff --git a/cmd/start.go b/cmd/start.go index 4c9e445..50860c4 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -3,6 +3,7 @@ package cmd import ( "log" "net/http" + "time" "git.coopcloud.tech/wiki-cafe/member-console/internal/auth" "git.coopcloud.tech/wiki-cafe/member-console/internal/middleware" @@ -36,18 +37,23 @@ var startCmd = &cobra.Command{ // Create middleware stack stack := middleware.CreateStack( - middleware.Recovery(), // Add recovery middleware first to catch all panics - middleware.RequestID(), // Add request ID middleware for tracing - middleware.SecureHeaders, - middleware.Logging, - middleware.MaxBodySize(1024*1024), // 1MB size limit - authConfig.Middleware(), + middleware.Recovery(), // Catch all panics + middleware.TimeoutMiddleware(32*time.Second), // Set request timeout + middleware.RequestID(), // Generate a unique request ID + middleware.MaxBodySize(1024*1024), // 1MB size limit + middleware.SecureHeaders, // Set secure headers + middleware.Logging, // Log requests + authConfig.Middleware(), // OIDC authentication middleware ) // Create HTTP server server := http.Server{ - Addr: ":" + port, - Handler: stack(httpRequestRouter), + Addr: ":" + port, + Handler: stack(httpRequestRouter), + ReadTimeout: 2 * time.Second, + WriteTimeout: 4 * time.Second, + IdleTimeout: 8 * time.Second, + MaxHeaderBytes: 1024 * 1024, // 1MB } // Serve the components directory @@ -76,4 +82,4 @@ func init() { // Add the command to the root command rootCmd.AddCommand(startCmd) -} \ No newline at end of file +} diff --git a/internal/middleware/timeout.go b/internal/middleware/timeout.go new file mode 100644 index 0000000..77f789f --- /dev/null +++ b/internal/middleware/timeout.go @@ -0,0 +1,46 @@ +package middleware + +import ( + "context" + "net/http" + "time" +) + +// TimeoutMiddleware is necessary in addition to http.Server's ReadTimeout, +// WriteTimeout, and IdleTimeout. http.Server's timeouts are network-level +// timeouts, while this middleware's timeout is at the application level. +// TODO: Verify this statement + +// TimeoutMiddleware sets a timeout for each request +func TimeoutMiddleware(duration time.Duration) Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Create a context with a timeout + ctx, cancel := context.WithTimeout(r.Context(), duration) + defer cancel() + + // Create a channel to signal when the request is done + done := make(chan struct{}) + + // Create a new request with the timeout context + r = r.WithContext(ctx) + + // Use a goroutine to run the next handler + go func() { + next.ServeHTTP(w, r) + close(done) + }() + + // Wait for the handler to finish or the context to timeout + select { + case <-done: + // Request finished within the timeout + return + case <-ctx.Done(): + // Timeout occurred, respond with a timeout error + http.Error(w, "Request timed out", http.StatusGatewayTimeout) + return + } + }) + } +}