diff --git a/router/core/subscription_response_writer.go b/router/core/subscription_response_writer.go index 6d39f32dc8..abe951a380 100644 --- a/router/core/subscription_response_writer.go +++ b/router/core/subscription_response_writer.go @@ -195,6 +195,9 @@ func GetSubscriptionResponseWriter(ctx *resolve.Context, r *http.Request, w http if wgParams.UseMultipart || wgParams.UseSse { ctx.ExecutionOptions.SendHeartbeat = true + // Flush the response head immediately so the client establishes the connection + // before the first message, instead of blocking until one is streamed. + flusher.Flush() } return ctx, flushWriter, true diff --git a/router/core/subscription_response_writer_test.go b/router/core/subscription_response_writer_test.go index 584fba9ddd..02db6b7400 100644 --- a/router/core/subscription_response_writer_test.go +++ b/router/core/subscription_response_writer_test.go @@ -1,10 +1,15 @@ package core import ( - "github.com/stretchr/testify/assert" + "context" "net/http" + "net/http/httptest" "net/url" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve" ) func TestNegotiateSubscriptionParams(t *testing.T) { @@ -121,3 +126,21 @@ func TestNegotiateSubscriptionParams(t *testing.T) { }) } } + +func TestGetSubscriptionResponseWriter(t *testing.T) { + // Headers set on a ResponseWriter are only sent to the client on the first + // Write/WriteHeader/Flush. An SSE subscription must flush the response head + // (200 + text/event-stream) as soon as it is established, otherwise clients + // block until the first message arrives instead of connecting immediately. + t.Run("flushes the SSE response head before any message is written", func(t *testing.T) { + recorder := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/graphql", nil) + req.Header.Set("Accept", sseMimeType) + + _, _, ok := GetSubscriptionResponseWriter(resolve.NewContext(context.Background()), req, recorder, false) + require.True(t, ok) + + assert.Equal(t, sseMimeType, recorder.Header().Get("Content-Type")) + assert.True(t, recorder.Flushed, "expected the SSE response head to be flushed before any message is written") + }) +}