Add middleware
The httpserver includes a built-in middleware chain and supports custom middleware at both the router and route-group level.
Built-in middleware
Every httpserver automatically includes these middleware, applied in order:
- Sampling — Applies sampling decisions based on HTTP headers
- Metric — Records request count, response time, and status code metrics
- Logging — Logs request details using fingers-crossed scope (only logs on errors)
- Compression — Handles gzip compression/decompression
- Max body size — Limits incoming request bodies with
httpserver.<name>.max_body_bytes - Error — Catches errors attached to the Gin context and returns JSON error responses, using
httpserver.<name>.errors - Recovery — Catches panics and returns 500 errors
- Location — Extracts location information
- Connection lifecycle — Manages connection age and request count limits
You don't need to configure these — they are enabled by default.
Request body size limit
Every server includes MaxBodySizeMiddleware. The default limit is 10 MiB:
httpserver:
default:
max_body_bytes: 10485760
Set max_body_bytes to 0 to disable the limit. Oversized bodies fail when a handler reads the request body, so bound JSON/form handlers return a client error instead of invoking your handler.
Error privacy
Every server includes error middleware. By default, 5xx responses hide internal error details and return {"err":"internal server error"}. Client errors with a status below 500 still expose their error message.
httpserver:
default:
errors:
privacy: private
Set privacy to public only when clients should see internal 5xx error messages, for example in local development or trusted internal services:
httpserver:
default:
errors:
privacy: public
Custom middleware
Use router.Use() to add middleware to the entire server or a route group:
router.Use(func(ginCtx *gin.Context) {
start := time.Now()
ginCtx.Next()
elapsed := time.Since(start)
logger.Info(ctx, "request completed in %s", elapsed)
})
Apply middleware to a specific group:
api := router.Group("/api")
api.Use(authMiddleware)
api.GET("/users", listUsers)
Factory-based middleware
Use router.UseFactory() for middleware that needs access to config, logger, or the resolved server settings. The factory is called lazily when the router is built:
router.UseFactory(func(ctx context.Context, config cfg.Config, logger log.Logger, settings *httpserver.Settings) (gin.HandlerFunc, error) {
apiKey := config.GetString("api_key")
return func(ginCtx *gin.Context) {
if ginCtx.GetHeader("X-API-Key") != apiKey {
ginCtx.AbortWithStatus(401)
return
}
ginCtx.Next()
}, nil
})
settings.Name contains the server name, such as "default" for RunDefaultServer or "admin" for NewServer("admin", ...). This is useful for reusable middleware that reads server-scoped configuration.
The auth package provides settings-aware middleware factories for common authenticators, so the server name does not need to be hard-coded:
router.UseFactory(auth.ConfigKeyHandlerFactory(auth.ProvideValueFromHeader(auth.HeaderApiKey)))
CORS
The package provides a CORS middleware that reads configuration from the gosoline config:
api_cors_allowed_origin_pattern: ".*"
api_cors_allowed_headers:
- Content-Type
- Authorization
api_cors_allowed_methods:
- GET
- POST
- PUT
- DELETE
import "github.com/gosoline-project/httpserver"
// In your RouterFactory:
corsMiddleware, err := httpserver.Cors(config)
if err != nil {
return err
}
router.Use(corsMiddleware)
The origin pattern is matched against the full origin. For example, https://example\.com allows https://example.com, but not https://example.com.evil.com.
Or use the default Gin CORS middleware directly:
import "github.com/gin-contrib/cors"
router.Use(cors.Default())
Embedded static file serving
CreateEmbeddedStaticServe is itself a middleware factory:
router.UseFactory(httpserver.CreateEmbeddedStaticServe(publicFs, "public", "/api"))
See Serve a frontend for full details.
Complete example
main.go
package main
import (
"context"
"time"
"github.com/gin-gonic/gin"
"github.com/gosoline-project/httpserver"
"github.com/justtrackio/gosoline/pkg/cfg"
"github.com/justtrackio/gosoline/pkg/log"
)
func main() {
httpserver.RunDefaultServer(func(ctx context.Context, config cfg.Config, logger log.Logger, router *httpserver.Router) error {
router.Use(func(ginCtx *gin.Context) {
start := time.Now()
ginCtx.Next()
elapsed := time.Since(start)
logger.Info(ctx, "request completed in %s", elapsed)
})
router.UseFactory(func(ctx context.Context, config cfg.Config, logger log.Logger, settings *httpserver.Settings) (gin.HandlerFunc, error) {
return func(ginCtx *gin.Context) {
ginCtx.Header("X-Server-Name", settings.Name)
ginCtx.Next()
}, nil
})
router.GET("/ping", func(ginCtx *gin.Context) {
ginCtx.JSON(200, gin.H{"message": "pong"})
})
return nil
})
}
config.dist.yml
app:
env: dev
name: middleware
httpserver:
default:
port: 8088