httpserver
The httpserver package provides an HTTP server with middleware support, composable handler groups, and graceful shutdown.
Core Components:
HTTPServer: HTTP server with middleware support and graceful shutdown. ImplementsRunnerinterface so it can be used as anapplicationservice.HandlerGroup: Composable group of HTTP handlers that share common middlewares. Implementshttp.Handlerfor nesting.Middleware: Interface for HTTP middleware with aWrap(http.Handler) http.Handlermethod.MiddlewareFunc: Function type that implementsMiddlewarefor inline middleware definitions.TraceIDMiddleware: Adds a unique trace ID to request context and response headers.RecoverMiddleware: Catches panics in handlers and returns HTTP 500 responses.FileServer: Serves static files from anfs.FS. ImplementsRunnerinterface.
Full package docs at pkg.go.dev
Step-by-step guide
Section titled “Step-by-step guide”-
Create a new HTTP server
server := httpserver.New("8080", 3*time.Second)The first argument is the port, the second is the shutdown timeout for graceful shutdown.
-
Register handlers
server.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("pong"))})server.Handle("/health", healthHandler)Use
HandleFuncfor handler functions orHandleforhttp.Handlerimplementations. -
Add middleware to the server
server.Use(httpserver.NewTraceIDMiddleware(nil, ""))server.Use(httpserver.NewRecoverMiddleware())Middlewares are applied in the order they are added.
TraceIDMiddlewareadds a trace ID to all requests,RecoverMiddlewarecatches panics. -
Create a handler group for nested routes
apiGroup := httpserver.NewHandlerGroup()apiGroup.HandleFunc("/users", usersHandler)apiGroup.HandleFunc("/posts", postsHandler)Handler groups let you organize routes with shared middleware.
-
Add middleware specific to the group
apiGroup.UseFunc(func(h http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {log.InfoContext(r.Context(), "API request", "path", r.URL.Path)h.ServeHTTP(w, r)})})Use
UseFuncto add inline middleware functions, orUseforMiddlewareimplementations. -
Mount the group on the server
server.HandleGroup("/api", apiGroup)The group is now accessible at
/api/usersand/api/posts. The path prefix is automatically stripped. -
Run the server
ctx := context.Background()if err := server.Run(ctx); err != nil {log.ErrorContext(ctx, "server error", "error", err)}Expected output:
time=2025-01-01T12:00:00.000+00:00 level=INFO msg="starting http server" address=:8080Press Ctrl+C to trigger graceful shutdown. The server waits for in-flight requests up to the shutdown timeout.
Using with Application
Section titled “Using with Application”Since HTTPServer implements the Runner interface, it can be registered as a service in an Application:
app := application.New()
server := httpserver.New("8080", 3*time.Second)server.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("pong"))})
app.RegisterService("api", server)
app.Run(ctx)The server also implements Healthchecker, so its health status is automatically tracked by the application.
Built-in middlewares
Section titled “Built-in middlewares”TraceIDMiddleware
Section titled “TraceIDMiddleware”Adds a unique UUID trace ID to each request’s context and response headers.
// Uses defaults: log.TraceIDKey for context, "Platforma-Trace-Id" headerserver.Use(httpserver.NewTraceIDMiddleware(nil, ""))
// Custom context key and headerserver.Use(httpserver.NewTraceIDMiddleware(myContextKey, "X-Request-ID"))The trace ID is available in handlers via r.Context().Value(log.TraceIDKey).
RecoverMiddleware
Section titled “RecoverMiddleware”Catches panics in handlers, logs the error with request context, and returns HTTP 500.
server.Use(httpserver.NewRecoverMiddleware())FileServer
Section titled “FileServer”Serves static files from an fs.FS implementation:
//go:embed staticvar staticFiles embed.FS
fileServer := httpserver.NewFileServer(staticFiles, "/static", "8080")app.RegisterService("files", fileServer)Complete example
Section titled “Complete example”package main
import ( "context" "net/http" "time"
"github.com/platforma-dev/platforma/application" "github.com/platforma-dev/platforma/httpserver" "github.com/platforma-dev/platforma/log")
func main() { ctx := context.Background()
// Initialize new application app := application.New()
// Create HTTP server api := httpserver.New("8080", 3*time.Second)
// Add /ping endpoint to `api` api.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("pong")) })
// Add /long endpoint to HTTP server to test graceful shutdown api.HandleFunc("/long", func(w http.ResponseWriter, r *http.Request) { time.Sleep(10 * time.Second) w.Write([]byte("pong")) })
// Add middleware to HTTP server. It will add trace ID to logs and responce headers api.Use(log.NewTraceIDMiddleware(nil, ""))
// Create handler group subApiGroup := httpserver.NewHandlerGroup()
// Add /clock endpoint to handler group subApiGroup.HandleFunc("/clock", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(time.Now().String())) })
// Add middleware to HTTP server. It will log all incoming requests to this handle group subApiGroup.UseFunc(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.InfoContext(r.Context(), "incoming request", "addr", r.RemoteAddr) h.ServeHTTP(w, r) }) })
// Add handle group to HTTP server with /subApi path api.HandleGroup("/subApi", subApiGroup)
// Register HTTP server as application server app.RegisterService("api", api)
// Run application if err := app.Run(ctx); err != nil { log.ErrorContext(ctx, "app finished with error", "error", err) }
// Now you can access http://localhost:8080/ping, http://localhost:8080/long // and http://localhost:8080/subApi/clock URLs with GET method}