Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion capture_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWri
return func(src io.Reader) (int64, error) {
n, err := next(src)

headerWritten = true
// A zero-byte ReadFrom does not commit a status. io.Copy
// from an empty or failed reader returns before WriteHeader,
// and the handler may still send 204 or 502.
if n > 0 {
headerWritten = true
}
m.Written += n
return n, err
}
Expand Down
31 changes: 31 additions & 0 deletions capture_metrics_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package httpsnoop

import (
"errors"
"io"
"log"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"testing/iotest"
"time"
)

Expand All @@ -24,6 +26,7 @@ func TestCaptureMetrics(t *testing.T) {
WantDuration time.Duration
WantWritten int64
WantCode int
WantClient int
WantErr string
}{
{
Expand Down Expand Up @@ -61,6 +64,27 @@ func TestCaptureMetrics(t *testing.T) {
WantWritten: 17,
WantCode: http.StatusOK,
},
{
Name: "readfrom error then 502",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := io.Copy(w, iotest.ErrReader(errors.New("upstream failed")))
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
}
}),
WantCode: http.StatusBadGateway,
WantClient: http.StatusBadGateway,
},
{
Name: "readfrom empty then 204",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// A reader without WriteTo forces io.Copy through ReadFrom.
_, _ = io.Copy(w, eofReader{})
w.WriteHeader(http.StatusNoContent)
}),
WantCode: http.StatusNoContent,
WantClient: http.StatusNoContent,
},
{
Name: "string writer",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -112,6 +136,9 @@ func TestCaptureMetrics(t *testing.T) {
}
if err == nil {
defer res.Body.Close()
if test.WantClient != 0 && res.StatusCode != test.WantClient {
t.Errorf("test %d: client got=%d want=%d", i, res.StatusCode, test.WantClient)
}
}
m := <-ch
if m.Code != test.WantCode {
Expand All @@ -125,6 +152,10 @@ func TestCaptureMetrics(t *testing.T) {
}
}

type eofReader struct{}

func (eofReader) Read([]byte) (int, error) { return 0, io.EOF }

func errContains(err error, s string) bool {
var errS string
if err == nil {
Expand Down