Skip to content
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

Pass dynamic header to federated services #383

Open
mxncson opened this issue Jul 6, 2022 · 6 comments
Open

Pass dynamic header to federated services #383

mxncson opened this issue Jul 6, 2022 · 6 comments
Labels
internally-reviewed Internally reviewed

Comments

@mxncson
Copy link

mxncson commented Jul 6, 2022

Hello @jensneuse,

How do you pass a dynamic header to the http handler (engine) ?
I have a simple use case where the client send an auth id that i need to validate(gateway side) then forward others different headers to my services.

I don't see a direct way to interface with the underlying http components in the engine. How can i pass a custom and non-generic headers to services ?

I've read #270 and the other unanswered issue similar to mine, i have tested all of the above and nothing fit this case.

@jensneuse
Copy link
Member

In WunderGraph, we do it this way, works also for Federation: https://wundergraph.com/docs/guides/strategies/inject_short_lived_token_to_upstream_requests
That's OSS too, just a level of abstraction to make things easy.
If you want to dig deeper yourself, here's a pointer on how to access headers during execution:

Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$1$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($id: ID!){droid(id: $id){name aliased: name friends {name} primaryFunction} hero {name} stringList nestedStringList}","variables":{"id":$$0$$}}}`,

Client Request Headers need to be passed to the resolver as part of the execution context to be able to use them during resolving:

@aerfio
Copy link

aerfio commented Aug 15, 2022

@jensneuse
For me it's not working, I've debugged it quite a bit
Following an examples/federation, you can check that it's broken by applying following patch:

diff --git a/examples/federation/gateway/http/http.go b/examples/federation/gateway/http/http.go
index a29a4091..a90907ad 100644
--- a/examples/federation/gateway/http/http.go
+++ b/examples/federation/gateway/http/http.go
@@ -27,7 +27,9 @@ func (g *GraphQLHTTPRequestHandler) handleHTTP(w http.ResponseWriter, r *http.Re
 
 	buf := bytes.NewBuffer(make([]byte, 0, 4096))
 	resultWriter := graphql.NewEngineResultWriterFromBuffer(buf)
-	if err = g.engine.Execute(r.Context(), &gqlRequest, &resultWriter); err != nil {
+	if err = g.engine.Execute(r.Context(), &gqlRequest, &resultWriter, graphql.WithAdditionalHttpHeaders(http.Header{
+		"X-Request-ID": []string{"123456"},
+	})); err != nil {
 		g.log.Error("engine.Execute", log.Error(err))
 		w.WriteHeader(http.StatusInternalServerError)
 		return
diff --git a/examples/federation/products/server.go b/examples/federation/products/server.go
index 1cf18943..46a3ca7e 100644
--- a/examples/federation/products/server.go
+++ b/examples/federation/products/server.go
@@ -20,7 +20,10 @@ func main() {
 	}
 
 	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
-	http.Handle("/query", graph.GraphQLEndpointHandler(graph.EndpointOptions{EnableDebug: true, EnableRandomness: true}))
+	http.Handle("/query", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		log.Println(r.Header)
+		graph.GraphQLEndpointHandler(graph.EndpointOptions{EnableDebug: true, EnableRandomness: true}).ServeHTTP(w, r)
+	}))
 	http.HandleFunc("/websocket_connections", graph.WebsocketConnectionsHandler)
 
 	log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)

treat X-Request-ID by any user defined header.

If I debugged correctly it's because

func (e *ExecutionEngineV2) getCachedPlan(ctx *internalExecutionContext, operation, definition *ast.Document, operationName string, report *operationreport.Report) plan.Plan {

does not take into account any header that is in ctx.Request.Header.

@genesor
Copy link

genesor commented Aug 18, 2022

@aerfio there is another step required to have the HTTP Headers passed to the underlying services for the federated gateway.

When creating your schema configuration you need to add the header with a custom syntax that will look for a value in the original HTTP request.

serviceCfg := graphqlSchema.Configuration{
	Fetch: graphqlSchema.FetchConfiguration{
		URL:    url,
		Method: http.MethodPost,
		Header: http.Header{
			// .request.headers.XXX transmits incoming HTTP headers value to the
			// internal sub-requests made
			"X-Request-ID":          []string{"{{ .request.headers.X-Request-ID }}"},
		},
	},
	Federation: graphqlSchema.FederationConfiguration{
		Enabled:    true,
		ServiceSDL: sdl,
	},
}

@reistiago
Copy link

Should a similar config work for subscriptions?

I'm trying to propagate headers in the init message by doing something similar to what is here, but the options.Header is always empty.

I've seen this commit, that just landed in master, which does it in a different way, but similar in the end, but I should still have the problem of the headers being empty.

@BenjaminYong
Copy link
Contributor

Bump! Same as @reistiago , I am having difficulty propagating the headers from my subscription request to the federated subgraphs. It works fine for queries and mutations.

Here's my data source config:

dataSourceConfig := graphqlDataSource.Configuration{
	Fetch: graphqlDataSource.FetchConfiguration{
		URL:    serviceConfig.URL,
		Method: http.MethodPost,
		Header: http.Header{
			"Authorization": []string{"{{ .request.headers.Authorization }}"},
		},
	},
	Subscription: graphqlDataSource.SubscriptionConfiguration{
		URL: serviceConfig.WS,
	},
	Federation: graphqlDataSource.FederationConfiguration{
		Enabled:    true,
		ServiceSDL: sdl,
	},
}

I've debugged in my func (g *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) HTTP handler and can print the Authorization header in the request. It seems to be lost downstream or not sent to the subgraphs.

@StarpTech StarpTech added the internally-reviewed Internally reviewed label Mar 11, 2024
@IsabelFreitas-catapult
Copy link

IsabelFreitas-catapult commented May 17, 2024

Any updates on this? I am using github.com/wundergraph/graphql-go-tools v1.67.2 and the initialPayload is not being propagated to the subgraphs
@reistiago @BenjaminYong did you get this to work with subscriptions?
@genesor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
internally-reviewed Internally reviewed
Projects
None yet
Development

No branches or pull requests

8 participants