Skip to content

Commit

Permalink
Use felixge/httpsnoop approach to preserve additional interfaces (#26)
Browse files Browse the repository at this point in the history
* Add overhead benchmark

* Use snoopwrap

* Use opencensus method for status code tracking

It's more efficient than using httpsnoop.

* Do not build status code tracker for go1.7

* Add remove support for http.Pusher in go1.7

* Adjust travis version range

Add up to go 1.11.

* Correct build tags
  • Loading branch information
mike-zorn authored and yurishkuro committed Dec 22, 2018
1 parent 0965915 commit 77df8e8
Show file tree
Hide file tree
Showing 5 changed files with 409 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go:
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- tip

script:
Expand Down
16 changes: 3 additions & 13 deletions nethttp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@ import (
"github.com/opentracing/opentracing-go/ext"
)

type statusCodeTracker struct {
http.ResponseWriter
status int
}

func (w *statusCodeTracker) WriteHeader(status int) {
w.status = status
w.ResponseWriter.WriteHeader(status)
}

type mwOptions struct {
opNameFunc func(r *http.Request) string
spanFilter func(r *http.Request) bool
Expand Down Expand Up @@ -122,12 +112,12 @@ func MiddlewareFunc(tr opentracing.Tracer, h http.HandlerFunc, options ...MWOpti
}
ext.Component.Set(sp, componentName)

w = &statusCodeTracker{w, 200}
sct := &statusCodeTracker{w, 200}
r = r.WithContext(opentracing.ContextWithSpan(r.Context(), sp))

h(w, r)
h(sct.wrappedResponseWriter(), r)

ext.HTTPStatusCode.Set(sp, uint16(w.(*statusCodeTracker).status))
ext.HTTPStatusCode.Set(sp, uint16(sct.status))
sp.Finish()
}
return http.HandlerFunc(fn)
Expand Down
22 changes: 22 additions & 0 deletions nethttp/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,25 @@ func TestSpanFilterOption(t *testing.T) {
})
}
}

func BenchmarkStatusCodeTrackingOverhead(b *testing.B) {
mux := http.NewServeMux()
mux.HandleFunc("/root", func(w http.ResponseWriter, r *http.Request) {})
tr := &mocktracer.MockTracer{}
mw := Middleware(tr, mux)
srv := httptest.NewServer(mw)
defer srv.Close()

b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := http.Get(srv.URL)
if err != nil {
b.Fatalf("server returned error: %v", err)
}
err = resp.Body.Close()
if err != nil {
b.Fatalf("failed to close response: %v", err)
}
}
})
}
135 changes: 135 additions & 0 deletions nethttp/status-code-tracker-old.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// +build go1.7,!go1.8

package nethttp

import (
"io"
"net/http"
)

type statusCodeTracker struct {
http.ResponseWriter
status int
}

func (w *statusCodeTracker) WriteHeader(status int) {
w.status = status
w.ResponseWriter.WriteHeader(status)
}

// wrappedResponseWriter returns a wrapped version of the original
// ResponseWriter and only implements the same combination of additional
// interfaces as the original. This implementation is based on
// https://github.com/felixge/httpsnoop.
func (w *statusCodeTracker) wrappedResponseWriter() http.ResponseWriter {
var (
hj, i0 = w.ResponseWriter.(http.Hijacker)
cn, i1 = w.ResponseWriter.(http.CloseNotifier)
fl, i3 = w.ResponseWriter.(http.Flusher)
rf, i4 = w.ResponseWriter.(io.ReaderFrom)
)
i2 := false

switch {
case !i0 && !i1 && !i2 && !i3 && !i4:
return struct {
http.ResponseWriter
}{w}
case !i0 && !i1 && !i2 && !i3 && i4:
return struct {
http.ResponseWriter
io.ReaderFrom
}{w, rf}
case !i0 && !i1 && !i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Flusher
}{w, fl}
case !i0 && !i1 && !i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Flusher
io.ReaderFrom
}{w, fl, rf}
case !i0 && i1 && !i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.CloseNotifier
}{w, cn}
case !i0 && i1 && !i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
}{w, cn, rf}
case !i0 && i1 && !i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
}{w, cn, fl}
case !i0 && i1 && !i2 && i3 && i4:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
io.ReaderFrom
}{w, cn, fl, rf}
case i0 && !i1 && !i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
}{w, hj}
case i0 && !i1 && !i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
io.ReaderFrom
}{w, hj, rf}
case i0 && !i1 && !i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.Flusher
}{w, hj, fl}
case i0 && !i1 && !i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.Flusher
io.ReaderFrom
}{w, hj, fl, rf}
case i0 && i1 && !i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
}{w, hj, cn}
case i0 && i1 && !i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
io.ReaderFrom
}{w, hj, cn, rf}
case i0 && i1 && !i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
http.Flusher
}{w, hj, cn, fl}
case i0 && i1 && !i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
http.Flusher
io.ReaderFrom
}{w, hj, cn, fl, rf}
default:
return struct {
http.ResponseWriter
}{w}
}
}
Loading

0 comments on commit 77df8e8

Please sign in to comment.