File: resp_writer_wrapper.go

package info (click to toggle)
golang-opentelemetry-contrib 0.56.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,884 kB
  • sloc: makefile: 278; sh: 211; sed: 1
file content (119 lines) | stat: -rw-r--r-- 3,278 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"

import (
	"net/http"
	"sync"
)

var _ http.ResponseWriter = &RespWriterWrapper{}

// RespWriterWrapper wraps a http.ResponseWriter in order to track the number of
// bytes written, the last error, and to catch the first written statusCode.
// TODO: The wrapped http.ResponseWriter doesn't implement any of the optional
// types (http.Hijacker, http.Pusher, http.CloseNotifier, etc)
// that may be useful when using it in real life situations.
type RespWriterWrapper struct {
	http.ResponseWriter
	OnWrite func(n int64) // must not be nil

	mu          sync.RWMutex
	written     int64
	statusCode  int
	err         error
	wroteHeader bool
}

// NewRespWriterWrapper creates a new RespWriterWrapper.
//
// The onWrite attribute is a callback that will be called every time the data
// is written, with the number of bytes that were written.
func NewRespWriterWrapper(w http.ResponseWriter, onWrite func(int64)) *RespWriterWrapper {
	return &RespWriterWrapper{
		ResponseWriter: w,
		OnWrite:        onWrite,
		statusCode:     http.StatusOK, // default status code in case the Handler doesn't write anything
	}
}

// Write writes the bytes array into the [ResponseWriter], and tracks the
// number of bytes written and last error.
func (w *RespWriterWrapper) Write(p []byte) (int, error) {
	w.mu.Lock()
	defer w.mu.Unlock()

	if !w.wroteHeader {
		w.writeHeader(http.StatusOK)
	}

	n, err := w.ResponseWriter.Write(p)
	n1 := int64(n)
	w.OnWrite(n1)
	w.written += n1
	w.err = err
	return n, err
}

// WriteHeader persists initial statusCode for span attribution.
// All calls to WriteHeader will be propagated to the underlying ResponseWriter
// and will persist the statusCode from the first call.
// Blocking consecutive calls to WriteHeader alters expected behavior and will
// remove warning logs from net/http where developers will notice incorrect handler implementations.
func (w *RespWriterWrapper) WriteHeader(statusCode int) {
	w.mu.Lock()
	defer w.mu.Unlock()

	w.writeHeader(statusCode)
}

// writeHeader persists the status code for span attribution, and propagates
// the call to the underlying ResponseWriter.
// It does not acquire a lock, and therefore assumes that is being handled by a
// parent method.
func (w *RespWriterWrapper) writeHeader(statusCode int) {
	if !w.wroteHeader {
		w.wroteHeader = true
		w.statusCode = statusCode
	}
	w.ResponseWriter.WriteHeader(statusCode)
}

// Flush implements [http.Flusher].
func (w *RespWriterWrapper) Flush() {
	w.mu.Lock()
	defer w.mu.Unlock()

	if !w.wroteHeader {
		w.writeHeader(http.StatusOK)
	}

	if f, ok := w.ResponseWriter.(http.Flusher); ok {
		f.Flush()
	}
}

// BytesWritten returns the number of bytes written.
func (w *RespWriterWrapper) BytesWritten() int64 {
	w.mu.RLock()
	defer w.mu.RUnlock()

	return w.written
}

// BytesWritten returns the HTTP status code that was sent.
func (w *RespWriterWrapper) StatusCode() int {
	w.mu.RLock()
	defer w.mu.RUnlock()

	return w.statusCode
}

// Error returns the last error.
func (w *RespWriterWrapper) Error() error {
	w.mu.RLock()
	defer w.mu.RUnlock()

	return w.err
}