diff --git a/context.go b/context.go index fc1d143..b3d16c0 100644 --- a/context.go +++ b/context.go @@ -45,9 +45,6 @@ type Context interface { Writer() ResponseWriter // SetWriter sets the ResponseWriter. SetWriter(w ResponseWriter) - // TeeWriter append an additional writer (sink) to which the response body will be written. - // This API is EXPERIMENTAL and is likely to change in future release. - TeeWriter(w io.Writer) // Path returns the registered path for the handler. Path() string // Params returns a Params slice containing the matched @@ -94,7 +91,6 @@ type context struct { req *http.Request params *Params skipNds *skippedNodes - mw *[]io.Writer // tree at allocation (read-only, no reset) tree *Tree @@ -120,7 +116,6 @@ func (c *context) Reset(fox *Router, w http.ResponseWriter, r *http.Request) { c.path = "" c.cachedQuery = nil *c.params = (*c.params)[:0] - *c.mw = (*c.mw)[:0] } func (c *context) resetNil() { @@ -130,7 +125,6 @@ func (c *context) resetNil() { c.path = "" c.cachedQuery = nil *c.params = (*c.params)[:0] - *c.mw = (*c.mw)[:0] } // Request returns the *http.Request. @@ -153,55 +147,6 @@ func (c *context) SetWriter(w ResponseWriter) { c.w = w } -// TeeWriter append an additional writer (sink) to which the response body will be written. -// Internally, TeeWriter make reasonable effort to reflect which interface the underlying ResponseWriter implement. -func (c *context) TeeWriter(w io.Writer) { - if w != nil { - if len(*c.mw) == 0 { - *c.mw = append(*c.mw, c.w) - } - *c.mw = append(*c.mw, w) - switch c.w.(type) { - case h1Writer: - c.w = h1MultiWriter{c.mw} - return - case h2Writer: - c.w = h2MultiWriter{c.mw} - return - } - - if c.req.ProtoMajor == 2 { - switch c.w.(type) { - case interface { - http.Flusher - http.Pusher - }: - c.w = h2MultiWriter{c.mw} - case http.Flusher: - c.w = flushMultiWriter{c.mw} - case http.Pusher: - c.w = pushMultiWriter{c.mw} - default: - c.w = multiWriter{c.mw} - } - return - } - - switch c.w.(type) { - case interface { - http.Flusher - http.Hijacker - io.ReaderFrom - }: - c.w = h1MultiWriter{c.mw} - case http.Flusher: - c.w = flushMultiWriter{c.mw} - default: - c.w = multiWriter{c.mw} - } - } -} - // Ctx returns the context associated with the current request. func (c *context) Ctx() netcontext.Context { return c.req.Context() @@ -298,8 +243,7 @@ func (c *context) Fox() *Router { } // Clone returns a copy of the Context that is safe to use after the HandlerFunc returns. -// Any attempt to write on the ResponseWriter will panic with the error ErrDiscardedResponseWriter. Note that -// TeeWriter are discarded. +// Any attempt to write on the ResponseWriter will panic with the error ErrDiscardedResponseWriter. func (c *context) Clone() Context { cp := context{ rec: c.rec, @@ -314,8 +258,6 @@ func (c *context) Clone() Context { copy(params, *c.params) cp.params = ¶ms cp.cachedQuery = nil - mw := make([]io.Writer, 0, 2) - cp.mw = &mw return &cp } @@ -339,7 +281,6 @@ func (c *context) CloneWith(w ResponseWriter, r *http.Request) ContextCloser { // now constraint into len(c.params) & cap(c.params) *cp.params = (*cp.params)[:len(*c.params):cap(*c.params)] copy(*cp.params, *c.params) - *cp.mw = (*cp.mw)[:0] return cp } diff --git a/context_test.go b/context_test.go index b619f53..ca63967 100644 --- a/context_test.go +++ b/context_test.go @@ -7,12 +7,8 @@ package fox import ( "bytes" netcontext "context" - "crypto/rand" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/net/http2" - "io" - "log" "net/http" "net/http/httptest" "net/url" @@ -70,7 +66,6 @@ func TestContext_Clone(t *testing.T) { assert.Equal(t, http.StatusOK, cc.Writer().Status()) assert.Equal(t, len(buf), cc.Writer().Size()) assert.Equal(t, wantValues, c.QueryParams()) - assert.Empty(t, *c.mw) assert.Panics(t, func() { _, _ = cc.Writer().Write([]byte("invalid")) }) @@ -91,7 +86,6 @@ func TestContext_CloneWith(t *testing.T) { assert.Equal(t, c.Path(), cp.Path()) assert.Equal(t, c.Fox(), cp.Fox()) assert.Nil(t, cc.cachedQuery) - assert.Empty(t, cc.mw) } func TestContext_Ctx(t *testing.T) { @@ -224,364 +218,6 @@ func TestContext_Tree(t *testing.T) { f.ServeHTTP(w, req) } -func TestContext_TeeWriter(t *testing.T) { - t.Parallel() - type capabilities struct { - rf bool - fl bool - hij bool - psh bool - } - - cases := []struct { - name string - w ResponseWriter - protoMajor int - want capabilities - }{ - { - name: "implement all h1 writer", - w: h1Writer{}, - protoMajor: 1, - want: capabilities{ - rf: true, - fl: true, - hij: true, - }, - }, - { - name: "implement all h2 writer", - w: h2Writer{}, - protoMajor: 2, - want: capabilities{ - fl: true, - psh: true, - }, - }, - { - name: "implement only flusher for h1", - w: flushWriter{}, - protoMajor: 1, - want: capabilities{ - fl: true, - }, - }, - { - name: "implement only flusher for h2", - w: flushWriter{}, - protoMajor: 2, - want: capabilities{ - fl: true, - }, - }, - { - name: "implement only pusher", - w: pushWriter{}, - protoMajor: 2, - want: capabilities{ - psh: true, - }, - }, - { - name: "only rw for h1", - w: multiWriter{}, - protoMajor: 1, - }, - { - name: "only rw for h2", - w: multiWriter{}, - protoMajor: 2, - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - req := httptest.NewRequest(http.MethodGet, "/foo", nil) - req.ProtoMajor = tc.protoMajor - c := NewTestContextOnly(New(), mockResponseWriter{}, req) - c.SetWriter(tc.w) - c.TeeWriter(bytes.NewBuffer(nil)) - _, flOk := c.Writer().(http.Flusher) - _, rfOk := c.Writer().(io.ReaderFrom) - _, hijOk := c.Writer().(http.Hijacker) - _, pshOk := c.Writer().(http.Pusher) - assert.Equal(t, tc.want.fl, flOk) - assert.Equal(t, tc.want.rf, rfOk) - assert.Equal(t, tc.want.hij, hijOk) - assert.Equal(t, tc.want.psh, pshOk) - }) - } -} - -func TestContext_TeeWriter_h1(t *testing.T) { - t.Parallel() - const length = 1 * 1024 * 1024 - buf := make([]byte, length) - _, _ = rand.Read(buf) - - cases := []struct { - name string - handler func(dumper *bytes.Buffer) HandlerFunc - }{ - { - name: "h1 writer", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - n, err := c.Writer().Write(buf) - require.NoError(t, err) - assert.Equal(t, length, n) - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - { - name: "h1 string writer", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - n, err := io.WriteString(c.Writer(), string(buf)) - require.NoError(t, err) - assert.Equal(t, length, n) - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - { - name: "h1 reader from", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - rf, ok := c.Writer().(io.ReaderFrom) - require.True(t, ok) - - n, err := rf.ReadFrom(bytes.NewReader(buf)) - require.NoError(t, err) - assert.Equal(t, length, int(n)) - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - { - name: "h1 flusher", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - flusher, ok := c.Writer().(http.Flusher) - require.True(t, ok) - - _, err := c.Writer().Write(buf[:1024]) - require.NoError(t, err) - flusher.Flush() - _, err = c.Writer().Write(buf[1024:]) - require.NoError(t, err) - flusher.Flush() - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - f := New() - dumper := bytes.NewBuffer(nil) - require.NoError(t, f.Handle(http.MethodGet, "/foo", tc.handler(dumper))) - - srv := httptest.NewServer(f) - defer srv.Close() - - req, _ := http.NewRequest(http.MethodGet, srv.URL+"/foo", nil) - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - out, err := io.ReadAll(resp.Body) - require.NoError(t, err) - assert.Equal(t, buf, out) - require.NoError(t, resp.Body.Close()) - assert.Equal(t, buf, dumper.Bytes()) - }) - } -} - -func TestContext_TeeWriter_flusher(t *testing.T) { - t.Parallel() - const length = 1 * 1024 * 1024 - buf := make([]byte, length) - _, _ = rand.Read(buf) - - cases := []struct { - name string - handler func(dumper *bytes.Buffer) HandlerFunc - }{ - { - name: "writer", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - n, err := c.Writer().Write(buf) - require.NoError(t, err) - assert.Equal(t, length, n) - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - { - name: "string writer", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - n, err := io.WriteString(c.Writer(), string(buf)) - require.NoError(t, err) - assert.Equal(t, length, n) - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - { - name: "flusher", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - flusher, ok := c.Writer().(http.Flusher) - require.True(t, ok) - - _, err := c.Writer().Write(buf[:1024]) - require.NoError(t, err) - flusher.Flush() - _, err = c.Writer().Write(buf[1024:]) - require.NoError(t, err) - flusher.Flush() - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - f := New() - dumper := bytes.NewBuffer(nil) - require.NoError(t, f.Handle(http.MethodGet, "/foo", tc.handler(dumper))) - - srv := httptest.NewServer(f) - defer srv.Close() - - req, _ := http.NewRequest(http.MethodGet, srv.URL+"/foo", nil) - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - out, err := io.ReadAll(resp.Body) - require.NoError(t, err) - assert.Equal(t, buf, out) - require.NoError(t, resp.Body.Close()) - assert.Equal(t, buf, dumper.Bytes()) - }) - } -} - -func TestContext_TeeWriter_h2(t *testing.T) { - t.Parallel() - const length = 1 * 1024 * 1024 - buf := make([]byte, length) - _, _ = rand.Read(buf) - - cases := []struct { - name string - handler func(dumper *bytes.Buffer) HandlerFunc - }{ - { - name: "h2 writer", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - n, err := c.Writer().Write(buf) - require.NoError(t, err) - assert.Equal(t, length, n) - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - { - name: "h2 string writer", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - n, err := io.WriteString(c.Writer(), string(buf)) - require.NoError(t, err) - assert.Equal(t, length, n) - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - { - name: "h2 flusher", - handler: func(dumper *bytes.Buffer) HandlerFunc { - return func(c Context) { - c.TeeWriter(dumper) - flusher, ok := c.Writer().(http.Flusher) - require.True(t, ok) - - _, err := c.Writer().Write(buf[:1024]) - require.NoError(t, err) - flusher.Flush() - _, err = c.Writer().Write(buf[1024:]) - require.NoError(t, err) - flusher.Flush() - assert.Equal(t, length, c.Writer().Size()) - assert.True(t, c.Writer().Written()) - } - }, - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - f := New() - dumper := bytes.NewBuffer(nil) - require.NoError(t, f.Handle(http.MethodGet, "/foo", tc.handler(dumper))) - - srv := httptest.NewUnstartedServer(f) - - err := http2.ConfigureServer(srv.Config, new(http2.Server)) - require.NoError(t, err) - - srv.TLS = srv.Config.TLSConfig - srv.StartTLS() - defer srv.Close() - - tr := &http.Transport{TLSClientConfig: srv.Config.TLSConfig} - require.NoError(t, http2.ConfigureTransport(tr)) - tr.TLSClientConfig.InsecureSkipVerify = true - client := &http.Client{Transport: tr} - - req, _ := http.NewRequest(http.MethodGet, srv.URL+"/foo", nil) - resp, err := client.Do(req) - require.NoError(t, err) - out, err := io.ReadAll(resp.Body) - require.NoError(t, err) - assert.Equal(t, buf, out) - require.NoError(t, resp.Body.Close()) - assert.Equal(t, buf, dumper.Bytes()) - }) - } -} - func TestWrapF(t *testing.T) { t.Parallel() @@ -700,25 +336,3 @@ func TestWrapH(t *testing.T) { }) } } - -// This example demonstrates how to capture the HTTP response body by using the TeeWriter method. -// The TeeWriter method attaches the provided io.Writer (in this case a bytes.Buffer) to the existing ResponseWriter. -// Unlike a typical io.MultiWriter, this implementation is designed to ensure that the ResponseWriter remains compatible -// with http interfaces, like io.ReaderFrom or http.Flusher, which might not be the case with a standard MultiWriter. -// Every time data is written to the ResponseWriter, it will also be written to the provided io.Writer. -// It's also worth noting that the TeeWriter method can be called multiple times to add more writers, if needed. -func ExampleContext_TeeWriter() { - bodyLogger := MiddlewareFunc(func(next HandlerFunc) HandlerFunc { - return func(c Context) { - buf := bytes.NewBuffer(nil) - c.TeeWriter(buf) - next(c) - log.Printf("response body: %s", buf.String()) - } - }) - - f := New(WithMiddleware(bodyLogger)) - f.MustHandle(http.MethodGet, "/hello/{name}", func(c Context) { - _ = c.String(http.StatusOK, "Hello %s\n", c.Param("name")) - }) -} diff --git a/fox_test.go b/fox_test.go index 7ffc17b..41f7b14 100644 --- a/fox_test.go +++ b/fox_test.go @@ -5,9 +5,7 @@ package fox import ( - "bytes" "fmt" - "io" "log" "math/rand" "net/http" @@ -564,23 +562,6 @@ func BenchmarkCatchAllParallel(b *testing.B) { }) } -func BenchmarkMultiWriter(b *testing.B) { - buf := bytes.NewBuffer(nil) - f := New() - f.MustHandle(http.MethodGet, "/hello/{name}", func(c Context) { - buf.Reset() - c.TeeWriter(buf) - _, _ = io.WriteString(c.Writer(), c.Param("name")) - }) - w := new(mockResponseWriter) - r := httptest.NewRequest("GET", "/hello/fox", nil) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - f.ServeHTTP(w, r) - } -} - func BenchmarkCloneWith(b *testing.B) { f := New() f.MustHandle(http.MethodGet, "/hello/{name}", func(c Context) { diff --git a/go.mod b/go.mod index 37f532c..c98a1aa 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.19 require ( github.com/google/gofuzz v1.2.0 github.com/stretchr/testify v1.8.4 - golang.org/x/net v0.20.0 ) require ( @@ -13,7 +12,6 @@ require ( github.com/kr/pretty v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect - golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e01bf68..eb76b96 100644 --- a/go.sum +++ b/go.sum @@ -19,10 +19,6 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/helpers_test.go b/helpers_test.go index 546fdd9..21661ad 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -5,7 +5,6 @@ package fox import ( - "bytes" "io" "net/http" "net/http/httptest" @@ -20,12 +19,6 @@ func TestNewTestContext(t *testing.T) { w := httptest.NewRecorder() _, c := NewTestContext(w, req) - buf := bytes.NewBuffer(nil) - - _, ok := c.Writer().(http.Flusher) - require.True(t, ok) - - c.TeeWriter(buf) flusher, ok := c.Writer().(http.Flusher) require.True(t, ok) diff --git a/response_writer.go b/response_writer.go index 7355c1b..fd81463 100644 --- a/response_writer.go +++ b/response_writer.go @@ -29,24 +29,11 @@ var ( _ io.ReaderFrom = (*h1Writer)(nil) ) -var ( - _ ResponseWriter = (*h1MultiWriter)(nil) - _ http.Flusher = (*h1MultiWriter)(nil) - _ http.Hijacker = (*h1MultiWriter)(nil) - _ io.ReaderFrom = (*h1MultiWriter)(nil) -) - var ( _ http.Pusher = (*h2Writer)(nil) _ http.Flusher = (*h2Writer)(nil) ) -var ( - _ ResponseWriter = (*h2MultiWriter)(nil) - _ http.Flusher = (*h2MultiWriter)(nil) - _ http.Pusher = (*h2MultiWriter)(nil) -) - var ( _ ResponseWriter = (*flushWriter)(nil) _ http.Flusher = (*flushWriter)(nil) @@ -57,18 +44,6 @@ var ( _ http.Pusher = (*pushWriter)(nil) ) -var ( - _ ResponseWriter = (*flushMultiWriter)(nil) - _ http.Flusher = (*flushMultiWriter)(nil) -) - -var ( - _ ResponseWriter = (*pushMultiWriter)(nil) - _ http.Pusher = (*pushMultiWriter)(nil) -) - -var _ ResponseWriter = (*multiWriter)(nil) - const kib = 1024 var copyBufPool = sync.Pool{ @@ -228,218 +203,6 @@ func (w pushWriter) Push(target string, opts *http.PushOptions) error { return w.recorder.ResponseWriter.(http.Pusher).Push(target, opts) } -type h1MultiWriter struct { - writers *[]io.Writer -} - -func (w h1MultiWriter) Header() http.Header { - return (*w.writers)[0].(ResponseWriter).Header() -} - -func (w h1MultiWriter) WriteHeader(statusCode int) { - (*w.writers)[0].(ResponseWriter).WriteHeader(statusCode) -} - -func (w h1MultiWriter) Status() int { - return (*w.writers)[0].(ResponseWriter).Status() -} - -func (w h1MultiWriter) Written() bool { - return (*w.writers)[0].(ResponseWriter).Written() -} - -func (w h1MultiWriter) Size() int { - return (*w.writers)[0].(ResponseWriter).Size() -} - -func (w h1MultiWriter) Unwrap() http.ResponseWriter { - return (*w.writers)[0].(rwUnwrapper).Unwrap() -} - -func (w h1MultiWriter) Write(p []byte) (n int, err error) { - return multiWrite(w.writers, p) -} - -func (w h1MultiWriter) WriteString(s string) (n int, err error) { - return multiWriteString(w.writers, s) -} - -func (w h1MultiWriter) Flush() { - multiFlush(w.writers) -} - -func (w h1MultiWriter) ReadFrom(src io.Reader) (n int64, err error) { - bufp := copyBufPool.Get().(*[]byte) - buf := *bufp - n, err = io.CopyBuffer(w, src, buf) - copyBufPool.Put(bufp) - return -} - -func (w h1MultiWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return (*w.writers)[0].(http.Hijacker).Hijack() -} - -type h2MultiWriter struct { - writers *[]io.Writer -} - -func (w h2MultiWriter) Header() http.Header { - return (*w.writers)[0].(ResponseWriter).Header() -} - -func (w h2MultiWriter) WriteHeader(statusCode int) { - (*w.writers)[0].(ResponseWriter).WriteHeader(statusCode) -} - -func (w h2MultiWriter) Status() int { - return (*w.writers)[0].(ResponseWriter).Status() -} - -func (w h2MultiWriter) Written() bool { - return (*w.writers)[0].(ResponseWriter).Written() -} - -func (w h2MultiWriter) Size() int { - return (*w.writers)[0].(ResponseWriter).Size() -} - -func (w h2MultiWriter) Unwrap() http.ResponseWriter { - return (*w.writers)[0].(rwUnwrapper).Unwrap() -} - -func (w h2MultiWriter) Write(p []byte) (n int, err error) { - return multiWrite(w.writers, p) -} - -func (w h2MultiWriter) WriteString(s string) (n int, err error) { - return multiWriteString(w.writers, s) -} - -func (w h2MultiWriter) Flush() { - multiFlush(w.writers) -} - -func (w h2MultiWriter) Push(target string, opts *http.PushOptions) error { - return (*w.writers)[0].(http.Pusher).Push(target, opts) -} - -type pushMultiWriter struct { - writers *[]io.Writer -} - -func (w pushMultiWriter) Header() http.Header { - return (*w.writers)[0].(ResponseWriter).Header() -} - -func (w pushMultiWriter) WriteHeader(statusCode int) { - (*w.writers)[0].(ResponseWriter).WriteHeader(statusCode) -} - -func (w pushMultiWriter) Status() int { - return (*w.writers)[0].(ResponseWriter).Status() -} - -func (w pushMultiWriter) Written() bool { - return (*w.writers)[0].(ResponseWriter).Written() -} - -func (w pushMultiWriter) Size() int { - return (*w.writers)[0].(ResponseWriter).Size() -} - -func (w pushMultiWriter) Unwrap() http.ResponseWriter { - return (*w.writers)[0].(rwUnwrapper).Unwrap() -} - -func (w pushMultiWriter) Write(p []byte) (n int, err error) { - return multiWrite(w.writers, p) -} - -func (w pushMultiWriter) WriteString(s string) (n int, err error) { - return multiWriteString(w.writers, s) -} - -func (w pushMultiWriter) Push(target string, opts *http.PushOptions) error { - return (*w.writers)[0].(http.Pusher).Push(target, opts) -} - -type flushMultiWriter struct { - writers *[]io.Writer -} - -func (w flushMultiWriter) Header() http.Header { - return (*w.writers)[0].(ResponseWriter).Header() -} - -func (w flushMultiWriter) WriteHeader(statusCode int) { - (*w.writers)[0].(ResponseWriter).WriteHeader(statusCode) -} - -func (w flushMultiWriter) Status() int { - return (*w.writers)[0].(ResponseWriter).Status() -} - -func (w flushMultiWriter) Written() bool { - return (*w.writers)[0].(ResponseWriter).Written() -} - -func (w flushMultiWriter) Size() int { - return (*w.writers)[0].(ResponseWriter).Size() -} - -func (w flushMultiWriter) Unwrap() http.ResponseWriter { - return (*w.writers)[0].(rwUnwrapper).Unwrap() -} - -func (w flushMultiWriter) Write(p []byte) (n int, err error) { - return multiWrite(w.writers, p) -} - -func (w flushMultiWriter) WriteString(s string) (n int, err error) { - return multiWriteString(w.writers, s) -} - -func (w flushMultiWriter) Flush() { - multiFlush(w.writers) -} - -type multiWriter struct { - writers *[]io.Writer -} - -func (w multiWriter) Header() http.Header { - return (*w.writers)[0].(ResponseWriter).Header() -} - -func (w multiWriter) WriteHeader(statusCode int) { - (*w.writers)[0].(ResponseWriter).WriteHeader(statusCode) -} - -func (w multiWriter) Status() int { - return (*w.writers)[0].(ResponseWriter).Status() -} - -func (w multiWriter) Written() bool { - return (*w.writers)[0].(ResponseWriter).Written() -} - -func (w multiWriter) Size() int { - return (*w.writers)[0].(ResponseWriter).Size() -} - -func (w multiWriter) Unwrap() http.ResponseWriter { - return (*w.writers)[0].(rwUnwrapper).Unwrap() -} - -func (w multiWriter) Write(p []byte) (n int, err error) { - return multiWrite(w.writers, p) -} - -func (w multiWriter) WriteString(s string) (n int, err error) { - return multiWriteString(w.writers, s) -} - // noUnwrap hide the Unwrap method of the ResponseWriter. type noUnwrap struct { ResponseWriter @@ -461,50 +224,6 @@ func (n noopWriter) WriteHeader(int) { panic(fmt.Errorf("%w: attempt to write on a clone", ErrDiscardedResponseWriter)) } -func multiWrite(writers *[]io.Writer, p []byte) (n int, err error) { - for _, writer := range *writers { - n, err = writer.Write(p) - if err != nil { - return - } - if n != len(p) { - err = io.ErrShortWrite - return - } - } - return len(p), nil -} - -func multiWriteString(writers *[]io.Writer, s string) (n int, err error) { - var p []byte // lazily initialized if/when needed - for _, writer := range *writers { - if sw, ok := writer.(io.StringWriter); ok { - n, err = sw.WriteString(s) - } else { - if p == nil { - p = []byte(s) - } - n, err = writer.Write(p) - } - if err != nil { - return - } - if n != len(s) { - err = io.ErrShortWrite - return - } - } - return len(s), nil -} - -func multiFlush(writers *[]io.Writer) { - for _, writer := range *writers { - if f, ok := writer.(http.Flusher); ok { - f.Flush() - } - } -} - func relevantCaller() runtime.Frame { pc := make([]uintptr, 16) n := runtime.Callers(1, pc) diff --git a/tree.go b/tree.go index cb4a1a5..758a41e 100644 --- a/tree.go +++ b/tree.go @@ -6,7 +6,6 @@ package fox import ( "fmt" - "io" "sort" "strings" "sync" @@ -732,11 +731,9 @@ STOP: func (t *Tree) allocateContext() *context { params := make(Params, 0, t.maxParams.Load()) skipNds := make(skippedNodes, 0, t.maxDepth.Load()) - mw := make([]io.Writer, 0, 2) return &context{ params: ¶ms, skipNds: &skipNds, - mw: &mw, // This is a read only value, no reset, it's always the // owner of the pool. tree: t,