Skip to content

Commit

Permalink
proxy: set zero content length for default response
Browse files Browse the repository at this point in the history
Content-Length header prevents http.ResponseWriter from writing chunked response.

It turns out that reading empty chunked response is slow(er).
In particular this change speeds up TrafficSegment tests that perform
10k requests to routes that send no body:
```
r50: Path("/test") && TrafficSegment(0.0, 0.5) -> status(200) -> <shunt>;
r30: Path("/test") && TrafficSegment(0.5, 0.8) -> status(201) -> <shunt>;
r20: Path("/test") && TrafficSegment(0.8, 1.0) -> status(202) -> <shunt>;
```

Test runtime before the change:
```
$ go test ./predicates/traffic/ -count=1 -run=TestTrafficSegment
ok      github.com/zalando/skipper/predicates/traffic   9.357s
```
and after:
```
ok      github.com/zalando/skipper/predicates/traffic   4.260s
```

Related #1562

Signed-off-by: Alexander Yastrebov <[email protected]>
  • Loading branch information
AlexanderYastrebov committed Jul 14, 2023
1 parent 8704ae6 commit d774ac2
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 10 deletions.
15 changes: 7 additions & 8 deletions proxy/context.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package proxy

import (
"bytes"
stdlibcontext "context"
"errors"
"io"
Expand Down Expand Up @@ -62,15 +61,15 @@ type noopFlushedResponseWriter struct {
ignoredHeader http.Header
}

func defaultBody() io.ReadCloser {
return io.NopCloser(&bytes.Buffer{})
func noBody() io.ReadCloser {
return http.NoBody
}

func defaultResponse(r *http.Request) *http.Response {
return &http.Response{
StatusCode: http.StatusNotFound,
Header: make(http.Header),
Body: defaultBody(),
Body: noBody(),
Request: r,
}
}
Expand All @@ -89,7 +88,7 @@ func cloneRequestMetadata(r *http.Request) *http.Request {
ProtoMinor: r.ProtoMinor,
Header: cloneHeader(r.Header),
Trailer: cloneHeader(r.Trailer),
Body: defaultBody(),
Body: noBody(),
ContentLength: r.ContentLength,
TransferEncoding: r.TransferEncoding,
Close: r.Close,
Expand All @@ -109,7 +108,7 @@ func cloneResponseMetadata(r *http.Response) *http.Response {
ProtoMinor: r.ProtoMinor,
Header: cloneHeader(r.Header),
Trailer: cloneHeader(r.Trailer),
Body: defaultBody(),
Body: noBody(),
ContentLength: r.ContentLength,
TransferEncoding: r.TransferEncoding,
Close: r.Close,
Expand Down Expand Up @@ -180,7 +179,7 @@ func (c *context) ensureDefaultResponse() {
}

if c.response.Body == nil {
c.response.Body = defaultBody()
c.response.Body = noBody()
}
}

Expand Down Expand Up @@ -235,7 +234,7 @@ func (c *context) Serve(r *http.Response) {
}

if r.Body == nil {
r.Body = defaultBody()
r.Body = noBody()
}

c.servedWithResponse = true
Expand Down
26 changes: 26 additions & 0 deletions proxy/defaultresponse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package proxy_test

import (
"net/http"
"testing"

"github.com/zalando/skipper/eskip"
"github.com/zalando/skipper/proxy/proxytest"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestDefaultResponse(t *testing.T) {
p := proxytest.Config{
Routes: eskip.MustParse(`* -> <shunt>`),
}.Create()
defer p.Close()

rsp, body, err := p.Client().GetBody(p.URL)
require.NoError(t, err)

assert.Equal(t, http.StatusNotFound, rsp.StatusCode)
assert.Len(t, body, 0)
assert.Equal(t, "0", rsp.Header.Get("Content-Length"))
}
6 changes: 4 additions & 2 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -958,8 +958,7 @@ func (p *Proxy) rejectBackend(ctx *context, req *http.Request) (*http.Response,
if !p.limiters.Get(limit.Settings).Allow(req.Context(), s) {
return &http.Response{
StatusCode: limit.StatusCode,
Header: http.Header{"Content-Length": []string{"0"}},
Body: io.NopCloser(&bytes.Buffer{}),
Body: noBody(),
}, true
}
}
Expand Down Expand Up @@ -1211,6 +1210,9 @@ func (p *Proxy) serveResponse(ctx *context) {
start := time.Now()
p.tracing.logStreamEvent(ctx.proxySpan, StreamHeadersEvent, StartEvent)
copyHeader(ctx.responseWriter.Header(), ctx.response.Header)
if ctx.response.Body == noBody() {
ctx.responseWriter.Header()["Content-Length"] = []string{"0"}
}

if err := ctx.Request().Context().Err(); err != nil {
// deadline exceeded or canceled in stdlib, client closed request
Expand Down

0 comments on commit d774ac2

Please sign in to comment.