Intercepteurs
Goa fournit une solution complète pour le traitement des requêtes qui combine des intercepteurs à sécurité de type avec des modèles d’intergiciels traditionnels. Ce guide couvre les trois approches.
Vue d’ensemble
Lorsque vous traitez des requêtes dans un service Goa, vous disposez de trois outils complémentaires :
- Intercepteurs Goa : Accès aux types du domaine de votre service, sécurisé et vérifié à la compilation
- Middleware HTTP : Modèle standard
http.Handlerpour les problèmes spécifiques à HTTP - Intercepteurs gRPC : Modèles gRPC standard pour les besoins spécifiques à RPC
Quand utiliser chacun
| Outil d’aide à la gestion de l’information | |
|---|---|
| Validation de la logique d’affaire - Goa Interceptors | |
| Transformation des données - Goa Interceptors - Enrichissement des requêtes/réponses - Goa Interceptors - Enjeux | |
| enrichissement des requêtes/réponses | Goa Interceptors |
| Logging, tracing | Middleware HTTP/gRPC |
| Compression, CORS | Middleware HTTP / gRPC |
| gestion des métadonnées | Intercepteurs gRPC |
| intercepteurs gRPC | Limitation de débit |
Intercepteurs Goa
Les intercepteurs Goa fournissent un accès sécurisé aux types du domaine de votre service, avec des vérifications à la compilation et des méthodes d’aide générées.
Définition des intercepteurs
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")
})
})
Application des intercepteurs
Appliquer au niveau du service ou de la méthode :
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)
})
})
})
Implémentation des intercepteurs
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
}
Modèles d’accès
Accès en lecture seule
var Monitor = Interceptor("Monitor", func() {
Description("Collects metrics without modifying data")
ReadPayload(func() {
Attribute("size")
})
ReadResult(func() {
Attribute("status")
})
})
Accès en écriture
var Enricher = Interceptor("Enricher", func() {
Description("Adds context information")
WritePayload(func() {
Attribute("requestID")
})
WriteResult(func() {
Attribute("processedAt")
})
})
Accès combiné
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")
})
})
Intercepteurs côté client
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)
// ...
})
Intercepteurs de flux
Pour les méthodes de diffusion en continu, utilisez les variantes de diffusion en continu :
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")
})
})
Ordre d’exécution
- Intercepteurs au niveau du service (dans l’ordre de déclaration)
- Intercepteurs au niveau de la méthode (dans l’ordre de déclaration)
- Le point final proprement dit
- Intercepteurs au niveau de la méthode (ordre inverse)
- Intercepteurs au niveau du service (ordre inverse)
Logiciel médiateur HTTP
L’intergiciel HTTP gère les problèmes au niveau du protocole en utilisant le modèle standard http.Handler.
Pile d’intergiciels courants
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
Création d’un intergiciel personnalisé
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 pour les en-têtes de sécurité
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 d’enrichissement du contexte
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 gestion des erreurs
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)
})
}
Intercepteurs gRPC
les intercepteurs gRPC gèrent les problèmes liés au protocole pour les appels RPC.
Intercepteurs unaires
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
}
}
Intercepteurs de flux
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
}
}
Intégration avec 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)
}
Combinaison des trois
Voici comment les trois approches fonctionnent ensemble :
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)
}
Flux d’exécution
Request Processing:
─────────────────────────────────────────────────────────────────>
HTTP/gRPC Middleware → Goa Interceptors → Service Method
Response Processing:
<─────────────────────────────────────────────────────────────────
Service Method → Goa Interceptors → HTTP/gRPC Middleware
Meilleures pratiques
Intercepteurs Goa
- Utiliser pour la validation de la logique d’entreprise et la transformation des données
- Garder les intercepteurs concentrés sur des responsabilités uniques
- Utiliser des modèles d’accès sûrs
Logiciel intermédiaire HTTP
- Ordonner soigneusement les intergiciels (récupération de panique d’abord, puis journalisation, etc.)
- Pré-compiler les objets coûteux (regex, etc.)
- Utiliser sync.Pool pour les objets fréquemment alloués
Intercepteurs gRPC
- Se concentrer sur les problèmes au niveau du protocole
- Gérer correctement l’annulation du contexte
- Utiliser les codes d’état appropriés
Général
- Tester les intercepteurs/médiateurs de manière isolée
- Tenir compte de l’impact sur les performances
- Documenter l’objectif de chaque intercepteur
Voir aussi
- Référence DSL - Définitions DSL de l’intercepteur
- HTTP Guide - Modèles d’intergiciels spécifiques à HTTP
- Guide gRPC - Modèles d’intercepteurs gRPC
- Clue Documentation - Intercepteurs et middleware d’observabilité