Skip to content

Commit

Permalink
feat: introduce custom response header limit
Browse files Browse the repository at this point in the history
Add a new property to configure the response header limit of the
transport used to send requests to route services and backends.

Resolves: cloudfoundry/routing-release#309
  • Loading branch information
maxmoehl authored and ameowlia committed Nov 18, 2024
1 parent 35e4728 commit 8528055
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 27 deletions.
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ type Config struct {
MaxIdleConns int `yaml:"max_idle_conns,omitempty"`
MaxIdleConnsPerHost int `yaml:"max_idle_conns_per_host,omitempty"`
MaxRequestHeaderBytes int `yaml:"max_request_header_bytes"`
MaxResponseHeaderBytes int `yaml:"max_response_header_bytes"`
KeepAlive100ContinueRequests bool `yaml:"keep_alive_100_continue_requests"`

HTTPRewrite HTTPRewrite `yaml:"http_rewrite,omitempty"`
Expand Down
24 changes: 24 additions & 0 deletions integration/header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -197,4 +198,27 @@ var _ = Describe("Headers", func() {
Expect(resp.Header.Get(HeaderKeySignature)).To(BeEmpty())
})
})

Context("Header Limits", func() {
Context("when a response header size limit is configured", func() {
BeforeEach(func() {
testApp = NewUnstartedTestApp(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Too-Large", strings.Repeat("0123456789", 10))
w.WriteHeader(200)
}))
testState.cfg.MaxResponseHeaderBytes = 80
testState.StartGorouterOrFail()
testApp.Start()
testState.register(testApp.Server, testAppRoute)
})

It("fails with 502 when the app exceeds the limit", func() {
req := testState.newRequest(fmt.Sprintf("http://%s", testAppRoute))
resp, err := testState.client.Do(req)
Expect(err).NotTo(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusBadGateway))
Expect(resp.Header).To(HaveKeyWithValue("X-Cf-Routererror", []string{"endpoint_failure (net/http: HTTP/1.x transport connection broken: net/http: server response headers exceeded 80 bytes; aborted)"}))
})
})
})
})
36 changes: 19 additions & 17 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,25 +84,27 @@ func NewProxy(

roundTripperFactory := &round_tripper.FactoryImpl{
BackendTemplate: &http.Transport{
DialContext: dialer.DialContext,
DisableKeepAlives: cfg.DisableKeepAlives,
MaxIdleConns: cfg.MaxIdleConns,
IdleConnTimeout: 90 * time.Second, // setting the value to golang default transport
MaxIdleConnsPerHost: cfg.MaxIdleConnsPerHost,
DisableCompression: true,
TLSClientConfig: backendTLSConfig,
TLSHandshakeTimeout: cfg.TLSHandshakeTimeout,
ExpectContinueTimeout: 1 * time.Second,
DialContext: dialer.DialContext,
DisableKeepAlives: cfg.DisableKeepAlives,
MaxIdleConns: cfg.MaxIdleConns,
IdleConnTimeout: 90 * time.Second, // setting the value to golang default transport
MaxIdleConnsPerHost: cfg.MaxIdleConnsPerHost,
DisableCompression: true,
TLSClientConfig: backendTLSConfig,
TLSHandshakeTimeout: cfg.TLSHandshakeTimeout,
ExpectContinueTimeout: 1 * time.Second,
MaxResponseHeaderBytes: int64(cfg.MaxResponseHeaderBytes),
},
RouteServiceTemplate: &http.Transport{
DialContext: dialer.DialContext,
DisableKeepAlives: cfg.DisableKeepAlives,
MaxIdleConns: cfg.MaxIdleConns,
IdleConnTimeout: 90 * time.Second, // setting the value to golang default transport
MaxIdleConnsPerHost: cfg.MaxIdleConnsPerHost,
DisableCompression: true,
TLSClientConfig: routeServiceTLSConfig,
ExpectContinueTimeout: 1 * time.Second,
DialContext: dialer.DialContext,
DisableKeepAlives: cfg.DisableKeepAlives,
MaxIdleConns: cfg.MaxIdleConns,
IdleConnTimeout: 90 * time.Second, // setting the value to golang default transport
MaxIdleConnsPerHost: cfg.MaxIdleConnsPerHost,
DisableCompression: true,
TLSClientConfig: routeServiceTLSConfig,
ExpectContinueTimeout: 1 * time.Second,
MaxResponseHeaderBytes: int64(cfg.MaxResponseHeaderBytes),
},
IsInstrumented: cfg.SendHttpStartStopClientEvent,
}
Expand Down
21 changes: 11 additions & 10 deletions proxy/round_tripper/dropsonde_round_tripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@ func (t *FactoryImpl) New(expectedServerName string, isRouteService bool, isHttp
customTLSConfig := utils.TLSConfigWithServerName(expectedServerName, template.TLSClientConfig, isRouteService)

newTransport := &http.Transport{
DialContext: template.DialContext,
DisableKeepAlives: template.DisableKeepAlives,
MaxIdleConns: template.MaxIdleConns,
IdleConnTimeout: template.IdleConnTimeout,
MaxIdleConnsPerHost: template.MaxIdleConnsPerHost,
DisableCompression: template.DisableCompression,
TLSClientConfig: customTLSConfig,
TLSHandshakeTimeout: template.TLSHandshakeTimeout,
ForceAttemptHTTP2: isHttp2,
ExpectContinueTimeout: template.ExpectContinueTimeout,
DialContext: template.DialContext,
DisableKeepAlives: template.DisableKeepAlives,
MaxIdleConns: template.MaxIdleConns,
IdleConnTimeout: template.IdleConnTimeout,
MaxIdleConnsPerHost: template.MaxIdleConnsPerHost,
DisableCompression: template.DisableCompression,
TLSClientConfig: customTLSConfig,
TLSHandshakeTimeout: template.TLSHandshakeTimeout,
ForceAttemptHTTP2: isHttp2,
ExpectContinueTimeout: template.ExpectContinueTimeout,
MaxResponseHeaderBytes: template.MaxResponseHeaderBytes,
}
if t.IsInstrumented {
return NewDropsondeRoundTripper(newTransport)
Expand Down

0 comments on commit 8528055

Please sign in to comment.