Repository: open-telemetry/opentelemetry-go-contrib (go)

Issue:
Error logs coming for http: superfluous response.WriteHeader call when using otelhttp handler and http.Flusher interface
### Description

Same bug as #6053 which has not been fixed fully. When using the Flusher interface, for example for SSE, the bug still manifests.

### Environment

- OS: macOS
- Architecture: arm64
- Go Version: 1.23.0
- `otelhttp` version: main (06ace3e8e57f)

### Steps To Reproduce

1. Using this code:

```
package main

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		flusher, ok := w.(http.Flusher)
		if !ok {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "text/event-stream")
		w.Header().Set("Cache-Control", "no-cache")
		w.Header().Set("Connection", "keep-alive")

		t := time.NewTicker(time.Second * 10)
		defer t.Stop()

		_, err := w.Write(formatSSE("ping", ""))
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		flusher.Flush()

		for {
			select {
			case <-r.Context().Done():
				return
			case <-t.C:
				_, err := w.Write(formatSSE("ping", ""))
				if err != nil {
					w.WriteHeader(http.StatusInternalServerError)
					return
				}
				flusher.Flush()
			}
		}
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("Hello, World!"))
	})

	otelHandler := otelhttp.NewHandler(handler, "simple-server")

	server := http.Server{
		Addr:    ":8080",
		Handler: otelHandler,
	}

	if err := server.ListenAndServe(); http.ErrServerClosed != err {
		fmt.Printf("Error: %v\n", err)
	}

	fmt.Println("Server stopped")
}

func formatSSE(event string, data string) []byte {
	eventPayload := "event: " + event + "\n"
	dataLines := strings.Split(data, "\n")
	for _, line := range dataLines {
		eventPayload = eventPayload + "data: " + line + "\n"
	}
	return []byte(eventPayload + "\n")
}
```

3. Run:

```
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@main

go run main.go

curl http://localhost:8080
```

4. See error ... (Sent for each SSE event sent to the client)

```
2024/09/04 12:27:52 http: superfluous response.WriteHeader call from go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request.(*RespWriterWrapper).writeHeader (resp_writer_wrapper.go:80)
```

### Expected behavior

No output


Pull request:
Fix superfluous writing header after flush in otelhttp
Closes #6073

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4912a5b27..038445e6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 
 ### Fixed
 
+- Superfluous call to `WriteHeader` when flushing after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6074)
 - Superfluous call to `WriteHeader` when writing the response body after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6055)
 
 <!-- Released section -->
diff --git a/instrumentation/net/http/otelhttp/internal/request/resp_writer_wrapper.go b/instrumentation/net/http/otelhttp/internal/request/resp_writer_wrapper.go
index c5f2fa805..fbc344cbd 100644
--- a/instrumentation/net/http/otelhttp/internal/request/resp_writer_wrapper.go
+++ b/instrumentation/net/http/otelhttp/internal/request/resp_writer_wrapper.go
@@ -82,7 +82,12 @@ func (w *RespWriterWrapper) writeHeader(statusCode int) {
 
 // Flush implements [http.Flusher].
 func (w *RespWriterWrapper) Flush() {
-	w.WriteHeader(http.StatusOK)
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	if !w.wroteHeader {
+		w.writeHeader(http.StatusOK)
+	}
 
 	if f, ok := w.ResponseWriter.(http.Flusher); ok {
 		f.Flush()


Test patch:
diff --git a/instrumentation/net/http/otelhttp/test/handler_test.go b/instrumentation/net/http/otelhttp/test/handler_test.go
index 780ec6fab..831db4c0a 100644
--- a/instrumentation/net/http/otelhttp/test/handler_test.go
+++ b/instrumentation/net/http/otelhttp/test/handler_test.go
@@ -215,6 +215,12 @@ func (rw *respWriteHeaderCounter) WriteHeader(statusCode int) {
 	rw.ResponseWriter.WriteHeader(statusCode)
 }
 
+func (rw *respWriteHeaderCounter) Flush() {
+	if f, ok := rw.ResponseWriter.(http.Flusher); ok {
+		f.Flush()
+	}
+}
+
 func TestHandlerPropagateWriteHeaderCalls(t *testing.T) {
 	testCases := []struct {
 		name                 string
@@ -265,6 +271,23 @@ func TestHandlerPropagateWriteHeaderCalls(t *testing.T) {
 			},
 			expectHeadersWritten: []int{http.StatusBadRequest},
 		},
+		{
+			name: "When writing the header indirectly through flush",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				f := w.(http.Flusher)
+				f.Flush()
+			},
+			expectHeadersWritten: []int{http.StatusOK},
+		},
+		{
+			name: "With a header already written when flushing",
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(http.StatusBadRequest)
+				f := w.(http.Flusher)
+				f.Flush()
+			},
+			expectHeadersWritten: []int{http.StatusBadRequest},
+		},
 	}
 
 	for _, tc := range testCases {