Custom Middleware¶
Compiled Go services support custom middleware for advanced use cases like authentication, logging, request modification, and stateful behavior.
Overview¶
Custom middleware is applied after the standard middleware chain:
Request → [Standard Middleware] → [Custom Middleware] → Handler → Response
Standard: Config Override → Latency/Error → Replay Read/Write → Cache Read → Upstream → Cache Write
Standard middleware handles: latency, errors, replay, caching, upstream proxy.
Adding Custom Middleware¶
Edit middleware.go in your service directory:
package petstore
import (
"net/http"
"github.com/cubahno/connexions/v2/pkg/middleware"
)
func getMiddleware() []func(*middleware.Params) func(http.Handler) http.Handler {
return []func(*middleware.Params) func(http.Handler) http.Handler{
createAuthMiddleware,
createLoggingMiddleware,
}
}
func createAuthMiddleware(params *middleware.Params) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
Middleware Params¶
The middleware.Params struct provides access to service configuration and request history:
type Params struct {
ServiceConfig *config.ServiceConfig // Service configuration
History *history.CurrentRequestStorage // Request/response history
}
ServiceConfig¶
Access service configuration values:
func createConfigAwareMiddleware(params *middleware.Params) func(http.Handler) http.Handler {
serviceName := params.ServiceConfig.Name
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Service: %s, Path: %s", serviceName, r.URL.Path)
next.ServeHTTP(w, r)
})
}
}
Request History¶
Access the current request and previous requests/responses:
func createHistoryMiddleware(params *middleware.Params) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get current request record
record, exists := params.History.Get(r)
if exists {
log.Printf("Request body: %s", string(record.Body))
if record.Response != nil {
log.Printf("Previous response: %d", record.Response.StatusCode)
}
}
next.ServeHTTP(w, r)
})
}
}
RequestedResource¶
The history record contains:
type RequestedResource struct {
Resource string // OpenAPI path: /pets/{id}
Body []byte // Request body
Response *HistoryResponse // Previous response (if any)
Request *http.Request // Current HTTP request
ServiceStorage Storage // Per-service key-value storage
}
Service Storage¶
Each service has a thread-safe key-value storage for maintaining state across requests:
func createStatefulMiddleware(params *middleware.Params) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
record, _ := params.History.Get(r)
storage := record.ServiceStorage
// Increment request counter
count, _ := storage.Get("request_count")
if count == nil {
count = 0
}
storage.Set("request_count", count.(int) + 1)
// Store user session
userID := r.Header.Get("X-User-ID")
if userID != "" {
storage.Set("last_user", userID)
}
next.ServeHTTP(w, r)
})
}
}
Storage Interface¶
type Storage interface {
Get(key string) (any, bool)
Set(key string, value any)
Data() map[string]any
}
Note: Storage is cleared periodically based on historyDuration setting (default: 5 minutes).
Common Patterns¶
Request Logging¶
func createLoggingMiddleware(params *middleware.Params) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("→ %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("← %s %s (%v)", r.Method, r.URL.Path, time.Since(start))
})
}
}
Request Modification¶
func createHeaderMiddleware(params *middleware.Params) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add headers to response
w.Header().Set("X-Service", params.ServiceConfig.Name)
w.Header().Set("X-Request-ID", uuid.New().String())
next.ServeHTTP(w, r)
})
}
}
Conditional Logic¶
func createConditionalMiddleware(params *middleware.Params) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Only apply to specific paths
if strings.HasPrefix(r.URL.Path, "/admin") {
if r.Header.Get("X-Admin-Token") != "secret" {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
}
next.ServeHTTP(w, r)
})
}
}