From 800e6ccfe32d0d742aaca53b7fe231f28cf7dd07 Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Fri, 15 Jan 2021 12:01:54 +0100 Subject: [PATCH 1/7] Simplify handling of CORS headers --- handler/proxy.go | 24 ++++++++++++------------ handler/proxy_cors_test.go | 12 ------------ 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/handler/proxy.go b/handler/proxy.go index a8273a47e..678e89301 100644 --- a/handler/proxy.go +++ b/handler/proxy.go @@ -181,7 +181,7 @@ func (p *Proxy) getTransport(scheme, origin, hostname string) *http.Transport { func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { startTime := time.Now() - if p.options.CORS != nil && isCorsPreflightRequest(req) { + if isCorsPreflightRequest(req) { p.setCorsRespHeaders(rw.Header(), req) rw.WriteHeader(http.StatusNoContent) return @@ -430,12 +430,16 @@ func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) { allAttributes, attrOk := p.options.Context.(body.Attributes) + if beresp != nil { + defer p.setCorsRespHeaders(headerCtx, req) + } + + if !attrOk { + return + } + // apply header values for _, ctxName := range attrCtx { // headers - if !attrOk { - break - } - for _, attrs := range allAttributes.JustAllAttributesWithName(ctxName) { attr, ok := attrs[ctxName] if !ok { @@ -451,7 +455,7 @@ func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) { } // apply query params in hierarchical and logical order: delete, set, add - if attrOk && req != nil && beresp == nil { // just one way -> origin + if req != nil && beresp == nil { // just one way -> origin var modify bool u := *req.URL @@ -505,10 +509,6 @@ func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) { req.URL.RawQuery = strings.ReplaceAll(values.Encode(), "+", "%20") } } - - if beresp != nil && isCorsRequest(req) { - p.setCorsRespHeaders(headerCtx, req) - } } // SetGetBody determines if we have to buffer a request body for further processing. @@ -549,7 +549,7 @@ func isCorsRequest(req *http.Request) bool { } func isCorsPreflightRequest(req *http.Request) bool { - return isCorsRequest(req) && req.Method == http.MethodOptions && (req.Header.Get("Access-Control-Request-Method") != "" || req.Header.Get("Access-Control-Request-Headers") != "") + return req.Method == http.MethodOptions && (req.Header.Get("Access-Control-Request-Method") != "" || req.Header.Get("Access-Control-Request-Headers") != "") } func IsCredentialed(headers http.Header) bool { @@ -557,7 +557,7 @@ func IsCredentialed(headers http.Header) bool { } func (p *Proxy) setCorsRespHeaders(headers http.Header, req *http.Request) { - if p.options.CORS == nil { + if p.options.CORS == nil || !isCorsRequest(req) { return } requestOrigin := req.Header.Get("Origin") diff --git a/handler/proxy_cors_test.go b/handler/proxy_cors_test.go index da0d17593..6574af78c 100644 --- a/handler/proxy_cors_test.go +++ b/handler/proxy_cors_test.go @@ -155,18 +155,6 @@ func TestCORSOptions_isCorsPreflightRequest(t *testing.T) { map[string]string{"Origin": "https://www.example.com"}, false, }, - { - "OPTIONS, without Origin, with ACRM", - http.MethodOptions, - map[string]string{"Access-Control-Request-Method": "POST"}, - false, - }, - { - "OPTIONS, without Origin, with ACRH", - http.MethodOptions, - map[string]string{"Access-Control-Request-Headers": "Content-Type"}, - false, - }, { "POST, with Origin, with ACRM", http.MethodPost, From ffaa19aa5267d784b10d17ce1a2f5b7c2f700fe8 Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Fri, 15 Jan 2021 18:48:50 +0100 Subject: [PATCH 2/7] Docu --- docs/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 56374fb06..0a997bc0f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -348,7 +348,11 @@ A `backend` defines the connection to a local/remote backend service. Backends c | `origin` | URL to connect to for backend requests
⚠ must start with the scheme `http://...` || | `path` | changeable part of upstream URL || | `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`. | `64MiB` | -| `set_request_headers` | header map to define additional or override header for the `origin` request || +| `add_request_headers` | header map to define additional header values for the `origin` request || +| `add_response_headers` | same as `add_request_headers` for the client response || +| `remove_request_headers` | header list to define header to be removed from the `origin` request || +| `remove_response_headers` | same as `remove_request_headers` for the client response || +| `set_request_headers` | header map to override header for the `origin` request || | `set_response_headers` | same as `set_request_headers` for the client response || | [`openapi`](#openapi_block) | Definition for validating outgoing requests to the `origin` and incoming responses from the `origin`. || | [`remove_query_params`](#query_params) | a list of query parameters to be removed from the upstream request URL || From 9fc4bba63adc7389c40b2ad396d5124b71ad9205 Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Fri, 15 Jan 2021 18:49:09 +0100 Subject: [PATCH 3/7] Add support for `add/set/remove_re..._headers` Remove support for `re..._headers` --- config/backend.go | 6 ++- handler/context_options.go | 6 ++- handler/proxy.go | 83 ++++++++++++++++++++++++++------------ handler/proxy_test.go | 14 +++---- 4 files changed, 73 insertions(+), 36 deletions(-) diff --git a/config/backend.go b/config/backend.go index dff676da9..e385e3f3a 100644 --- a/config/backend.go +++ b/config/backend.go @@ -40,10 +40,12 @@ func (b Backend) Schema(inline bool) *hcl.BodySchema { 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"` + AddRequestHeaders map[string]string `hcl:"add_request_headers,optional"` + DelRequestHeaders []string `hcl:"remove_request_headers,optional"` SetResponseHeaders map[string]string `hcl:"set_response_headers,optional"` + AddResponseHeaders map[string]string `hcl:"add_response_headers,optional"` + DelResponseHeaders []string `hcl:"remove_response_headers,optional"` AddQueryParams map[string]cty.Value `hcl:"add_query_params,optional"` DelQueryParams []string `hcl:"remove_query_params,optional"` SetQueryParams map[string]cty.Value `hcl:"set_query_params,optional"` diff --git a/handler/context_options.go b/handler/context_options.go index b5ab112b5..a858bbeac 100644 --- a/handler/context_options.go +++ b/handler/context_options.go @@ -7,10 +7,12 @@ import ( ) const ( - attrReqHeaders = "request_headers" - attrResHeaders = "response_headers" attrSetReqHeaders = "set_request_headers" + attrAddReqHeaders = "add_request_headers" + attrDelReqHeaders = "remove_request_headers" attrSetResHeaders = "set_response_headers" + attrAddResHeaders = "add_response_headers" + attrDelResHeaders = "remove_response_headers" attrAddQueryParams = "add_query_params" attrDelQueryParams = "remove_query_params" attrSetQueryParams = "set_query_params" diff --git a/handler/proxy.go b/handler/proxy.go index 678e89301..b8e4fc2d4 100644 --- a/handler/proxy.go +++ b/handler/proxy.go @@ -406,51 +406,84 @@ func (p *Proxy) Director(req *http.Request) error { func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) { var ( - attrCtx = []string{attrReqHeaders, attrSetReqHeaders} - bereq *http.Request - headerCtx http.Header + attrCtxAdd = attrAddReqHeaders + attrCtxDel = attrDelReqHeaders + attrCtxSet = attrSetReqHeaders + bereq *http.Request + headerCtx http.Header ) if beresp != nil { - attrCtx = []string{attrResHeaders, attrSetResHeaders} + attrCtxAdd = attrAddResHeaders + attrCtxDel = attrDelResHeaders + attrCtxSet = attrSetResHeaders bereq = beresp.Request headerCtx = beresp.Header + + defer p.setCorsRespHeaders(headerCtx, req) } else if req != nil { headerCtx = req.Header - } - - evalCtx := eval.NewHTTPContext(p.evalContext, p.bufferOption, req, bereq, beresp) - // Remove blacklisted headers after evaluation to be accessible within our context configuration. - if attrCtx[0] == attrReqHeaders { - for _, key := range headerBlacklist { - headerCtx.Del(key) + // Remove blacklisted headers after evaluation to + // be accessible within our context configuration. + if attrCtxSet == attrSetReqHeaders { + for _, key := range headerBlacklist { + headerCtx.Del(key) + } } } allAttributes, attrOk := p.options.Context.(body.Attributes) - - if beresp != nil { - defer p.setCorsRespHeaders(headerCtx, req) - } - if !attrOk { return } - // apply header values - for _, ctxName := range attrCtx { // headers - for _, attrs := range allAttributes.JustAllAttributesWithName(ctxName) { - attr, ok := attrs[ctxName] - if !ok { - continue + evalCtx := eval.NewHTTPContext(p.evalContext, p.bufferOption, req, bereq, beresp) + + // apply header values in hierarchical and logical order: delete, set, add + for _, attrs := range allAttributes.JustAllAttributes() { + attr, ok := attrs[attrCtxDel] + if ok { + val, diags := attr.Expr.Value(evalCtx) + if seetie.SetSeverityLevel(diags).HasErrors() { + p.log.WithField("parse config", p.String()).Error(diags) } + + for _, key := range seetie.ValueToStringSlice(val) { + k := http.CanonicalHeaderKey(key) + if k == "User-Agent" { + headerCtx[k] = []string{} + continue + } + + headerCtx.Del(k) + } + } + + attr, ok = attrs[attrCtxSet] + if ok { + options, diags := NewOptionsMap(evalCtx, attr) + if diags != nil { + p.log.WithField("parse config", p.String()).Error(diags) + } + + for key, values := range options { + k := http.CanonicalHeaderKey(key) + headerCtx[k] = values + } + } + + attr, ok = attrs[attrCtxAdd] + if ok { options, diags := NewOptionsMap(evalCtx, attr) - if diags.HasErrors() { + if diags != nil { p.log.WithField("parse config", p.String()).Error(diags) - continue } - setHeaderFields(headerCtx, options) + + for key, values := range options { + k := http.CanonicalHeaderKey(key) + headerCtx[k] = append(headerCtx[k], values...) + } } } diff --git a/handler/proxy_test.go b/handler/proxy_test.go index fd6f20b15..f4f0db947 100644 --- a/handler/proxy_test.go +++ b/handler/proxy_test.go @@ -954,16 +954,16 @@ func TestProxy_SetRoundtripContext_Null_Eval(t *testing.T) { for i, tc := range []testCase{ {"no eval", `path = "/"`, test.Header{}}, - {"json_body client field", `response_headers = { "x-client" = "my-val-x-${req.json_body.client}" }`, + {"json_body client field", `set_response_headers = { "x-client" = "my-val-x-${req.json_body.client}" }`, test.Header{ "x-client": "my-val-x-true", }}, - {"json_body non existing field", `response_headers = { + {"json_body non existing field", `set_response_headers = { "${beresp.json_body.not-there}" = "my-val-0-${beresp.json_body.origin}" "${req.json_body.client}-my-val-a" = "my-val-b-${beresp.json_body.client}" }`, test.Header{"true-my-val-a": ""}}, // since one reference is failing ('not-there') the whole block does - {"json_body null value", `response_headers = { "x-null" = "${beresp.json_body.nil}" }`, test.Header{"x-null": ""}}, + {"json_body null value", `set_response_headers = { "x-null" = "${beresp.json_body.nil}" }`, test.Header{"x-null": ""}}, } { t.Run(tc.name, func(st *testing.T) { h := test.New(st) @@ -1055,10 +1055,10 @@ func TestProxy_BufferingOptions(t *testing.T) { {"beresp validation", newOptions(), `path = "/"`, eval.BufferResponse}, {"bereq validation", newOptions(), `path = "/"`, eval.BufferRequest}, {"no validation", newOptions(), `path = "/"`, eval.BufferNone}, - {"req buffer json.body & beresp validation", newOptions(), `response_headers = { x-test = "${req.json_body.client}" }`, eval.BufferRequest | eval.BufferResponse}, - {"beresp buffer json.body & bereq validation", newOptions(), `response_headers = { x-test = "${beresp.json_body.origin}" }`, eval.BufferRequest | eval.BufferResponse}, - {"req buffer json.body & bereq validation", newOptions(), `response_headers = { x-test = "${req.json_body.client}" }`, eval.BufferRequest}, - {"beresp buffer json.body & beresp validation", newOptions(), `response_headers = { x-test = "${beresp.json_body.origin}" }`, eval.BufferResponse}, + {"req buffer json.body & beresp validation", newOptions(), `set_response_headers = { x-test = "${req.json_body.client}" }`, eval.BufferRequest | eval.BufferResponse}, + {"beresp buffer json.body & bereq validation", newOptions(), `set_response_headers = { x-test = "${beresp.json_body.origin}" }`, eval.BufferRequest | eval.BufferResponse}, + {"req buffer json.body & bereq validation", newOptions(), `set_response_headers = { x-test = "${req.json_body.client}" }`, eval.BufferRequest}, + {"beresp buffer json.body & beresp validation", newOptions(), `set_response_headers = { x-test = "${beresp.json_body.origin}" }`, eval.BufferResponse}, } { t.Run(tc.name, func(st *testing.T) { h := test.New(st) From c7bb1ae564d8af72be877956601dbc8a5e8e308a Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Mon, 18 Jan 2021 14:46:00 +0100 Subject: [PATCH 4/7] Reduce the number of iterations over options --- handler/proxy.go | 97 +++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/handler/proxy.go b/handler/proxy.go index b8e4fc2d4..c0f57a9c7 100644 --- a/handler/proxy.go +++ b/handler/proxy.go @@ -440,8 +440,14 @@ func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) { evalCtx := eval.NewHTTPContext(p.evalContext, p.bufferOption, req, bereq, beresp) - // apply header values in hierarchical and logical order: delete, set, add + var modifyQuery bool + + u := *req.URL + u.RawQuery = strings.ReplaceAll(u.RawQuery, "+", "%2B") + values := u.Query() + for _, attrs := range allAttributes.JustAllAttributes() { + // apply header values in hierarchical and logical order: delete, set, add attr, ok := attrs[attrCtxDel] if ok { val, diags := attr.Expr.Value(evalCtx) @@ -485,63 +491,62 @@ func (p *Proxy) SetRoundtripContext(req *http.Request, beresp *http.Response) { headerCtx[k] = append(headerCtx[k], values...) } } - } - // apply query params in hierarchical and logical order: delete, set, add - if req != nil && beresp == nil { // just one way -> origin - var modify bool + if req == nil || beresp != nil { // just one way -> origin + continue + } - u := *req.URL - u.RawQuery = strings.ReplaceAll(u.RawQuery, "+", "%2B") - values := u.Query() + // apply query params in hierarchical and logical order: delete, set, add + attr, ok = attrs[attrDelQueryParams] + if ok { + val, diags := attr.Expr.Value(evalCtx) + if seetie.SetSeverityLevel(diags).HasErrors() { + p.log.WithField("parse config", p.String()).Error(diags) + } - // not by name to ensure the order for all params - for _, attrs := range allAttributes.JustAllAttributes() { - attr, ok := attrs[attrDelQueryParams] - if ok { - val, diags := attr.Expr.Value(evalCtx) - if seetie.SetSeverityLevel(diags).HasErrors() { - p.log.WithField("parse config", p.String()).Error(diags) - } - for _, key := range seetie.ValueToStringSlice(val) { - values.Del(key) - } - modify = true + for _, key := range seetie.ValueToStringSlice(val) { + values.Del(key) } - attr, ok = attrs[attrSetQueryParams] - if ok { - options, diags := NewOptionsMap(evalCtx, attr) - if diags != nil { - p.log.WithField("parse config", p.String()).Error(diags) - } - for k, v := range options { - values[k] = v - } - modify = true + modifyQuery = true + } + + attr, ok = attrs[attrSetQueryParams] + if ok { + options, diags := NewOptionsMap(evalCtx, attr) + if diags != nil { + p.log.WithField("parse config", p.String()).Error(diags) } - attr, ok = attrs[attrAddQueryParams] - if ok { - options, diags := NewOptionsMap(evalCtx, attr) - if diags != nil { - p.log.WithField("parse config", p.String()).Error(diags) - } - for k, v := range options { - if _, ok = values[k]; !ok { - values[k] = v - } else { - values[k] = append(values[k], v...) - } - } - modify = true + for k, v := range options { + values[k] = v } + + modifyQuery = true } - if modify { - req.URL.RawQuery = strings.ReplaceAll(values.Encode(), "+", "%20") + attr, ok = attrs[attrAddQueryParams] + if ok { + options, diags := NewOptionsMap(evalCtx, attr) + if diags != nil { + p.log.WithField("parse config", p.String()).Error(diags) + } + + for k, v := range options { + if _, ok = values[k]; !ok { + values[k] = v + } else { + values[k] = append(values[k], v...) + } + } + + modifyQuery = true } } + + if modifyQuery { + req.URL.RawQuery = strings.ReplaceAll(values.Encode(), "+", "%20") + } } // SetGetBody determines if we have to buffer a request body for further processing. From a78e717e65978a57e5d62308a3a447f534d01dd7 Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Tue, 19 Jan 2021 10:10:30 +0100 Subject: [PATCH 5/7] Test --- server/http_integration_test.go | 71 ++++++++++ .../integration/endpoint_eval/12_couper.hcl | 125 ++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 server/testdata/integration/endpoint_eval/12_couper.hcl diff --git a/server/http_integration_test.go b/server/http_integration_test.go index 90f417c80..2b77abf5e 100644 --- a/server/http_integration_test.go +++ b/server/http_integration_test.go @@ -665,6 +665,77 @@ func TestHTTPServer_QueryParams(t *testing.T) { } } +func TestHTTPServer_RequestHeaders(t *testing.T) { + client := newClient() + + const confPath = "testdata/integration/endpoint_eval/" + + type expectation struct { + Headers http.Header + } + + type testCase struct { + file string + query string + exp expectation + } + + for _, tc := range []testCase{ + {"12_couper.hcl", "ae=ae&aeb=aeb&def=def&xyz=xyz", expectation{ + Headers: http.Header{ + "Aeb": []string{"aeb", "aeb"}, + "Aeb_a_and_b": []string{"A&B", "A&B"}, + "Aeb_empty": []string{"", ""}, + "Aeb_multi": []string{"str1", "str2", "str3", "str4"}, + "Aeb_noop": []string{"", ""}, + "Aeb_null": []string{"", ""}, + "Aeb_string": []string{"str", "str"}, + "Def_a_and_b": []string{"A&B", "A&B"}, + "Def_empty": []string{"", ""}, + "Def_multi": []string{"str1", "str2", "str3", "str4"}, + "Def_noop": []string{"", ""}, + "Def_null": []string{"", ""}, + "Def_string": []string{"str", "str"}, + "Def": []string{"def", "def"}, + "Foo": []string{""}, + "Xxx": []string{"aaa", "bbb"}, + }, + }}, + } { + shutdown, _ := newCouper(path.Join(confPath, tc.file), test.New(t)) + + t.Run("_"+tc.query, func(subT *testing.T) { + helper := test.New(subT) + + req, err := http.NewRequest(http.MethodGet, "http://example.com:8080?"+tc.query, nil) + helper.Must(err) + + res, err := client.Do(req) + helper.Must(err) + + resBytes, err := ioutil.ReadAll(res.Body) + helper.Must(err) + + _ = res.Body.Close() + + var jsonResult expectation + err = json.Unmarshal(resBytes, &jsonResult) + if err != nil { + t.Errorf("unmarshal json: %v: got:\n%s", err, string(resBytes)) + } + + jsonResult.Headers.Del("User-Agent") + jsonResult.Headers.Del("X-Forwarded-For") + + if !reflect.DeepEqual(jsonResult, tc.exp) { + t.Errorf("\nwant: \n%#v\ngot: \n%#v\npayload:\n%s", tc.exp, jsonResult, string(resBytes)) + } + }) + + shutdown() + } +} + func TestHTTPServer_QueryEncoding(t *testing.T) { client := newClient() diff --git a/server/testdata/integration/endpoint_eval/12_couper.hcl b/server/testdata/integration/endpoint_eval/12_couper.hcl new file mode 100644 index 000000000..2d97419f2 --- /dev/null +++ b/server/testdata/integration/endpoint_eval/12_couper.hcl @@ -0,0 +1,125 @@ +server "api" { + api { + endpoint "/" { + backend "anything" { + remove_request_headers = [ "aeb_del", "CaseIns", req.query.xyz[0] ] + set_request_headers = { + aeb_string = "str" + aeb_multi = ["str1", "str2"] + aeb_a_and_b = "A&B" + aeb_noop = req.headers.noop + aeb_null = null + aeb_empty = "" + xxx = ["yyy", "xxx"] + xxx = ["aaa", "bbb"] + "${req.query.aeb[0]}" = "aeb" + } + add_request_headers = { + aeb_string = "str" + aeb_multi = ["str3", "str4"] + aeb_a_and_b = "A&B" + aeb_noop = req.headers.noop + aeb_null = null + aeb_empty = "" + "${req.query.aeb[0]}" = "aeb" + } + } + + remove_request_headers = [ "ae_del" ] + set_request_headers = { + ae_string = "str" + ae_multi = ["str1", "str2"] + ae_a_and_b = "A&B" + ae_noop = req.headers.noop + ae_null = null + ae_empty = "" + xxx = "zzz" + "${req.query.ae[0]}" = "ae" + } + add_request_headers = { + ae_string = "str" + ae_multi = ["str3", "str4"] + ae_a_and_b = "A&B" + ae_noop = req.headers.noop + ae_null = null + ae_empty = "" + xxx = "ccc" + "${req.query.ae[0]}" = "ae" + } + } + } + +# TODO: free-endpoints +# endpoint "/free/endpoint" { +# backend { +# origin = "https://w11w.de" +# hostname = "w11w.de" + +# remove_request_headers = [ "feb_del" ] +# set_request_headers = { +# feb_string = "str" +# feb_multi = ["str1", "str2"] +# feb_a_and_b = "A&B" +# feb_noop = req.headers.noop +# feb_null = null +# feb_empty = "" +# } +# add_request_headers = { +# feb_string = "str" +# feb_multi = ["str3", "str4"] +# feb_a_and_b = "A&B" +# feb_noop = req.headers.noop +# feb_null = null +# feb_empty = "" +# } +# } + +# remove_request_headers = [ "fe_del" ] +# set_request_headers = { +# fe_String = "str" +# fe_multi = ["str1", "str2"] +# fe_a_and_b = "A&B" +# fe_noop = req.headers.noop +# fe_null = null +# fe_empty = "" +# } +# add_request_headers = { +# fe_String = "str" +# fe_multi = ["str3", "str4"] +# fe_a_and_b = "A&B" +# fe_noop = req.headers.noop +# fe_null = null +# fe_empty = "" +# } +# } +} + +definitions { + # backend origin within a definition block gets replaced with the integration test "anything" server. + backend "anything" { + origin = env.COUPER_TEST_BACKEND_ADDR + + remove_request_headers = [ "def_del" ] + set_request_headers = { + def_string = "str" + def_multi = ["str1", "str2"] + def_a_and_b = "A&B" + def_noop = req.headers.noop + def_null = null + def_empty = "" + xxx = "ddd" + "${req.query.def[0]}" = "def" + foo = req.query.foo[0] + } + add_request_headers = { + def_string = "str" + def_multi = ["str3", "str4"] + def_a_and_b = "A&B" + def_noop = req.headers.noop + def_null = null + def_empty = "" + xxx = "eee" + "${req.query.def[0]}" = "def" + } + } +} From 8b7432db827a0ede8e9bc81d1a2ba84f0a903ce6 Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Tue, 19 Jan 2021 10:27:49 +0100 Subject: [PATCH 6/7] More tests --- internal/test/test_backend.go | 3 + server/http_integration_test.go | 21 +++++ .../integration/endpoint_eval/12_couper.hcl | 80 ++++--------------- 3 files changed, 39 insertions(+), 65 deletions(-) diff --git a/internal/test/test_backend.go b/internal/test/test_backend.go index 6d296dc80..9d6242a09 100644 --- a/internal/test/test_backend.go +++ b/internal/test/test_backend.go @@ -75,6 +75,9 @@ func createAnythingHandler(status int) func(rw http.ResponseWriter, req *http.Re rw.Header().Set("Content-Length", strconv.Itoa(len(respContent))) rw.Header().Set("Content-Type", "application/json") + rw.Header().Set("Remove-Me-1", "r1") + rw.Header().Set("Remove-Me-2", "r2") + rw.WriteHeader(status) _, _ = rw.Write(respContent) } diff --git a/server/http_integration_test.go b/server/http_integration_test.go index 2b77abf5e..7ced2777b 100644 --- a/server/http_integration_test.go +++ b/server/http_integration_test.go @@ -713,6 +713,27 @@ func TestHTTPServer_RequestHeaders(t *testing.T) { res, err := client.Do(req) helper.Must(err) + if r1 := res.Header.Get("Remove-Me-1"); r1 != "" { + t.Errorf("Unexpected header %s", r1) + } + if r2 := res.Header.Get("Remove-Me-2"); r2 != "" { + t.Errorf("Unexpected header %s", r2) + } + + if s1 := res.Header.Get("Set-Me-1"); s1 != "s1" { + t.Errorf("Missing or invalid header Set-Me-1: %s", s1) + } + if s2 := res.Header.Get("Set-Me-2"); s2 != "s2" { + t.Errorf("Missing or invalid header Set-Me-2: %s", s2) + } + + if a1 := res.Header.Get("Add-Me-1"); a1 != "a1" { + t.Errorf("Missing or invalid header Add-Me-1: %s", a1) + } + if a2 := res.Header.Get("Add-Me-2"); a2 != "a2" { + t.Errorf("Missing or invalid header Add-Me-2: %s", a2) + } + resBytes, err := ioutil.ReadAll(res.Body) helper.Must(err) diff --git a/server/testdata/integration/endpoint_eval/12_couper.hcl b/server/testdata/integration/endpoint_eval/12_couper.hcl index 2d97419f2..6a313bd40 100644 --- a/server/testdata/integration/endpoint_eval/12_couper.hcl +++ b/server/testdata/integration/endpoint_eval/12_couper.hcl @@ -23,75 +23,17 @@ server "api" { aeb_empty = "" "${req.query.aeb[0]}" = "aeb" } - } - remove_request_headers = [ "ae_del" ] - set_request_headers = { - ae_string = "str" - ae_multi = ["str1", "str2"] - ae_a_and_b = "A&B" - ae_noop = req.headers.noop - ae_null = null - ae_empty = "" - xxx = "zzz" - "${req.query.ae[0]}" = "ae" - } - add_request_headers = { - ae_string = "str" - ae_multi = ["str3", "str4"] - ae_a_and_b = "A&B" - ae_noop = req.headers.noop - ae_null = null - ae_empty = "" - xxx = "ccc" - "${req.query.ae[0]}" = "ae" + remove_response_headers = [ "Remove-Me-2" ] + set_response_headers = { + "Set-Me-2" = "s2" + } + add_response_headers = { + "Add-Me-2" = "a2" + } } } } - -# TODO: free-endpoints -# endpoint "/free/endpoint" { -# backend { -# origin = "https://w11w.de" -# hostname = "w11w.de" - -# remove_request_headers = [ "feb_del" ] -# set_request_headers = { -# feb_string = "str" -# feb_multi = ["str1", "str2"] -# feb_a_and_b = "A&B" -# feb_noop = req.headers.noop -# feb_null = null -# feb_empty = "" -# } -# add_request_headers = { -# feb_string = "str" -# feb_multi = ["str3", "str4"] -# feb_a_and_b = "A&B" -# feb_noop = req.headers.noop -# feb_null = null -# feb_empty = "" -# } -# } - -# remove_request_headers = [ "fe_del" ] -# set_request_headers = { -# fe_String = "str" -# fe_multi = ["str1", "str2"] -# fe_a_and_b = "A&B" -# fe_noop = req.headers.noop -# fe_null = null -# fe_empty = "" -# } -# add_request_headers = { -# fe_String = "str" -# fe_multi = ["str3", "str4"] -# fe_a_and_b = "A&B" -# fe_noop = req.headers.noop -# fe_null = null -# fe_empty = "" -# } -# } } definitions { @@ -121,5 +63,13 @@ definitions { xxx = "eee" "${req.query.def[0]}" = "def" } + + remove_response_headers = [ "remove-me-1" ] + set_response_headers = { + "set-me-1" = "s1" + } + add_response_headers = { + "add-me-1" = "a1" + } } } From fe988af0a3b2d3f6006ba01d4dc4d11e5bfec166 Mon Sep 17 00:00:00 2001 From: Marcel Ludwig Date: Tue, 19 Jan 2021 18:15:46 +0100 Subject: [PATCH 7/7] format docs --- docs/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0a997bc0f..bbc8aa3fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -348,11 +348,11 @@ A `backend` defines the connection to a local/remote backend service. Backends c | `origin` | URL to connect to for backend requests
⚠ must start with the scheme `http://...` || | `path` | changeable part of upstream URL || | `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`. | `64MiB` | -| `add_request_headers` | header map to define additional header values for the `origin` request || -| `add_response_headers` | same as `add_request_headers` for the client response || -| `remove_request_headers` | header list to define header to be removed from the `origin` request || -| `remove_response_headers` | same as `remove_request_headers` for the client response || -| `set_request_headers` | header map to override header for the `origin` request || +| `add_request_headers` | header map to define additional header values for the `origin` request || +| `add_response_headers` | same as `add_request_headers` for the client response || +| `remove_request_headers` | header list to define header to be removed from the `origin` request || +| `remove_response_headers` | same as `remove_request_headers` for the client response || +| `set_request_headers` | header map to override header for the `origin` request || | `set_response_headers` | same as `set_request_headers` for the client response || | [`openapi`](#openapi_block) | Definition for validating outgoing requests to the `origin` and incoming responses from the `origin`. || | [`remove_query_params`](#query_params) | a list of query parameters to be removed from the upstream request URL ||