Fix custom headers replacement

This commit is contained in:
Michael 2017-11-23 17:40:03 +01:00 committed by Traefiker
parent 24368747ab
commit f6181ef3e2
4 changed files with 115 additions and 44 deletions

View file

@ -261,6 +261,11 @@ Here, `frontend1` will be matched before `frontend2` (`10 > 5`).
Custom headers can be configured through the frontends, to add headers to either requests or responses that match the frontend's rules.
This allows for setting headers such as `X-Script-Name` to be added to the request, or custom headers to be added to the response.
!!! warning
If the custom header name is the same as one header name of the request or response, it will be replaced.
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, and the `X-Custom-Response-Header` added to the response.
```toml
[frontends]
[frontends.frontend1]
@ -273,7 +278,20 @@ This allows for setting headers such as `X-Script-Name` to be added to the reque
rule = "PathPrefixStrip:/cheese"
```
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, and the `X-Custom-Response-Header` added to the response.
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` removed to the request and the `X-Custom-Response-Header` removed to the response.
```toml
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.headers.customresponseheaders]
X-Custom-Response-Header = ""
[frontends.frontend1.headers.customrequestheaders]
X-Script-Name = "test"
X-Custom-Request-Header = ""
[frontends.frontend1.routes.test_1]
rule = "PathPrefixStrip:/cheese"
```
#### Security headers

View file

@ -35,46 +35,35 @@ func NewHeaderFromStruct(headers types.Headers) *HeaderStruct {
}
}
// NewHeader constructs a new header instance with supplied options.
func NewHeader(options ...HeaderOptions) *HeaderStruct {
var o HeaderOptions
if len(options) == 0 {
o = HeaderOptions{}
} else {
o = options[0]
}
return &HeaderStruct{
opt: o,
}
}
// Handler implements the http.HandlerFunc for integration with the standard net/http lib.
func (s *HeaderStruct) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Let headers process the request.
s.Process(w, r)
h.ServeHTTP(w, r)
})
}
func (s *HeaderStruct) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
s.Process(w, r)
s.ModifyRequestHeaders(r)
// If there is a next, call it.
if next != nil {
next(w, r)
}
}
// Process runs the actual checks and returns an error if the middleware chain should stop.
func (s *HeaderStruct) Process(w http.ResponseWriter, r *http.Request) {
// ModifyRequestHeaders set or delete request headers
func (s *HeaderStruct) ModifyRequestHeaders(r *http.Request) {
// Loop through Custom request headers
for header, value := range s.opt.CustomRequestHeaders {
r.Header.Set(header, value)
}
// Loop through Custom response headers
for header, value := range s.opt.CustomResponseHeaders {
w.Header().Add(header, value)
if value == "" {
r.Header.Del(header)
} else {
r.Header.Set(header, value)
}
}
}
// ModifyResponseHeaders set or delete response headers
func (s *HeaderStruct) ModifyResponseHeaders(res *http.Response) error {
// Loop through Custom response headers
for header, value := range s.opt.CustomResponseHeaders {
if value == "" {
res.Header.Del(header)
} else {
res.Header.Set(header, value)
}
}
return nil
}

View file

@ -1,6 +1,6 @@
package middlewares
//Middleware tests based on https://github.com/unrolled/secure
// Middleware tests based on https://github.com/unrolled/secure
import (
"net/http"
@ -15,36 +15,66 @@ var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("bar"))
})
// newHeader constructs a new header instance with supplied options.
func newHeader(options ...HeaderOptions) *HeaderStruct {
var o HeaderOptions
if len(options) == 0 {
o = HeaderOptions{}
} else {
o = options[0]
}
return &HeaderStruct{
opt: o,
}
}
func TestNoConfig(t *testing.T) {
s := NewHeader()
header := newHeader()
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
s.Handler(myHandler).ServeHTTP(res, req)
header.ServeHTTP(res, req, myHandler)
assert.Equal(t, http.StatusOK, res.Code, "Status not OK")
assert.Equal(t, "bar", res.Body.String(), "Body not the expected")
}
func TestCustomResponseHeader(t *testing.T) {
s := NewHeader(HeaderOptions{
func TestModifyResponseHeaders(t *testing.T) {
header := newHeader(HeaderOptions{
CustomResponseHeaders: map[string]string{
"X-Custom-Response-Header": "test_response",
},
})
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
res.HeaderMap.Add("X-Custom-Response-Header", "test_response")
s.Handler(myHandler).ServeHTTP(res, req)
header.ModifyResponseHeaders(res.Result())
assert.Equal(t, http.StatusOK, res.Code, "Status not OK")
assert.Equal(t, "test_response", res.Header().Get("X-Custom-Response-Header"), "Did not get expected header")
res = httptest.NewRecorder()
res.HeaderMap.Add("X-Custom-Response-Header", "")
header.ModifyResponseHeaders(res.Result())
assert.Equal(t, http.StatusOK, res.Code, "Status not OK")
assert.Equal(t, "", res.Header().Get("X-Custom-Response-Header"), "Did not get expected header")
res = httptest.NewRecorder()
res.HeaderMap.Add("X-Custom-Response-Header", "test_override")
header.ModifyResponseHeaders(res.Result())
assert.Equal(t, http.StatusOK, res.Code, "Status not OK")
assert.Equal(t, "test_override", res.Header().Get("X-Custom-Response-Header"), "Did not get expected header")
}
func TestCustomRequestHeader(t *testing.T) {
s := NewHeader(HeaderOptions{
header := newHeader(HeaderOptions{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "test_request",
},
@ -53,8 +83,35 @@ func TestCustomRequestHeader(t *testing.T) {
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
s.Handler(myHandler).ServeHTTP(res, req)
header.ServeHTTP(res, req, nil)
assert.Equal(t, http.StatusOK, res.Code, "Status not OK")
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"), "Did not get expected header")
}
func TestCustomRequestHeaderEmptyValue(t *testing.T) {
header := newHeader(HeaderOptions{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "test_request",
},
})
res := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
header.ServeHTTP(res, req, nil)
assert.Equal(t, http.StatusOK, res.Code, "Status not OK")
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"), "Did not get expected header")
header = newHeader(HeaderOptions{
CustomRequestHeaders: map[string]string{
"X-Custom-Request-Header": "",
},
})
header.ServeHTTP(res, req, nil)
assert.Equal(t, http.StatusOK, res.Code, "Status not OK")
assert.Equal(t, "", req.Header.Get("X-Custom-Request-Header"), "This header is not expected")
}

View file

@ -960,12 +960,20 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
continue frontend
}
var headerMiddleware *middlewares.HeaderStruct
var responseModifier func(res *http.Response) error
if frontend.Headers.HasCustomHeadersDefined() {
headerMiddleware = middlewares.NewHeaderFromStruct(frontend.Headers)
responseModifier = headerMiddleware.ModifyResponseHeaders
}
fwd, err := forward.New(
forward.Stream(true),
forward.PassHostHeader(frontend.PassHostHeader),
forward.RoundTripper(roundTripper),
forward.ErrorHandler(errorHandler),
forward.Rewriter(rewriter),
forward.ResponseModifier(responseModifier),
)
if err != nil {
@ -1140,8 +1148,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
}
}
if frontend.Headers.HasCustomHeadersDefined() {
headerMiddleware := middlewares.NewHeaderFromStruct(frontend.Headers)
if headerMiddleware != nil {
log.Debugf("Adding header middleware for frontend %s", frontendName)
n.Use(headerMiddleware)
}