Interceptors
Goa proporciona una solución completa para el procesamiento de peticiones que combina interceptores de tipo seguro con patrones de middleware tradicionales. Esta guía cubre los tres enfoques.
Visión general
Cuando se procesan peticiones en un servicio Goa, se dispone de tres herramientas complementarias:
- Interceptores Goa: Acceso comprobado en tiempo de compilación a los tipos de dominio de tu servicio
- Middleware HTTP: Patrón estándar
http.Handlerpara problemas específicos de HTTP - Interceptores gRPC: Patrones gRPC estándar para necesidades específicas de RPC
Cuándo usar cada uno
| Preocupación | Herramienta |
|---|---|
| Validación de lógica de negocio Interceptores Goa | |
| Transformación de datos - Interceptores Goa | |
| Enriquecimiento de peticiones y respuestas (Goa Interceptors) | |
| Registro, rastreo Middleware HTTP/gRPC | |
| Compresión, CORS Middleware HTTP | |
| Gestión de metadatos Interceptores gRPC | |
| Limitación de velocidad HTTP/gRPC Middleware |
Interceptores gRPC
Los interceptores Goa proporcionan acceso seguro a los tipos de dominio de su servicio, con comprobaciones en tiempo de compilación y métodos de ayuda generados.
Definición de Interceptores
var RequestLogger = Interceptor("RequestLogger", func() {
Description("Logs incoming requests and their timing")
// Read status from result
ReadResult(func() {
Attribute("status")
})
// Add timing information to result
WriteResult(func() {
Attribute("processedAt")
Attribute("duration")
})
})
Aplicación de interceptores
Aplicar a nivel de servicio o método:
var _ = Service("calculator", func() {
// Apply to all methods
ServerInterceptor(RequestLogger)
Method("add", func() {
// Method-specific interceptor
ServerInterceptor(ValidateNumbers)
Payload(func() {
Attribute("a", Int)
Attribute("b", Int)
})
Result(func() {
Attribute("sum", Int)
Attribute("status", Int)
Attribute("processedAt", String)
Attribute("duration", Int)
})
})
})
Implementación de interceptores
func (i *ServerInterceptors) RequestLogger(ctx context.Context, info *RequestLoggerInfo, next goa.Endpoint) (any, error) {
start := time.Now()
// Call next interceptor or endpoint
res, err := next(ctx, info.RawPayload())
if err != nil {
return nil, err
}
// Access result through type-safe interface
r := info.Result(res)
// Add timing information
r.SetProcessedAt(time.Now().Format(time.RFC3339))
r.SetDuration(int(time.Since(start).Milliseconds()))
return res, nil
}
Patrones de acceso
Acceso de sólo lectura
var Monitor = Interceptor("Monitor", func() {
Description("Collects metrics without modifying data")
ReadPayload(func() {
Attribute("size")
})
ReadResult(func() {
Attribute("status")
})
})
Acceso de escritura
var Enricher = Interceptor("Enricher", func() {
Description("Adds context information")
WritePayload(func() {
Attribute("requestID")
})
WriteResult(func() {
Attribute("processedAt")
})
})
Acceso combinado
var DataProcessor = Interceptor("DataProcessor", func() {
Description("Processes both requests and responses")
ReadPayload(func() {
Attribute("rawData")
})
WritePayload(func() {
Attribute("processed")
})
ReadResult(func() {
Attribute("status")
})
WriteResult(func() {
Attribute("enriched")
})
})
Interceptores del lado del cliente
var ClientContext = Interceptor("ClientContext", func() {
Description("Enriches requests with client context")
WritePayload(func() {
Attribute("clientVersion")
Attribute("clientID")
})
ReadResult(func() {
Attribute("rateLimit")
Attribute("rateLimitRemaining")
})
})
var _ = Service("inventory", func() {
ClientInterceptor(ClientContext)
// ...
})
Interceptores de flujo
Para los métodos de streaming, utilice variantes de streaming:
var ServerProgressTracker = Interceptor("ServerProgressTracker", func() {
Description("Adds progress to server stream responses")
WriteStreamingResult(func() {
Attribute("percentComplete")
Attribute("itemsProcessed")
})
})
var ClientMetadataEnricher = Interceptor("ClientMetadataEnricher", func() {
Description("Enriches outgoing client stream messages")
WriteStreamingPayload(func() {
Attribute("clientTimestamp")
})
})
Orden de ejecución
- Interceptores a nivel de servicio (por orden de declaración)
- Interceptores a nivel de método (por orden de declaración)
- El punto final real
- Interceptores a nivel de método (orden inverso)
- Interceptores a nivel de servicio (orden inverso)
Middleware HTTP
El middleware HTTP gestiona los problemas a nivel de protocolo utilizando el patrón estándar http.Handler.
Pila de middleware común
mux := goahttp.NewMuxer()
// Add middleware (outermost to innermost)
mux.Use(debug.HTTP()) // Debug logging
mux.Use(otelhttp.NewMiddleware("service")) // OpenTelemetry
mux.Use(log.HTTP(ctx)) // Request logging
mux.Use(goahttpmiddleware.RequestID()) // Request ID
mux.Use(goahttpmiddleware.PopulateRequestContext()) // Goa context
Creación de middleware personalizado
func ExampleMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Pre-processing
start := time.Now()
next.ServeHTTP(w, r)
// Post-processing
log.Printf("Request took %v", time.Since(start))
})
}
Middleware de cabeceras de seguridad
func SecurityHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-XSS-Protection", "1; mode=block")
next.ServeHTTP(w, r)
})
}
Middleware de enriquecimiento del contexto
func ContextEnrichmentMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, "request.start", time.Now())
ctx = context.WithValue(ctx, "request.id", r.Header.Get("X-Request-ID"))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Middleware de gestión de errores
func ErrorHandlingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic recovered: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
Interceptores gRPC
los interceptores gRPC gestionan los problemas a nivel de protocolo de las llamadas RPC.
Interceptores Unarios
func LoggingInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("Method: %s, Duration: %v, Error: %v",
info.FullMethod, time.Since(start), err)
return resp, err
}
}
Interceptores de flujo
func StreamLoggingInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
start := time.Now()
err := handler(srv, ss)
log.Printf("Stream: %s, Duration: %v, Error: %v",
info.FullMethod, time.Since(start), err)
return err
}
}
Integración con Goa
func main() {
srv := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
MetadataInterceptor(),
LoggingInterceptor(),
MonitoringInterceptor(),
)),
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
StreamMetadataInterceptor(),
StreamLoggingInterceptor(),
)),
)
pb.RegisterServiceServer(srv, server)
}
Combinando los tres
He aquí cómo funcionan los tres enfoques juntos:
func main() {
// 1. Create service with Goa interceptors
svc := NewService()
interceptors := NewInterceptors(log.Default())
endpoints := NewEndpoints(svc, interceptors)
// 2. Set up HTTP with middleware
mux := goahttp.NewMuxer()
mux.Use(otelhttp.NewMiddleware("payment-svc"))
mux.Use(debug.HTTP())
mux.Use(log.HTTP(ctx))
httpServer := genhttp.New(endpoints, mux, dec, enc, eh, eh)
genhttp.Mount(mux, httpServer)
// 3. Set up gRPC with interceptors
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_recovery.UnaryServerInterceptor(),
grpc_prometheus.UnaryServerInterceptor,
)),
)
grpcSvr := gengrpc.New(endpoints, nil)
genpb.RegisterPaymentServer(grpcServer, grpcSvr)
}
Flujo de ejecución
Request Processing:
─────────────────────────────────────────────────────────────────>
HTTP/gRPC Middleware → Goa Interceptors → Service Method
Response Processing:
<─────────────────────────────────────────────────────────────────
Service Method → Goa Interceptors → HTTP/gRPC Middleware
Mejores prácticas
Interceptores Goa
- Usar para validación de lógica de negocio y transformación de datos
- Mantener los interceptores centrados en responsabilidades únicas
- Utilizar patrones de acceso seguros
Middleware HTTP
- Ordenar cuidadosamente el middleware (primero la recuperación de pánico, luego el logging, etc.)
- Precompile objetos costosos (regex, etc.)
- Utilice sync.Pool para los objetos asignados con frecuencia
Interceptores gRPC
- Centrarse en las preocupaciones a nivel de protocolo
- Manejar adecuadamente la cancelación de contexto
- Utilizar códigos de estado apropiados
General
- Probar los interceptores y el middleware de forma aislada
- Considerar el impacto en el rendimiento
- Documente la finalidad de cada interceptor
Véase también
- Referencia DSL - Definiciones DSL del interceptor
- Guía HTTP - Patrones de middleware específicos de HTTP
- Guía gRPC - Patrones de interceptores gRPC
- Documentación de Clue - Interceptores y middleware de observabilidad