Repository: avenga/couper
Pull request #77 (2020-12-07 12:34:43)

Add set_re*_headers


Patch:
diff --git a/config/backend.go b/config/backend.go
index 6c61306e..38786a9a 100644
--- a/config/backend.go
+++ b/config/backend.go
@@ -27,11 +27,13 @@ func (b Backend) Schema(inline bool) *hcl.BodySchema {
 	}
 
 	type Inline struct {
-		Origin          string            `hcl:"origin,optional"`
-		Hostname        string            `hcl:"hostname,optional"`
-		Path            string            `hcl:"path,optional"`
-		RequestHeaders  map[string]string `hcl:"request_headers,optional"`
-		ResponseHeaders map[string]string `hcl:"response_headers,optional"`
+		Origin             string            `hcl:"origin,optional"`
+		Hostname           string            `hcl:"hostname,optional"`
+		Path               string            `hcl:"path,optional"`
+		RequestHeaders     map[string]string `hcl:"request_headers,optional"`
+		ResponseHeaders    map[string]string `hcl:"response_headers,optional"`
+		SetRequestHeaders  map[string]string `hcl:"set_request_headers,optional"`
+		SetResponseHeaders map[string]string `hcl:"set_response_headers,optional"`
 	}
 
 	schema, _ = gohcl.ImpliedBodySchema(&Inline{})
diff --git a/config/runtime/server.go b/config/runtime/server.go
index c02206d9..eb0efaf0 100644
--- a/config/runtime/server.go
+++ b/config/runtime/server.go
@@ -210,6 +210,18 @@ func newProxy(ctx *hcl.EvalContext, beConf *config.Backend, corsOpts *config.COR
 		log.Fatal(err)
 	}
 
+	for _, name := range []string{"request_headers", "response_headers"} {
+		for _, body := range remainCtx {
+			attr, err := body.JustAttributes()
+			if err != nil {
+				log.Fatal(err)
+			}
+			if _, ok := attr[name]; ok {
+				log.Warningf("'%s' is deprecated, use 'set_%s' instead", name, name)
+			}
+		}
+	}
+
 	proxyOptions, err := handler.NewProxyOptions(beConf, corsOptions, remainCtx)
 	if err != nil {
 		log.Fatal(err)
diff --git a/docs/README.md b/docs/README.md
index 33d4c027..622169a9 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -159,7 +159,7 @@ server "variables-srv" {
   api {
     endpoint "/" {
       backend "my_backend_definition" {
-        request_headers = {
+        set_request_headers = {
           x-env-user = env.USER
           user-agent = "myproxyClient/${req.headers.app-version}"
           x-uuid = req.id
@@ -297,8 +297,8 @@ A `backend` defines the connection to a local/remote backend service. Backends c
 |`hostname`| value of the HTTP host header field for the `origin` request. Since `hostname` replaces the request host the value will also be used for a server identity check during a TLS handshake with the origin. |
 |`path`|changeable part of upstream URL|
 |`timeout`| <ul><li>the total deadline duration a backend request has for write and read/pipe</li><li>valid time units are: "ns", "us" (or "µs"), "ms", "s", "m", "h"</li></ul> |
-| `request_headers` | header map to define additional or override header for the `origin` request |
-| `response_headers` | same as `request_headers` for the client response |
+| `set_request_headers` | header map to define additional or override header for the `origin` request |
+| `set_response_headers` | same as `set_request_headers` for the client response |
 | `request_body_limit` | Limit to configure the maximum buffer size while accessing `req.post` or `req.json_body` content. Valid units are: `KiB, MiB, GiB`. Default: `64MiB`. |
 
 ### The `access_control` attribute <a name="access_control_attribute"></a> 
diff --git a/handler/context_options.go b/handler/context_options.go
index 5f890891..daa8f424 100644
--- a/handler/context_options.go
+++ b/handler/context_options.go
@@ -8,8 +8,10 @@ import (
 )
 
 const (
-	attrReqHeaders = "request_headers"
-	attrResHeaders = "response_headers"
+	attrReqHeaders    = "request_headers"
+	attrResHeaders    = "response_headers"
+	attrSetReqHeaders = "set_request_headers"
+	attrSetResHeaders = "set_response_headers"
 )
 
 type OptionsMap map[string][]string
diff --git a/handler/proxy.go b/handler/proxy.go
index 9bf64334..8ba2667f 100644
--- a/handler/proxy.go
+++ b/handler/proxy.go
@@ -363,13 +363,13 @@ func (p *Proxy) Director(req *http.Request) error {
 
 func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) {
 	var (
-		attrCtx   = attrReqHeaders
+		attrCtx   = []string{attrReqHeaders, attrSetReqHeaders}
 		bereq     *http.Request
 		headerCtx http.Header
 	)
 
 	if beresp != nil {
-		attrCtx = attrResHeaders
+		attrCtx = []string{attrResHeaders, attrSetResHeaders}
 		bereq = beresp.Request
 		headerCtx = beresp.Header
 	} else if req != nil {
@@ -379,18 +379,20 @@ func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) {
 	evalCtx := eval.NewHTTPContext(p.evalContext, p.bufferOption, req, bereq, beresp)
 
 	// Remove blacklisted headers after evaluation to be accessible within our context configuration.
-	if attrCtx == attrReqHeaders {
+	if attrCtx[0] == attrReqHeaders {
 		for _, key := range headerBlacklist {
 			headerCtx.Del(key)
 		}
 	}
 
-	for _, ctxBody := range p.options.Context {
-		options, err := NewCtxOptions(attrCtx, evalCtx, ctxBody)
-		if err != nil {
-			p.log.WithField("parse config", p.String()).Error(err)
+	for _, ctx := range attrCtx {
+		for _, ctxBody := range p.options.Context {
+			options, err := NewCtxOptions(ctx, evalCtx, ctxBody)
+			if err != nil {
+				p.log.WithField("parse config", p.String()).Error(err)
+			}
+			setHeaderFields(headerCtx, options)
 		}
-		setHeaderFields(headerCtx, options)
 	}
 
 	if beresp != nil && isCorsRequest(req) {


Test patch:
diff --git a/handler/proxy_test.go b/handler/proxy_test.go
index bea571b2..b5f08cda 100644
--- a/handler/proxy_test.go
+++ b/handler/proxy_test.go
@@ -512,11 +512,11 @@ func TestProxy_ServeHTTP_Eval(t *testing.T) {
 
 	tests := []testCase{
 		{"GET use req.Header", fields{baseCtx, opts}, `
-		response_headers = {
+		set_response_headers = {
 			X-Method = req.method
 		}`, http.MethodGet, nil, header{"X-Method": http.MethodGet}, false},
 		{"POST use req.post", fields{baseCtx, opts}, `
-		response_headers = {
+		set_response_headers = {
 			X-Method = req.method
 			X-Post = req.post.foo
 		}`, http.MethodPost, strings.NewReader(`foo=bar`), header{
@@ -587,7 +587,7 @@ func TestProxy_SetGetBody_LimitBody_Roundtrip(t *testing.T) {
 	} {
 		t.Run(testcase.name, func(subT *testing.T) {
 			proxy, _, _, closeFn := helper.NewProxy(&handler.ProxyOptions{
-				Context:          helper.NewProxyContext("request_headers = { x = req.post }"), // ensure buffering is enabled
+				Context:          helper.NewProxyContext("set_request_headers = { x = req.post }"), // ensure buffering is enabled
 				RequestBodyLimit: testcase.limit,
 			})
 			closeFn() // unused
@@ -660,7 +660,7 @@ func TestProxy_setRoundtripContext_Variables_json_body(t *testing.T) {
 	}{
 		{"method /w body", `
 		origin = "http://1.2.3.4/"
-		request_headers = {
+		set_request_headers = {
 			x-test = req.json_body.foo
 		}`, []string{
 			http.MethodGet,
@@ -674,12 +674,12 @@ func TestProxy_setRoundtripContext_Variables_json_body(t *testing.T) {
 		}, test.Header{"Content-Type": "application/json"}, `{"foo": "bar"}`, want{req: test.Header{"x-test": "bar"}}},
 		{"method /w body", `
 		origin = "http://1.2.3.4/"
-		request_headers = {
+		set_request_headers = {
 			x-test = req.json_body.foo
 		}`, []string{http.MethodTrace}, test.Header{"Content-Type": "application/json"}, `{"foo": "bar"}`, want{req: test.Header{"x-test": ""}}},
 		{"method /wo body", `
 		origin = "http://1.2.3.4/"
-		request_headers = {
+		set_request_headers = {
 			x-test = req.json_body.foo
 		}`, []string{
 			http.MethodGet,
diff --git a/server/testdata/integration/endpoint_eval/01_couper.hcl b/server/testdata/integration/endpoint_eval/01_couper.hcl
index fc74dfca..f80b6841 100644
--- a/server/testdata/integration/endpoint_eval/01_couper.hcl
+++ b/server/testdata/integration/endpoint_eval/01_couper.hcl
@@ -14,7 +14,7 @@ server "api" {
         path = "/anything"
         origin = "http://${req.path_params.origin}"
         hostname = req.path_params.hostname
-        response_headers = {
+        set_response_headers = {
           x-origin = req.path_params.origin
         }
       }