Skip to content

feat: forward headers to grpc services#1382

Merged
dkorittki merged 6 commits intomasterfrom
dominik/eng-7125-allow-header-forwarding
Feb 18, 2026
Merged

feat: forward headers to grpc services#1382
dkorittki merged 6 commits intomasterfrom
dominik/eng-7125-allow-header-forwarding

Conversation

@dkorittki
Copy link
Copy Markdown
Contributor

@dkorittki dkorittki commented Feb 13, 2026

This is part of a Cosmo Connect related feature. It allows to send HTTP headers from user requests down to gRPC sources. Technically it converts headers to gRPC metadata on datasource.Load.

A header X-My-Custom-Header will be sent as a grpc metadata field x-my-custom-header with all grpc calls the datasource will make. The change is implemented solely on the grpc datasource. All headers are mapped one-to-one as they are given to the datasources Load method.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.

Summary by CodeRabbit

  • New Features

    • HTTP headers are now forwarded as gRPC metadata, allowing request headers to influence downstream behavior.
    • Metadata-driven overrides enabled for user IDs, names, and per-item counts via incoming headers.
    • Existing context metadata is preserved and composed with forwarded headers.
  • Tests

    • Added comprehensive tests validating header-driven overrides, metadata preservation, operation coverage (queries/mutations), and error scenarios.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Adds propagation of incoming HTTP headers into outgoing gRPC metadata for the gRPC datasource, plus tests and mock gRPC handlers that read those metadata keys and apply them to override response fields.

Changes

Cohort / File(s) Summary
gRPC datasource header propagation
v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go
Converts incoming HTTP headers to lowercase and appends them as gRPC metadata on the outgoing context for datasource Load calls. Added imports for strings and google.golang.org/grpc/metadata.
Header-driven datasource tests
v2/pkg/engine/datasource/grpc_datasource/grpc_datasource_test.go
Adds Test_Datasource_Load_WithHeaders and Test_Datasource_Load_PreservesExistingContextMetadata covering header-based overrides, metadata forwarding/preservation, error scenarios, and interactions across queries/mutations and nested types. Adds net/http and grpc/metadata test imports.
Mock gRPC service metadata handling
v2/pkg/grpctest/mockservice.go, v2/pkg/grpctest/mockservice_resolve.go
Mock service reads incoming gRPC metadata (e.g., x-user-prefix, x-user-id, x-custom-name, x-count-offset) and adjusts generated responses (user names/IDs, created user name, per-item product counts). Added grpc/metadata and strconv imports; minor param name change in resolve function signature.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (HTTP request)
  participant Engine as Engine Datasource
  participant GRPC as gRPC Service

  Client->>Engine: HTTP request with headers
  Engine->>Engine: Lowercase headers -> key/value pairs
  Engine->>GRPC: gRPC call (context with metadata)
  GRPC->>GRPC: Read metadata (x-user-id, x-user-prefix, x-custom-name, x-count-offset)
  GRPC-->>Engine: Response (data possibly overridden by metadata)
  Engine-->>Client: GraphQL response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective of the PR: forwarding HTTP headers to gRPC services. It is clear, concise, and directly reflects the primary change implemented across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dominik/eng-7125-allow-header-forwarding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dkorittki dkorittki force-pushed the dominik/eng-7125-allow-header-forwarding branch from 9637a05 to 402694e Compare February 13, 2026 16:37
@dkorittki dkorittki marked this pull request as ready for review February 16, 2026 17:25
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go (1)

95-125: ⚠️ Potential issue | 🟠 Major

Use metadata.Join to preserve upstream outgoing context metadata.

metadata.NewOutgoingContext replaces any existing outgoing metadata on the context rather than merging with it. If upstream code sets auth, tracing, or other metadata before calling Load, it will be lost. Merge with existing metadata before creating the new outgoing context.

🛠️ Suggested fix
 	// convert headers to grpc metadata and attach to ctx
 	if len(headers) > 0 {
 		md := make(metadata.MD, len(headers))
 		for k, v := range headers {
 			md.Set(strings.ToLower(k), v...)
 		}
+		if existing, ok := metadata.FromOutgoingContext(ctx); ok {
+			md = metadata.Join(existing, md)
+		}
 		ctx = metadata.NewOutgoingContext(ctx, md)
 	}
🧹 Nitpick comments (1)
v2/pkg/grpctest/mockservice_resolve.go (1)

833-852: Guard x-count-offset parsing against int32 overflow.

Line 843 uses strconv.Atoi then casts to int32, which can wrap on large values. Parsing with a 32‑bit bound keeps offsets predictable.

🔧 Suggested change
-			if v, err := strconv.Atoi(values[0]); err == nil {
-				offset = int32(v)
-			}
+			if v, err := strconv.ParseInt(values[0], 10, 32); err == nil {
+				offset = int32(v)
+			}

Copy link
Copy Markdown
Member

@jensneuse jensneuse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two minor points worth considering

NewOutgoingContext vs AppendToOutgoingContext: The code uses metadata.NewOutgoingContext, which replaces any existing outgoing metadata on the context. If the ctx ever arrives with pre-existing gRPC metadata, it would be lost. Using metadata.AppendToOutgoingContext would be safer for forward-compatibility, though today this isn’t an issue since the ctx comes from an HTTP request context.

No HTTP-specific header filtering: Headers like content-type, content-length, host, connection are meaningless as gRPC metadata and could theoretically conflict (e.g., content-type: application/json clashing with gRPC’s application/grpc). Headers starting with grpc- are reserved by gRPC itself. However, since the router controls what gets passed, this is a policy decision for the router, not this library — and the HTTP datasource makes the same trust assumption.

@dkorittki
Copy link
Copy Markdown
Contributor Author

dkorittki commented Feb 17, 2026

Good catches, I added a commit fixing this.

However, since the router controls what gets passed, this is a policy decision for the router, not this library — and the HTTP datasource makes the same trust assumption.

I'll add the filtering in the router PR then. I will leave this one open until the router PR is ready because if I would merge this straight then we would get all headers immediately without filtering.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go (1)

95-129: ⚠️ Potential issue | 🟠 Major

Filter/validate HTTP header names before converting to gRPC metadata.

gRPC metadata keys enforce strict validation: only ASCII 0-9, a-z, A-Z (normalized to lowercase), and - _ . are allowed. The grpc- prefix is reserved for gRPC internals and forwarding arbitrary HTTP headers without validation risks gRPC rejecting the call or causing errors. Currently, the code only lowercases headers but does not validate the key format or skip reserved names before passing to metadata.AppendToOutgoingContext.

Consider implementing a validation check to skip invalid keys and the grpc- prefix.

🔧 Suggested guard
 	if len(headers) > 0 {
 		// assume that each header has exactly one value for default pairs size
 		pairs := make([]string, 0, len(headers)*2)
 		for headerName, headerValues := range headers {
 			headerName = strings.ToLower(headerName)
+			if !isValidMetadataKey(headerName) || strings.HasPrefix(headerName, "grpc-") {
+				continue
+			}
 			for _, v := range headerValues {
 				pairs = append(pairs, headerName, v)
 			}
 		}
 		ctx = metadata.AppendToOutgoingContext(ctx, pairs...)
 	}
+
+func isValidMetadataKey(k string) bool {
+	if k == "" {
+		return false
+	}
+	for i := 0; i < len(k); i++ {
+		c := k[i]
+		if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' {
+			continue
+		}
+		return false
+	}
+	return true
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go` around lines 95
- 129, In Load, before converting HTTP headers to gRPC metadata (the block that
builds pairs and calls metadata.AppendToOutgoingContext), validate and filter
header keys: normalize to lowercase, skip any key that begins with the reserved
prefix "grpc-" and skip keys that contain characters outside the allowed set
(ASCII letters a–z, digits 0–9, and the characters '-', '_' and '.'); only
append pairs for keys that pass this check so metadata.AppendToOutgoingContext
receives only valid gRPC keys. Use the existing headers iteration in Load (the
pairs creation logic) to apply this filter so no change is needed to
acquirePoolItem or newJSONBuilder usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go`:
- Around line 95-129: In Load, before converting HTTP headers to gRPC metadata
(the block that builds pairs and calls metadata.AppendToOutgoingContext),
validate and filter header keys: normalize to lowercase, skip any key that
begins with the reserved prefix "grpc-" and skip keys that contain characters
outside the allowed set (ASCII letters a–z, digits 0–9, and the characters '-',
'_' and '.'); only append pairs for keys that pass this check so
metadata.AppendToOutgoingContext receives only valid gRPC keys. Use the existing
headers iteration in Load (the pairs creation logic) to apply this filter so no
change is needed to acquirePoolItem or newJSONBuilder usage.

@dkorittki dkorittki merged commit 8459b34 into master Feb 18, 2026
10 checks passed
@dkorittki dkorittki deleted the dominik/eng-7125-allow-header-forwarding branch February 18, 2026 15:48
jensneuse pushed a commit that referenced this pull request Feb 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.0.0-rc.252](v2.0.0-rc.251...v2.0.0-rc.252)
(2026-02-19)


### Features

* forward headers to grpc subgraphs
([#1382](#1382))
([8459b34](8459b34))


### Bug Fixes

* **resolve:** fix flaky singleflight deduplication tests
([#1393](#1393))
([4105082](4105082))
* **resolve:** guard OnFinished against nil loaderHookContext on skipped
fetches
([#1394](#1394))
([f79d071](f79d071))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants