Repository: 99designs/gqlgen
Pull request #3383 (2024-11-21 21:29:08)

websocket subscribe missing header bugfix
Make serving requests from websockets, the headers of the request are being lost along the function chain. 

This PR adds them back along so that the server is able to access the request headers via the 

`graphql.GetOperationContext(ctx)` method 


I have:
 - [X] Added tests covering the bug / feature (see [testing](https://github.com/99designs/gqlgen/blob/master/TESTING.md))
 - [X] Updated any relevant documentation (see [docs](https://github.com/99designs/gqlgen/tree/master/docs/content))


coveralls: 
[![Coverage Status](https://coveralls.io/builds/71010613/badge)](https://coveralls.io/builds/71010613)

coverage: 59.587% (-0.01%) from 59.601%
when pulling **7632bbbe0ec6617591ffb82b83f1eed61cc2ba96 on jeremylaier-tc:ws-header-fix**
into **1040b9255c0899df507a96b106f82be219df8cc6 on 99designs:master**.


Patch:
diff --git a/graphql/handler/transport/websocket.go b/graphql/handler/transport/websocket.go
index 32e31c7c..d6c174cd 100644
--- a/graphql/handler/transport/websocket.go
+++ b/graphql/handler/transport/websocket.go
@@ -54,6 +54,7 @@ type (
 		receivedPong    bool
 		exec            graphql.GraphExecutor
 		closed          bool
+		headers         http.Header
 
 		initPayload InitPayload
 	}
@@ -119,6 +120,7 @@ func (t Websocket) Do(w http.ResponseWriter, r *http.Request, exec graphql.Graph
 		ctx:       r.Context(),
 		exec:      exec,
 		me:        me,
+		headers:   r.Header,
 		Websocket: t,
 	}
 
@@ -387,6 +389,8 @@ func (c *wsConnection) subscribe(start time.Time, msg *message) {
 		End:   graphql.Now(),
 	}
 
+	params.Headers = c.headers
+
 	rc, err := c.exec.CreateOperationContext(ctx, params)
 	if err != nil {
 		resp := c.exec.DispatchError(graphql.WithOperationContext(ctx, rc), err)


Test patch:
diff --git a/graphql/handler/transport/websocket_test.go b/graphql/handler/transport/websocket_test.go
index ec47c422..cc020180 100644
--- a/graphql/handler/transport/websocket_test.go
+++ b/graphql/handler/transport/websocket_test.go
@@ -189,6 +189,48 @@ func TestWebsocketWithKeepAlive(t *testing.T) {
 	assert.Equal(t, connectionKeepAliveMsg, msg.Type)
 }
 
+func TestWebsocketWithPassedHeaders(t *testing.T) {
+	h := testserver.New()
+	h.AddTransport(transport.Websocket{
+		KeepAlivePingInterval: 100 * time.Millisecond,
+	})
+
+	h.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
+		assert.NotNil(t, graphql.GetOperationContext(ctx).Headers)
+
+		return next(ctx)
+	})
+
+	srv := httptest.NewServer(h)
+	defer srv.Close()
+
+	c := wsConnect(srv.URL)
+	defer c.Close()
+
+	require.NoError(t, c.WriteJSON(&operationMessage{Type: connectionInitMsg}))
+	assert.Equal(t, connectionAckMsg, readOp(c).Type)
+	assert.Equal(t, connectionKeepAliveMsg, readOp(c).Type)
+
+	require.NoError(t, c.WriteJSON(&operationMessage{
+		Type:    startMsg,
+		ID:      "test_1",
+		Payload: json.RawMessage(`{"query": "subscription { name }"}`),
+	}))
+
+	// keepalive
+	msg := readOp(c)
+	assert.Equal(t, connectionKeepAliveMsg, msg.Type)
+
+	// server message
+	h.SendNextSubscriptionMessage()
+	msg = readOp(c)
+	assert.Equal(t, dataMsg, msg.Type)
+
+	// keepalive
+	msg = readOp(c)
+	assert.Equal(t, connectionKeepAliveMsg, msg.Type)
+}
+
 func TestWebsocketInitFunc(t *testing.T) {
 	t.Run("accept connection if WebsocketInitFunc is NOT provided", func(t *testing.T) {
 		h := testserver.New()