Add a timeout middleware to the server

This commit is contained in:
Christian Galo 2025-04-22 01:45:21 -05:00
parent d53afa4903
commit bfdf7bf7d2
2 changed files with 61 additions and 9 deletions

View File

@ -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)
}
}

View File

@ -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
}
})
}
}