Skip to content

Commit 2b9fca2

Browse files
authored
Honor Discard() in httpFancyWriter.ReadFrom (#1110)
1 parent 3b17157 commit 2b9fca2

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

middleware/wrap_writer.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,12 @@ func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {
207207
}
208208

209209
func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) {
210-
if f.basicWriter.tee != nil {
211-
// Route through basicWriter.Write so that data is also written to the
212-
// tee writer. basicWriter.Write already increments basicWriter.bytes,
213-
// so we must NOT add n again here (that would double-count).
210+
if f.basicWriter.tee != nil || f.basicWriter.discard {
211+
// Route through basicWriter.Write so that the tee and discard semantics
212+
// are honored (the fast ReaderFrom path below would bypass both, writing
213+
// straight to the original ResponseWriter). basicWriter.Write already
214+
// increments basicWriter.bytes, so we must NOT add n again here (that
215+
// would double-count).
214216
n, err := io.Copy(&f.basicWriter, r)
215217
return n, err
216218
}

middleware/wrap_writer_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,26 @@ package middleware
22

33
import (
44
"bytes"
5+
"io"
56
"net/http"
67
"net/http/httptest"
78
"strings"
89
"testing"
910
)
1011

12+
// readerFromRecorder is a ResponseRecorder that also satisfies io.ReaderFrom,
13+
// mirroring the real net/http response writer (which implements ReadFrom and
14+
// is what httpFancyWriter.ReadFrom fast-paths to). Bytes copied via ReadFrom
15+
// are recorded to the body so a test can observe whether they reached the
16+
// original writer.
17+
type readerFromRecorder struct {
18+
*httptest.ResponseRecorder
19+
}
20+
21+
func (r *readerFromRecorder) ReadFrom(src io.Reader) (int64, error) {
22+
return io.Copy(r.ResponseRecorder.Body, src)
23+
}
24+
1125
func TestHttpFancyWriterRemembersWroteHeaderWhenFlushed(t *testing.T) {
1226
f := &httpFancyWriter{basicWriter: basicWriter{ResponseWriter: httptest.NewRecorder()}}
1327
f.Flush()
@@ -109,3 +123,25 @@ func TestHttpFancyWriterReadFromByteCountWithTee(t *testing.T) {
109123
assertEqual(t, len(input), f.BytesWritten())
110124
assertEqual(t, []byte(input), teeBuf.Bytes())
111125
}
126+
127+
// TestHttpFancyWriterReadFromHonorsDiscard verifies that Discard() suppresses
128+
// the body even when it is written via ReadFrom/io.Copy. Before the fix, the
129+
// non-tee ReadFrom path streamed straight to the original ResponseWriter's
130+
// ReaderFrom, bypassing the discard flag entirely.
131+
func TestHttpFancyWriterReadFromHonorsDiscard(t *testing.T) {
132+
original := &readerFromRecorder{&httptest.ResponseRecorder{
133+
HeaderMap: make(http.Header),
134+
Body: new(bytes.Buffer),
135+
}}
136+
f := &httpFancyWriter{basicWriter: basicWriter{ResponseWriter: original}}
137+
f.Discard()
138+
139+
const input = "hello world"
140+
n, err := f.ReadFrom(strings.NewReader(input))
141+
assertNoError(t, err)
142+
assertEqual(t, int64(len(input)), n)
143+
// The data must NOT reach the original writer once Discard() is set.
144+
assertEqual(t, 0, original.Body.Len())
145+
// BytesWritten still reflects the bytes consumed.
146+
assertEqual(t, len(input), f.BytesWritten())
147+
}

0 commit comments

Comments
 (0)