Skip to content

Commit 3f07cc7

Browse files
authored
Implement http.Flusher interface on Log middleware (#257)
This adds `Flush()` to the `middleware.badResponseLoggingWriter`, making it implement `http.Flusher` if the wrapped `http.ResponseWriter` does.
1 parent ac27843 commit 3f07cc7

File tree

3 files changed

+82
-14
lines changed

3 files changed

+82
-14
lines changed

middleware/logging.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (l Log) Wrap(next http.Handler) http.Handler {
5454
wrapped := newBadResponseLoggingWriter(w, &buf)
5555
next.ServeHTTP(wrapped, r)
5656

57-
statusCode, writeErr := wrapped.statusCode, wrapped.writeError
57+
statusCode, writeErr := wrapped.getStatusCode(), wrapped.getWriteError()
5858

5959
if writeErr != nil {
6060
if errors.Is(writeErr, context.Canceled) {

middleware/response.go

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@ const (
1212
maxResponseBodyInLogs = 4096 // At most 4k bytes from response bodies in our logs.
1313
)
1414

15-
// badResponseLoggingWriter writes the body of "bad" responses (i.e. 5xx
15+
type badResponseLoggingWriter interface {
16+
http.ResponseWriter
17+
getStatusCode() int
18+
getWriteError() error
19+
}
20+
21+
// nonFlushingBadResponseLoggingWriter writes the body of "bad" responses (i.e. 5xx
1622
// responses) to a buffer.
17-
type badResponseLoggingWriter struct {
23+
type nonFlushingBadResponseLoggingWriter struct {
1824
rw http.ResponseWriter
1925
buffer io.Writer
2026
logBody bool
@@ -23,27 +29,39 @@ type badResponseLoggingWriter struct {
2329
writeError error // The error returned when downstream Write() fails.
2430
}
2531

26-
// newBadResponseLoggingWriter makes a new badResponseLoggingWriter.
27-
func newBadResponseLoggingWriter(rw http.ResponseWriter, buffer io.Writer) *badResponseLoggingWriter {
28-
return &badResponseLoggingWriter{
32+
// flushingBadResponseLoggingWriter is a badResponseLoggingWriter that
33+
// implements http.Flusher.
34+
type flushingBadResponseLoggingWriter struct {
35+
nonFlushingBadResponseLoggingWriter
36+
f http.Flusher
37+
}
38+
39+
func newBadResponseLoggingWriter(rw http.ResponseWriter, buffer io.Writer) badResponseLoggingWriter {
40+
b := nonFlushingBadResponseLoggingWriter{
2941
rw: rw,
3042
buffer: buffer,
3143
logBody: false,
3244
bodyBytesLeft: maxResponseBodyInLogs,
3345
statusCode: http.StatusOK,
3446
}
47+
48+
if f, ok := rw.(http.Flusher); ok {
49+
return &flushingBadResponseLoggingWriter{b, f}
50+
}
51+
52+
return &b
3553
}
3654

3755
// Header returns the header map that will be sent by WriteHeader.
3856
// Implements ResponseWriter.
39-
func (b *badResponseLoggingWriter) Header() http.Header {
57+
func (b *nonFlushingBadResponseLoggingWriter) Header() http.Header {
4058
return b.rw.Header()
4159
}
4260

4361
// Write writes HTTP response data.
44-
func (b *badResponseLoggingWriter) Write(data []byte) (int, error) {
62+
func (b *nonFlushingBadResponseLoggingWriter) Write(data []byte) (int, error) {
4563
if b.statusCode == 0 {
46-
// WriteHeader has (probably) not been called, so we need to call it with StatusOK to fuflil the interface contract.
64+
// WriteHeader has (probably) not been called, so we need to call it with StatusOK to fulfill the interface contract.
4765
// https://godoc.org/net/http#ResponseWriter
4866
b.WriteHeader(http.StatusOK)
4967
}
@@ -58,7 +76,7 @@ func (b *badResponseLoggingWriter) Write(data []byte) (int, error) {
5876
}
5977

6078
// WriteHeader writes the HTTP response header.
61-
func (b *badResponseLoggingWriter) WriteHeader(statusCode int) {
79+
func (b *nonFlushingBadResponseLoggingWriter) WriteHeader(statusCode int) {
6280
b.statusCode = statusCode
6381
if statusCode >= 500 {
6482
b.logBody = true
@@ -67,15 +85,23 @@ func (b *badResponseLoggingWriter) WriteHeader(statusCode int) {
6785
}
6886

6987
// Hijack hijacks the first response writer that is a Hijacker.
70-
func (b *badResponseLoggingWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
88+
func (b *nonFlushingBadResponseLoggingWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
7189
hj, ok := b.rw.(http.Hijacker)
7290
if ok {
7391
return hj.Hijack()
7492
}
7593
return nil, nil, fmt.Errorf("badResponseLoggingWriter: can't cast underlying response writer to Hijacker")
7694
}
7795

78-
func (b *badResponseLoggingWriter) captureResponseBody(data []byte) {
96+
func (b *nonFlushingBadResponseLoggingWriter) getStatusCode() int {
97+
return b.statusCode
98+
}
99+
100+
func (b *nonFlushingBadResponseLoggingWriter) getWriteError() error {
101+
return b.writeError
102+
}
103+
104+
func (b *nonFlushingBadResponseLoggingWriter) captureResponseBody(data []byte) {
79105
if len(data) > b.bodyBytesLeft {
80106
b.buffer.Write(data[:b.bodyBytesLeft])
81107
io.WriteString(b.buffer, "...")
@@ -86,3 +112,7 @@ func (b *badResponseLoggingWriter) captureResponseBody(data []byte) {
86112
b.bodyBytesLeft -= len(data)
87113
}
88114
}
115+
116+
func (b *flushingBadResponseLoggingWriter) Flush() {
117+
b.f.Flush()
118+
}

middleware/response_test.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,50 @@ func TestBadResponseLoggingWriter(t *testing.T) {
3131
default:
3232
http.Error(wrapped, tc.data, tc.statusCode)
3333
}
34-
if wrapped.statusCode != tc.statusCode {
35-
t.Errorf("Wrong status code: have %d want %d", wrapped.statusCode, tc.statusCode)
34+
if wrapped.getStatusCode() != tc.statusCode {
35+
t.Errorf("Wrong status code: have %d want %d", wrapped.getStatusCode(), tc.statusCode)
3636
}
3737
data := string(buf.Bytes())
3838
if data != tc.expected {
3939
t.Errorf("Wrong data: have %q want %q", data, tc.expected)
4040
}
4141
}
4242
}
43+
44+
// nonFlushingResponseWriter implements http.ResponseWriter but does not implement http.Flusher
45+
type nonFlushingResponseWriter struct{}
46+
47+
func (rw *nonFlushingResponseWriter) Header() http.Header {
48+
return nil
49+
}
50+
51+
func (rw *nonFlushingResponseWriter) Write(_ []byte) (int, error) {
52+
return -1, nil
53+
}
54+
55+
func (rw *nonFlushingResponseWriter) WriteHeader(_ int) {
56+
}
57+
58+
func TestBadResponseLoggingWriter_WithAndWithoutFlusher(t *testing.T) {
59+
var buf bytes.Buffer
60+
61+
nf := newBadResponseLoggingWriter(&nonFlushingResponseWriter{}, &buf)
62+
63+
_, ok := nf.(http.Flusher)
64+
if ok {
65+
t.Errorf("Should not be able to cast nf as an http.Flusher")
66+
}
67+
68+
rec := httptest.NewRecorder()
69+
f := newBadResponseLoggingWriter(rec, &buf)
70+
71+
ff, ok := f.(http.Flusher)
72+
if !ok {
73+
t.Errorf("Should be able to cast f as an http.Flusher")
74+
}
75+
76+
ff.Flush()
77+
if !rec.Flushed {
78+
t.Errorf("Flush should have worked but did not")
79+
}
80+
}

0 commit comments

Comments
 (0)