diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f46e4a937..b02f085283b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ The next release will require at least [Go 1.22]. - Add the `WithMetricsAttributesFn` option to allow setting dynamic, per-request metric attributes in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#5876) - The `go.opentelemetry.io/contrib/config` package supports configuring `with_resource_constant_labels` for the prometheus exporter. (#5890) - Support [Go 1.23]. (#6017) +- Implement `http.Hijacker` in`go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#5796) ### Removed diff --git a/instrgen/driver/driver b/instrgen/driver/driver new file mode 100755 index 00000000000..9ae93a34bd7 Binary files /dev/null and b/instrgen/driver/driver differ diff --git a/instrumentation/github.com/gorilla/mux/otelmux/mux.go b/instrumentation/github.com/gorilla/mux/otelmux/mux.go index c7b2355eca8..0e1168ee20e 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/mux.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/mux.go @@ -4,7 +4,9 @@ package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" import ( + "bufio" "fmt" + "net" "net/http" "sync" @@ -76,6 +78,13 @@ type recordingResponseWriter struct { status int } +func (h *recordingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hijacker, ok := h.writer.(http.Hijacker); ok { + return hijacker.Hijack() + } + return nil, nil, fmt.Errorf("underlying ResponseWriter does not support hijacking") +} + var rrwPool = &sync.Pool{ New: func() interface{} { return &recordingResponseWriter{} diff --git a/instrumentation/github.com/gorilla/mux/otelmux/test/mux_test.go b/instrumentation/github.com/gorilla/mux/otelmux/test/mux_test.go index d5e489b2eb6..5d2fa268b10 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/test/mux_test.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/test/mux_test.go @@ -4,8 +4,10 @@ package test import ( + "bufio" "context" "fmt" + "net" "net/http" "net/http/httptest" "testing" @@ -23,6 +25,74 @@ import ( "go.opentelemetry.io/otel/trace" ) +type recordingResponseWriter struct { + writer http.ResponseWriter + statusCode int + size int +} + +func (r *recordingResponseWriter) Header() http.Header { + return r.writer.Header() +} + +func (r *recordingResponseWriter) Write(b []byte) (int, error) { + if r.statusCode == 0 { + r.statusCode = http.StatusOK + } + size, err := r.writer.Write(b) + r.size += size + return size, err +} + +func (r *recordingResponseWriter) WriteHeader(statusCode int) { + r.statusCode = statusCode + r.writer.WriteHeader(statusCode) +} + +func (r *recordingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hj, ok := r.writer.(http.Hijacker) + if !ok { + return nil, nil, http.ErrNotSupported + } + return hj.Hijack() +} + +type mockHijacker struct { + http.ResponseWriter +} + +func (m *mockHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { + conn, rw := net.Pipe() + return conn, bufio.NewReadWriter(bufio.NewReader(rw), bufio.NewWriter(rw)), nil +} + +type mockNonHijacker struct { + http.ResponseWriter +} + +func TestRecordingResponseWriterHijack(t *testing.T) { + mockWriter := &mockHijacker{httptest.NewRecorder()} + recWriter := &recordingResponseWriter{writer: mockWriter} + + conn, rw, err := recWriter.Hijack() + require.NoError(t, err) + assert.NotNil(t, conn) + assert.NotNil(t, rw) + + err = conn.Close() + require.NoError(t, err) +} + +func TestRecordingResponseWriterHijackNonHijacker(t *testing.T) { + mockWriter := &mockNonHijacker{httptest.NewRecorder()} + recWriter := &recordingResponseWriter{writer: mockWriter} + + conn, rw, err := recWriter.Hijack() + require.NoError(t, err) + assert.NotNil(t, conn) + assert.NotNil(t, rw) +} + func TestCustomSpanNameFormatter(t *testing.T) { exporter := tracetest.NewInMemoryExporter()