-
Notifications
You must be signed in to change notification settings - Fork 208
feat(gRPC): add support for http header propagation to gRPC sources #2450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,79 @@ | ||||||||||||||||||||||
| package grpcremote | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import ( | ||||||||||||||||||||||
| "context" | ||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| "go.opentelemetry.io/otel" | ||||||||||||||||||||||
| "google.golang.org/grpc" | ||||||||||||||||||||||
| "google.golang.org/grpc/metadata" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // metadataCarrier adapts metadata.MD to the TextMapCarrier interface for OTEL propagation | ||||||||||||||||||||||
| type metadataCarrier struct { | ||||||||||||||||||||||
| metadata.MD | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func (mc metadataCarrier) Get(key string) string { | ||||||||||||||||||||||
| values := mc.MD.Get(key) | ||||||||||||||||||||||
| if len(values) == 0 { | ||||||||||||||||||||||
| return "" | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return values[0] | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func (mc metadataCarrier) Set(key string, value string) { | ||||||||||||||||||||||
| mc.MD.Set(key, value) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func (mc metadataCarrier) Keys() []string { | ||||||||||||||||||||||
| keys := make([]string, 0, len(mc.MD)) | ||||||||||||||||||||||
| for k := range mc.MD { | ||||||||||||||||||||||
| keys = append(keys, k) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return keys | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // httpHeadersKey is the context key for storing HTTP headers to forward | ||||||||||||||||||||||
| type httpHeadersKey struct{} | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // HeaderForwardingInterceptor creates a gRPC unary client interceptor that: | ||||||||||||||||||||||
| // 1. Extracts headers stored in the context | ||||||||||||||||||||||
| // 2. Forwards configured headers as gRPC metadata | ||||||||||||||||||||||
| // 3. Injects OTEL trace context into gRPC metadata | ||||||||||||||||||||||
| func HeaderForwardingInterceptor(headersToForward []string) grpc.UnaryClientInterceptor { | ||||||||||||||||||||||
| return func( | ||||||||||||||||||||||
| ctx context.Context, | ||||||||||||||||||||||
| method string, | ||||||||||||||||||||||
| req, reply interface{}, | ||||||||||||||||||||||
| cc *grpc.ClientConn, | ||||||||||||||||||||||
| invoker grpc.UnaryInvoker, | ||||||||||||||||||||||
| opts ...grpc.CallOption, | ||||||||||||||||||||||
| ) error { | ||||||||||||||||||||||
| md := make(metadata.MD) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Inject OTEL trace context | ||||||||||||||||||||||
| otel.GetTextMapPropagator().Inject(ctx, metadataCarrier{md}) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Extract HTTP headers from context if available | ||||||||||||||||||||||
| if httpHeaders, ok := ctx.Value(httpHeadersKey{}).(http.Header); ok && httpHeaders != nil { | ||||||||||||||||||||||
| // Forward configured headers from HTTP headers to gRPC metadata | ||||||||||||||||||||||
| for _, headerName := range headersToForward { | ||||||||||||||||||||||
| if values := httpHeaders.Values(headerName); len(values) > 0 { | ||||||||||||||||||||||
| // gRPC metadata keys are lowercase | ||||||||||||||||||||||
| md.Append(headerName, values...) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+61
to
+65
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Header names should be lowercased before appending to gRPC metadata. The comment on line 63 correctly notes that "gRPC metadata keys are lowercase", but 🔧 Proposed fix to lowercase header names+import "strings"
+
// Forward configured headers from HTTP headers to gRPC metadata
for _, headerName := range headersToForward {
if values := httpHeaders.Values(headerName); len(values) > 0 {
// gRPC metadata keys are lowercase
- md.Append(headerName, values...)
+ md.Append(strings.ToLower(headerName), values...)
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Create outgoing context with metadata | ||||||||||||||||||||||
| ctx = metadata.NewOutgoingContext(ctx, md) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return invoker(ctx, method, req, reply, cc, opts...) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // WithHTTPHeaders stores HTTP headers in the context for later use by the interceptor | ||||||||||||||||||||||
| func WithHTTPHeaders(ctx context.Context, headers http.Header) context.Context { | ||||||||||||||||||||||
| return context.WithValue(ctx, httpHeadersKey{}, headers) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 2613
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 1824
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 103
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 2285
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 2249
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 458
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 1945
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 1673
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 230
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 1091
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 2895
🏁 Script executed:
Repository: wundergraph/cosmo
Length of output: 9457
Regex-based header propagation rules are not supported for gRPC subgraphs.
The
PropagatedHeadersfunction returns both explicit header names and regex-based patterns, but only the explicit names are forwarded to gRPC subgraphs (the regex patterns are discarded). TheHeaderForwardingInterceptoronly supports exact header name matching and has no infrastructure for evaluating regex patterns. Consider adding a comment explaining this is a deliberate limitation of the gRPC implementation, as HTTP subscriptions support both header name and regex-based propagation rules.🤖 Prompt for AI Agents