From 0d2691db91d097acd575e1af38d4a1ed62cf9c7a Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Mon, 27 Mar 2023 14:45:53 +0200 Subject: [PATCH 01/12] backend_responses: use of .json_body necessary for JSON parsing response --- eval/buffer.go | 5 ++++- eval/buffer_string.go | 18 ++++++++++++++---- eval/buffer_test.go | 2 +- eval/context.go | 6 +++--- handler/transport/token_request.go | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/eval/buffer.go b/eval/buffer.go index 125a4610f..a97ab76e5 100644 --- a/eval/buffer.go +++ b/eval/buffer.go @@ -18,11 +18,13 @@ const ( BufferNone BufferOption = iota BufferRequest BufferResponse + _ + JSONParseResponse ) func (i BufferOption) GoString() string { var result []string - for _, o := range []BufferOption{BufferRequest, BufferResponse} { + for _, o := range []BufferOption{BufferRequest, BufferResponse, JSONParseResponse} { if (i & o) == o { result = append(result, o.String()) } @@ -118,6 +120,7 @@ func MustBuffer(bodies ...hcl.Body) BufferOption { fallthrough case BackendResponses: result |= BufferResponse + result |= JSONParseResponse } default: // e.g. backend_responses.default diff --git a/eval/buffer_string.go b/eval/buffer_string.go index 22218b370..8472cfadd 100644 --- a/eval/buffer_string.go +++ b/eval/buffer_string.go @@ -11,15 +11,25 @@ func _() { _ = x[BufferNone-0] _ = x[BufferRequest-1] _ = x[BufferResponse-2] + _ = x[JSONParseResponse-4] } -const _BufferOption_name = "BufferNoneBufferRequestBufferResponse" +const ( + _BufferOption_name_0 = "BufferNoneBufferRequestBufferResponse" + _BufferOption_name_1 = "JSONParseResponse" +) -var _BufferOption_index = [...]uint8{0, 10, 23, 37} +var ( + _BufferOption_index_0 = [...]uint8{0, 10, 23, 37} +) func (i BufferOption) String() string { - if i >= BufferOption(len(_BufferOption_index)-1) { + switch { + case i <= 2: + return _BufferOption_name_0[_BufferOption_index_0[i]:_BufferOption_index_0[i+1]] + case i == 4: + return _BufferOption_name_1 + default: return "BufferOption(" + strconv.FormatInt(int64(i), 10) + ")" } - return _BufferOption_name[_BufferOption_index[i]:_BufferOption_index[i+1]] } diff --git a/eval/buffer_test.go b/eval/buffer_test.go index 87bb1ecdc..f77844569 100644 --- a/eval/buffer_test.go +++ b/eval/buffer_test.go @@ -27,7 +27,7 @@ func TestMustBuffer(t *testing.T) { {"buffer responses", `endpoint "/" { set_response_headers = { x = backend_responses } }`, BufferResponse}, {"buffer default response", `endpoint "/" { set_response_headers = { x = backend_responses.default } }`, BufferResponse}, {"buffer response body", `endpoint "/" { set_response_headers = { x = backend_responses.default.body } }`, BufferResponse}, - {"buffer response json_body", `endpoint "/" { set_response_headers = { x = backend_responses.default.json_body } }`, BufferResponse}, + {"buffer response json_body", `endpoint "/" { set_response_headers = { x = backend_responses.default.json_body } }`, BufferResponse|JSONParseResponse}, {"buffer request/response", `endpoint "/" { set_response_headers = { x = request diff --git a/eval/context.go b/eval/context.go index cf05cc068..f5f8f7e8d 100644 --- a/eval/context.go +++ b/eval/context.go @@ -274,7 +274,7 @@ func newBerespValues(ctx context.Context, readBody bool, beresp *http.Response) var respBody, respJSONBody cty.Value if readBody && !IsUpgradeResponse(bereq, beresp) { if bOk && (bufferOption&BufferResponse) == BufferResponse { - respBody, respJSONBody = parseRespBody(beresp) + respBody, respJSONBody = parseRespBody(beresp, (bufferOption&JSONParseResponse) == JSONParseResponse) } } else if bOk && (bufferOption&BufferResponse) != BufferResponse { hasBlock, _ := bereq.Context().Value(request.ResponseBlock).(bool) @@ -505,7 +505,7 @@ func parseReqBody(req *http.Request) (cty.Value, cty.Value) { return cty.StringVal(string(b)), jsonBody } -func parseRespBody(beresp *http.Response) (cty.Value, cty.Value) { +func parseRespBody(beresp *http.Response, parseJSON bool) (cty.Value, cty.Value) { jsonBody := cty.EmptyObjectVal b := parseSetRespBody(beresp) @@ -513,7 +513,7 @@ func parseRespBody(beresp *http.Response) (cty.Value, cty.Value) { return cty.NilVal, jsonBody } - if isJSONMediaType(beresp.Header.Get("Content-Type")) { + if parseJSON && isJSONMediaType(beresp.Header.Get("Content-Type")) { jsonBody = parseJSONBytes(b) } return cty.StringVal(string(b)), jsonBody diff --git a/handler/transport/token_request.go b/handler/transport/token_request.go index 58473b05b..f7cba9a9a 100644 --- a/handler/transport/token_request.go +++ b/handler/transport/token_request.go @@ -80,7 +80,7 @@ func (t *TokenRequest) readToken() string { func (t *TokenRequest) requestToken(req *http.Request) (string, int64, error) { ctx := context.WithValue(req.Context(), request.Wildcard, nil) // disable handling this - ctx = context.WithValue(ctx, request.BufferOptions, eval.BufferResponse) // always read out a possible token + ctx = context.WithValue(ctx, request.BufferOptions, eval.BufferResponse|eval.JSONParseResponse) // always read out a possible token ctx = context.WithValue(ctx, request.TokenRequest, t.config.Name) // set the name for variable mapping purposes outreq, _ := http.NewRequestWithContext(ctx, req.Method, "", nil) result := t.reqProducer.Produce(outreq) From 73b4838e252d86ab54f177bb0123866c028ecd35 Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Mon, 27 Mar 2023 17:09:45 +0200 Subject: [PATCH 02/12] backend_request: use of .json_body necessary for JSON parsing request --- eval/buffer.go | 7 ++++++- eval/buffer_string.go | 8 ++++++-- eval/buffer_test.go | 2 +- eval/context.go | 13 +++++++------ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/eval/buffer.go b/eval/buffer.go index a97ab76e5..6c3bc7134 100644 --- a/eval/buffer.go +++ b/eval/buffer.go @@ -19,12 +19,16 @@ const ( BufferRequest BufferResponse _ + JSONParseRequest + _ + _ + _ JSONParseResponse ) func (i BufferOption) GoString() string { var result []string - for _, o := range []BufferOption{BufferRequest, BufferResponse, JSONParseResponse} { + for _, o := range []BufferOption{BufferRequest, BufferResponse, JSONParseRequest, JSONParseResponse} { if (i & o) == o { result = append(result, o.String()) } @@ -115,6 +119,7 @@ func MustBuffer(bodies ...hcl.Body) BufferOption { fallthrough case BackendRequest: result |= BufferRequest + result |= JSONParseRequest case BackendResponse: fallthrough diff --git a/eval/buffer_string.go b/eval/buffer_string.go index 8472cfadd..2c551f278 100644 --- a/eval/buffer_string.go +++ b/eval/buffer_string.go @@ -11,12 +11,14 @@ func _() { _ = x[BufferNone-0] _ = x[BufferRequest-1] _ = x[BufferResponse-2] - _ = x[JSONParseResponse-4] + _ = x[JSONParseRequest-4] + _ = x[JSONParseResponse-8] } const ( _BufferOption_name_0 = "BufferNoneBufferRequestBufferResponse" - _BufferOption_name_1 = "JSONParseResponse" + _BufferOption_name_1 = "JSONParseRequest" + _BufferOption_name_2 = "JSONParseResponse" ) var ( @@ -29,6 +31,8 @@ func (i BufferOption) String() string { return _BufferOption_name_0[_BufferOption_index_0[i]:_BufferOption_index_0[i+1]] case i == 4: return _BufferOption_name_1 + case i == 8: + return _BufferOption_name_2 default: return "BufferOption(" + strconv.FormatInt(int64(i), 10) + ")" } diff --git a/eval/buffer_test.go b/eval/buffer_test.go index f77844569..8f72afb99 100644 --- a/eval/buffer_test.go +++ b/eval/buffer_test.go @@ -20,7 +20,7 @@ func TestMustBuffer(t *testing.T) { {"buffer request", `endpoint "/" { set_response_headers = { x = request } }`, BufferRequest}, {"buffer request body", `endpoint "/" { set_response_headers = { x = request.body } }`, BufferRequest}, {"buffer request form_body", `endpoint "/" { set_response_headers = { x = request.form_body } }`, BufferRequest}, - {"buffer request json_body", `endpoint "/" { set_response_headers = { x = request.json_body } }`, BufferRequest}, + {"buffer request json_body", `endpoint "/" { set_response_headers = { x = request.json_body } }`, BufferRequest|JSONParseRequest}, {"buffer request add_form_params", `endpoint "/" { add_form_params = [] }`, BufferRequest}, {"buffer request set_form_params", `endpoint "/" { set_form_params = [] }`, BufferRequest}, {"buffer request remove_form_params", `endpoint "/" { remove_form_params = [] }`, BufferRequest}, diff --git a/eval/context.go b/eval/context.go index f5f8f7e8d..cfd21b020 100644 --- a/eval/context.go +++ b/eval/context.go @@ -161,7 +161,8 @@ func (c *Context) WithClientRequest(req *http.Request) *Context { } } port, _ := strconv.ParseInt(p, 10, 64) - body, jsonBody := parseReqBody(req) + // TODO how to set request.BufferOptions appropriately before WithClientRequest() is called + body, jsonBody := parseReqBody(req, true) origin := NewRawOrigin(req.URL) ctx.eval.Variables[ClientRequest] = cty.ObjectVal(ctxMap.Merge(ContextMap{ @@ -254,7 +255,9 @@ func newBerespValues(ctx context.Context, readBody bool, beresp *http.Response) } port, _ := strconv.ParseInt(p, 10, 64) - body, jsonBody := parseReqBody(bereq) + bufferOption, bOk := bereq.Context().Value(request.BufferOptions).(BufferOption) + + body, jsonBody := parseReqBody(bereq, (bufferOption&JSONParseRequest) == JSONParseRequest) bereqVal = cty.ObjectVal(ContextMap{ Method: cty.StringVal(bereq.Method), URL: cty.StringVal(bereq.URL.String()), @@ -269,8 +272,6 @@ func newBerespValues(ctx context.Context, readBody bool, beresp *http.Response) FormBody: seetie.ValuesMapToValue(parseForm(bereq).PostForm), }.Merge(newVariable(ctx, bereq.Cookies(), bereq.Header))) - bufferOption, bOk := bereq.Context().Value(request.BufferOptions).(BufferOption) - var respBody, respJSONBody cty.Value if readBody && !IsUpgradeResponse(bereq, beresp) { if bOk && (bufferOption&BufferResponse) == BufferResponse { @@ -487,7 +488,7 @@ func isJSONMediaType(contentType string) bool { return len(mParts) == 2 && mParts[0] == "application" && (mParts[1] == "json" || strings.HasSuffix(mParts[1], "+json")) } -func parseReqBody(req *http.Request) (cty.Value, cty.Value) { +func parseReqBody(req *http.Request, parseJSON bool) (cty.Value, cty.Value) { jsonBody := cty.EmptyObjectVal if req == nil || req.GetBody == nil { return cty.NilVal, jsonBody @@ -499,7 +500,7 @@ func parseReqBody(req *http.Request) (cty.Value, cty.Value) { return cty.NilVal, jsonBody } - if isJSONMediaType(req.Header.Get("Content-Type")) { + if parseJSON && isJSONMediaType(req.Header.Get("Content-Type")) { jsonBody = parseJSONBytes(b) } return cty.StringVal(string(b)), jsonBody From 2d4fa1e7c0e97ad9c51905cf266776d7e55d5557 Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Mon, 27 Mar 2023 18:39:00 +0200 Subject: [PATCH 03/12] also look for backend_requests (plural) --- eval/buffer.go | 12 ++-- server/http_endpoints_test.go | 57 ++++++++++++++++++- .../integration/backends/02_couper.hcl | 22 +++++++ 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/eval/buffer.go b/eval/buffer.go index 6c3bc7134..3a8dcf287 100644 --- a/eval/buffer.go +++ b/eval/buffer.go @@ -76,7 +76,7 @@ func MustBuffer(bodies ...hcl.Body) BufferOption { rootName := traversal.RootName() if len(traversal) == 1 { - if rootName == ClientRequest || rootName == BackendRequest { + if rootName == ClientRequest || rootName == BackendRequests || rootName == BackendRequest { result |= BufferRequest } if rootName == BackendResponses || rootName == BackendResponse { @@ -85,7 +85,7 @@ func MustBuffer(bodies ...hcl.Body) BufferOption { continue } - if rootName != ClientRequest && rootName != BackendRequest && rootName != BackendResponses && rootName != BackendResponse { + if rootName != ClientRequest && rootName != BackendRequests && rootName != BackendRequest && rootName != BackendResponses && rootName != BackendResponse { continue } @@ -98,6 +98,8 @@ func MustBuffer(bodies ...hcl.Body) BufferOption { case ClientRequest: fallthrough case BackendRequest: + fallthrough + case BackendRequests: result |= BufferRequest case BackendResponse: @@ -106,11 +108,11 @@ func MustBuffer(bodies ...hcl.Body) BufferOption { result |= BufferResponse } case CTX: // e.g. jwt token (value) could be read from any (body) source - if rootName == ClientRequest || rootName == BackendRequest { + if rootName == ClientRequest || rootName == BackendRequests || rootName == BackendRequest { result |= BufferRequest } case FormBody: - if rootName == ClientRequest || rootName == BackendRequest { + if rootName == ClientRequest || rootName == BackendRequests || rootName == BackendRequest { result |= BufferRequest } case JSONBody: @@ -118,6 +120,8 @@ func MustBuffer(bodies ...hcl.Body) BufferOption { case ClientRequest: fallthrough case BackendRequest: + fallthrough + case BackendRequests: result |= BufferRequest result |= JSONParseRequest diff --git a/server/http_endpoints_test.go b/server/http_endpoints_test.go index f7641ee17..e331ca812 100644 --- a/server/http_endpoints_test.go +++ b/server/http_endpoints_test.go @@ -61,7 +61,12 @@ func TestBackend_BackendVariable_RequestResponse(t *testing.T) { responseLogs, _ := entry.Data["response"].(logging.Fields) data, _ := entry.Data["custom"].(logrus.Fields) - if data != nil && entry.Data["backend"] == "anonymous_76_16" { + if data == nil { + t.Error("missing custom logs") + continue + } + + if entry.Data["backend"] == "anonymous_76_16" { expected := logrus.Fields{ "x-from-request-body": "grant_type=client_credentials", "x-from-request-form-body": "client_credentials", @@ -82,7 +87,7 @@ func TestBackend_BackendVariable_RequestResponse(t *testing.T) { if diff := cmp.Diff(responseLogs["headers"], expectedHeaders); diff != "" { t.Error(diff) } - } else { + } else if entry.Data["backend"] == "anonymous_44_23" { expected := logrus.Fields{ "x-from-request-json-body": float64(1), "x-from-request-header": "bar", @@ -96,6 +101,54 @@ func TestBackend_BackendVariable_RequestResponse(t *testing.T) { if diff := cmp.Diff(data, expected); diff != "" { t.Error(diff) } + } else { + t.Error("wrong backend log") + } + } +} + +func TestBackend_BackendVariable_RequestResponse2(t *testing.T) { + client := newClient() + helper := test.New(t) + + shutdown, hook := newCouper("testdata/integration/backends/02_couper.hcl", helper) + defer shutdown() + + req, err := http.NewRequest(http.MethodGet, "http://example.com:8080/request2", nil) + helper.Must(err) + + hook.Reset() + res, err := client.Do(req) + helper.Must(err) + + if res.Header.Get("X-From-Requests-Json-Body") != "2" || + res.Header.Get("X-From-Requests-Body") != `{"b":2}` { + t.Errorf("Unexpected header given: %#v", res.Header) + } + + for _, entry := range hook.AllEntries() { + if entry.Data["type"] != "couper_backend" { + continue + } + + data, _ := entry.Data["custom"].(logrus.Fields) + + if data == nil { + t.Error("missing custom logs") + continue + } + + if entry.Data["backend"] == "anonymous_101_17" { + expected := logrus.Fields{ + "x-from-requests-json-body": float64(2), + "x-from-requests-body": `{"b":2}`, + } + + if diff := cmp.Diff(data, expected); diff != "" { + t.Error(diff) + } + } else { + t.Error("wrong backend log") } } } diff --git a/server/testdata/integration/backends/02_couper.hcl b/server/testdata/integration/backends/02_couper.hcl index 8652118b1..33a37c2d3 100644 --- a/server/testdata/integration/backends/02_couper.hcl +++ b/server/testdata/integration/backends/02_couper.hcl @@ -96,6 +96,28 @@ server { } } } + + endpoint "/request2" { + request "r" { + url = "${env.COUPER_TEST_BACKEND_ADDR}/anything" + + json_body = { + b = 2 + } + backend { + custom_log_fields = { + x-from-requests-body = backend_requests.r.body + x-from-requests-json-body = backend_requests.r.json_body.b + } + } + } + response { + headers = { + x-from-requests-body = backend_requests.r.body + x-from-requests-json-body = backend_requests.r.json_body.b + } + } + } } definitions { From 9122e08e9cd149c9d3ed8487a94616a8085cce11 Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Mon, 27 Mar 2023 18:43:48 +0200 Subject: [PATCH 04/12] buffer backend request body only if necessary --- eval/context.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eval/context.go b/eval/context.go index cfd21b020..e0a07ef63 100644 --- a/eval/context.go +++ b/eval/context.go @@ -256,8 +256,11 @@ func newBerespValues(ctx context.Context, readBody bool, beresp *http.Response) port, _ := strconv.ParseInt(p, 10, 64) bufferOption, bOk := bereq.Context().Value(request.BufferOptions).(BufferOption) + var body, jsonBody cty.Value - body, jsonBody := parseReqBody(bereq, (bufferOption&JSONParseRequest) == JSONParseRequest) + if bOk && (bufferOption&BufferRequest) == BufferRequest { + body, jsonBody = parseReqBody(bereq, (bufferOption&JSONParseRequest) == JSONParseRequest) + } bereqVal = cty.ObjectVal(ContextMap{ Method: cty.StringVal(bereq.Method), URL: cty.StringVal(bereq.URL.String()), From 9be586ef3c04219d1b5bcdd98416fe54f21872bd Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Tue, 28 Mar 2023 08:23:59 +0200 Subject: [PATCH 05/12] go fmt --- eval/buffer_test.go | 4 ++-- handler/transport/token_request.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eval/buffer_test.go b/eval/buffer_test.go index 8f72afb99..81076a7e2 100644 --- a/eval/buffer_test.go +++ b/eval/buffer_test.go @@ -20,14 +20,14 @@ func TestMustBuffer(t *testing.T) { {"buffer request", `endpoint "/" { set_response_headers = { x = request } }`, BufferRequest}, {"buffer request body", `endpoint "/" { set_response_headers = { x = request.body } }`, BufferRequest}, {"buffer request form_body", `endpoint "/" { set_response_headers = { x = request.form_body } }`, BufferRequest}, - {"buffer request json_body", `endpoint "/" { set_response_headers = { x = request.json_body } }`, BufferRequest|JSONParseRequest}, + {"buffer request json_body", `endpoint "/" { set_response_headers = { x = request.json_body } }`, BufferRequest | JSONParseRequest}, {"buffer request add_form_params", `endpoint "/" { add_form_params = [] }`, BufferRequest}, {"buffer request set_form_params", `endpoint "/" { set_form_params = [] }`, BufferRequest}, {"buffer request remove_form_params", `endpoint "/" { remove_form_params = [] }`, BufferRequest}, {"buffer responses", `endpoint "/" { set_response_headers = { x = backend_responses } }`, BufferResponse}, {"buffer default response", `endpoint "/" { set_response_headers = { x = backend_responses.default } }`, BufferResponse}, {"buffer response body", `endpoint "/" { set_response_headers = { x = backend_responses.default.body } }`, BufferResponse}, - {"buffer response json_body", `endpoint "/" { set_response_headers = { x = backend_responses.default.json_body } }`, BufferResponse|JSONParseResponse}, + {"buffer response json_body", `endpoint "/" { set_response_headers = { x = backend_responses.default.json_body } }`, BufferResponse | JSONParseResponse}, {"buffer request/response", `endpoint "/" { set_response_headers = { x = request diff --git a/handler/transport/token_request.go b/handler/transport/token_request.go index f7cba9a9a..f92cb85e4 100644 --- a/handler/transport/token_request.go +++ b/handler/transport/token_request.go @@ -79,9 +79,9 @@ func (t *TokenRequest) readToken() string { } func (t *TokenRequest) requestToken(req *http.Request) (string, int64, error) { - ctx := context.WithValue(req.Context(), request.Wildcard, nil) // disable handling this + ctx := context.WithValue(req.Context(), request.Wildcard, nil) // disable handling this ctx = context.WithValue(ctx, request.BufferOptions, eval.BufferResponse|eval.JSONParseResponse) // always read out a possible token - ctx = context.WithValue(ctx, request.TokenRequest, t.config.Name) // set the name for variable mapping purposes + ctx = context.WithValue(ctx, request.TokenRequest, t.config.Name) // set the name for variable mapping purposes outreq, _ := http.NewRequestWithContext(ctx, req.Method, "", nil) result := t.reqProducer.Produce(outreq) if result.Err != nil { From e7539462c058df43613e660d30314f3767d3d5d3 Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Thu, 30 Mar 2023 09:06:43 +0200 Subject: [PATCH 06/12] more tests for buffer options; fix for backend_requests.r --- eval/buffer.go | 3 +++ eval/buffer_test.go | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/eval/buffer.go b/eval/buffer.go index 3a8dcf287..12a413fc5 100644 --- a/eval/buffer.go +++ b/eval/buffer.go @@ -137,6 +137,9 @@ func MustBuffer(bodies ...hcl.Body) BufferOption { if rootName == BackendResponse || rootName == BackendResponses { result |= BufferResponse } + if rootName == BackendRequest || rootName == BackendRequests { + result |= BufferRequest + } } } } diff --git a/eval/buffer_test.go b/eval/buffer_test.go index 81076a7e2..fd852dd2c 100644 --- a/eval/buffer_test.go +++ b/eval/buffer_test.go @@ -21,13 +21,22 @@ func TestMustBuffer(t *testing.T) { {"buffer request body", `endpoint "/" { set_response_headers = { x = request.body } }`, BufferRequest}, {"buffer request form_body", `endpoint "/" { set_response_headers = { x = request.form_body } }`, BufferRequest}, {"buffer request json_body", `endpoint "/" { set_response_headers = { x = request.json_body } }`, BufferRequest | JSONParseRequest}, + {"buffer backend_requests specific", `endpoint "/" { set_response_headers = { x = backend_requests.r } }`, BufferRequest}, + {"buffer backend_requests body", `endpoint "/" { set_response_headers = { x = backend_requests.r.body } }`, BufferRequest}, + {"buffer backend_requests form_body", `endpoint "/" { set_response_headers = { x = backend_requests.r.form_body } }`, BufferRequest}, + {"buffer backend_requests json_body", `endpoint "/" { set_response_headers = { x = backend_requests.r.json_body } }`, BufferRequest | JSONParseRequest}, + {"buffer backend_request body", `backend "b" { set_response_headers = { x = backend_request.body } }`, BufferRequest}, + {"buffer backend_request form_body", `backend "b" { set_response_headers = { x = backend_request.form_body } }`, BufferRequest}, + {"buffer backend_request json_body", `backend "b" { set_response_headers = { x = backend_request.json_body } }`, BufferRequest | JSONParseRequest}, {"buffer request add_form_params", `endpoint "/" { add_form_params = [] }`, BufferRequest}, {"buffer request set_form_params", `endpoint "/" { set_form_params = [] }`, BufferRequest}, {"buffer request remove_form_params", `endpoint "/" { remove_form_params = [] }`, BufferRequest}, - {"buffer responses", `endpoint "/" { set_response_headers = { x = backend_responses } }`, BufferResponse}, - {"buffer default response", `endpoint "/" { set_response_headers = { x = backend_responses.default } }`, BufferResponse}, - {"buffer response body", `endpoint "/" { set_response_headers = { x = backend_responses.default.body } }`, BufferResponse}, - {"buffer response json_body", `endpoint "/" { set_response_headers = { x = backend_responses.default.json_body } }`, BufferResponse | JSONParseResponse}, + {"buffer backend_responses", `endpoint "/" { set_response_headers = { x = backend_responses } }`, BufferResponse}, + {"buffer backend_responses default", `endpoint "/" { set_response_headers = { x = backend_responses.default } }`, BufferResponse}, + {"buffer backend_responses body", `endpoint "/" { set_response_headers = { x = backend_responses.default.body } }`, BufferResponse}, + {"buffer backend_responses json_body", `endpoint "/" { set_response_headers = { x = backend_responses.default.json_body } }`, BufferResponse | JSONParseResponse}, + {"buffer backend_response body", `backend "b" { set_response_headers = { x = backend_response.body } }`, BufferResponse}, + {"buffer backend_response json_body", `backend "b" { set_response_headers = { x = backend_response.json_body } }`, BufferResponse | JSONParseResponse}, {"buffer request/response", `endpoint "/" { set_response_headers = { x = request From cda90f8151a168ac910d847dc0dff64e6a35d681 Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Fri, 31 Mar 2023 13:50:14 +0200 Subject: [PATCH 07/12] test for effect of buffer options on buffering/json-parsing backend requests/responses --- eval/context_test.go | 180 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/eval/context_test.go b/eval/context_test.go index e03749e6e..ea17e96b0 100644 --- a/eval/context_test.go +++ b/eval/context_test.go @@ -3,12 +3,15 @@ package eval_test import ( "bytes" "context" + // "fmt" "io" "net/http" "net/http/httptest" "reflect" + "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/hcl/v2/hclsimple" "github.com/zclconf/go-cty/cty" @@ -120,6 +123,183 @@ func TestNewHTTPContext(t *testing.T) { } } +func TestContext_buffer_parseJSON(t *testing.T) { + baseCtx := eval.NewDefaultContext() + tests := []struct { + name string + bufferOptions eval.BufferOption + contentType string + expBereqsDefBody interface{} + expBereqsDefJsonBody interface{} + expBereqBody interface{} + expBereqJsonBody interface{} + expBerespsDefBody interface{} + expBerespsDefJsonBody interface{} + expBerespBody interface{} + expBerespJsonBody interface{} + }{ + { + "buffer both, json-parse both, application/json", + eval.BufferRequest | eval.JSONParseRequest | eval.BufferResponse | eval.JSONParseResponse, + "application/json", + `{"a":"1"}`, + map[string]interface{}{"a": "1"}, + `{"a":"1"}`, + map[string]interface{}{"a": "1"}, + `{"b":"2"}`, + map[string]interface{}{"b": "2"}, + `{"b":"2"}`, + map[string]interface{}{"b": "2"}, + }, + { + "buffer both, json-parse both, application/foo+json", + eval.BufferRequest | eval.JSONParseRequest | eval.BufferResponse | eval.JSONParseResponse, + "application/foo+json", + `{"a":"1"}`, + map[string]interface{}{"a": "1"}, + `{"a":"1"}`, + map[string]interface{}{"a": "1"}, + `{"b":"2"}`, + map[string]interface{}{"b": "2"}, + `{"b":"2"}`, + map[string]interface{}{"b": "2"}, + }, + { + "buffer req, json-parse req", + eval.BufferRequest | eval.JSONParseRequest, + "application/json", + `{"a":"1"}`, + map[string]interface{}{"a": "1"}, + `{"a":"1"}`, + map[string]interface{}{"a": "1"}, + nil, + nil, + nil, + nil, + }, + { + "buffer resp, json-parse resp", + eval.BufferResponse | eval.JSONParseResponse, + "application/json", + nil, + nil, + nil, + nil, + `{"b":"2"}`, + map[string]interface{}{"b": "2"}, + `{"b":"2"}`, + map[string]interface{}{"b": "2"}, + }, + { + "buffer both, don't json-parse", + eval.BufferRequest | eval.BufferResponse, + "application/json", + `{"a":"1"}`, + map[string]interface{}{}, + `{"a":"1"}`, + map[string]interface{}{}, + `{"b":"2"}`, + map[string]interface{}{}, + `{"b":"2"}`, + map[string]interface{}{}, + }, + { + "don't buffer, don't json-parse", + eval.BufferNone, + "application/json", + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + }, + { + "don't buffer, json-parse both", + eval.BufferNone, + "application/json", + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + }, + { + "buffer both, json-parse both, text/plain", + eval.BufferRequest | eval.JSONParseRequest | eval.BufferResponse | eval.JSONParseResponse, + "text/plain", + `{"a":"1"}`, + map[string]interface{}{}, + `{"a":"1"}`, + map[string]interface{}{}, + `{"b":"2"}`, + map[string]interface{}{}, + `{"b":"2"}`, + map[string]interface{}{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(subT *testing.T) { + helper := test.New(subT) + + req := httptest.NewRequest(http.MethodPost, "/test", io.NopCloser(strings.NewReader(`{"a":"1"}`))) + req.Header.Set("Content-Type", tt.contentType) + helper.Must(eval.SetGetBody(req, eval.BufferRequest, 512)) + + ctx := context.WithValue(req.Context(), request.BufferOptions, tt.bufferOptions) + resp := &http.Response{ + Body: io.NopCloser(strings.NewReader(`{"b":"2"}`)), + Header: http.Header{ + "Content-Type": []string{tt.contentType}, + }, + Request: req.WithContext(ctx), + } + + hclContext := baseCtx.WithBeresp(resp, cty.NilVal, true).HCLContext() + + beRequests := seetie.ValueToMap(hclContext.Variables[eval.BackendRequests]) + defaultRequest := beRequests["default"].(map[string]interface{}) + if defaultRequest["body"] != tt.expBereqsDefBody { + subT.Errorf("backend_requests.default.body expected: %#v, got: %#v", tt.expBereqsDefBody, defaultRequest["body"]) + } + if diff := cmp.Diff(defaultRequest["json_body"], tt.expBereqsDefJsonBody); diff != "" { + subT.Errorf("backend_requests.default.json_body expected: %#v, got: %#v", tt.expBereqsDefJsonBody, defaultRequest["json_body"]) + } + + beRequest := seetie.ValueToMap(hclContext.Variables[eval.BackendRequest]) + if beRequest["body"] != tt.expBereqBody { + subT.Errorf("backend_request.body expected: %#v, got: %#v", tt.expBereqBody, beRequest["body"]) + } + if diff := cmp.Diff(beRequest["json_body"], tt.expBereqJsonBody); diff != "" { + subT.Errorf("backend_request.json_body expected: %#v, got: %#v", tt.expBereqJsonBody, beRequest["json_body"]) + } + + beResponses := seetie.ValueToMap(hclContext.Variables[eval.BackendResponses]) + defaultResponse := beResponses["default"].(map[string]interface{}) + if defaultResponse["body"] != tt.expBerespsDefBody { + subT.Errorf("backend_responses.default.body expected: %#v, got: %#v", tt.expBerespsDefBody, defaultResponse["body"]) + } + if diff := cmp.Diff(defaultResponse["json_body"], tt.expBerespsDefJsonBody); diff != "" { + subT.Errorf("backend_responses.default.json_body expected: %#v, got: %#v", tt.expBerespsDefJsonBody, defaultResponse["json_body"]) + } + + beResponse := seetie.ValueToMap(hclContext.Variables[eval.BackendResponse]) + if beResponse["body"] != tt.expBerespBody { + subT.Errorf("backend_response.body expected: %#v, got: %#v", tt.expBerespBody, beResponse["body"]) + } + if diff := cmp.Diff(beResponse["json_body"], tt.expBerespJsonBody); diff != "" { + subT.Errorf("backend_response.json_body expected: %#v, got: %#v", tt.expBerespJsonBody, beResponse["json_body"]) + } + }) + } +} + func TestDefaultEnvVariables(t *testing.T) { tests := []struct { name string From e9a651aea544e6cd114561d722ca87e8145d101b Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Fri, 31 Mar 2023 15:07:14 +0200 Subject: [PATCH 08/12] definie constants directly, without using iota --- eval/buffer.go | 14 +++++--------- eval/context_test.go | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/eval/buffer.go b/eval/buffer.go index 12a413fc5..3127f9d57 100644 --- a/eval/buffer.go +++ b/eval/buffer.go @@ -15,15 +15,11 @@ import ( type BufferOption uint8 const ( - BufferNone BufferOption = iota - BufferRequest - BufferResponse - _ - JSONParseRequest - _ - _ - _ - JSONParseResponse + BufferNone BufferOption = 0 + BufferRequest BufferOption = 1 + BufferResponse BufferOption = 2 + JSONParseRequest BufferOption = 4 + JSONParseResponse BufferOption = 8 ) func (i BufferOption) GoString() string { diff --git a/eval/context_test.go b/eval/context_test.go index ea17e96b0..524061340 100644 --- a/eval/context_test.go +++ b/eval/context_test.go @@ -3,7 +3,6 @@ package eval_test import ( "bytes" "context" - // "fmt" "io" "net/http" "net/http/httptest" From 3a117ca6b41825a38197398f001a6a3260c34eb5 Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Fri, 31 Mar 2023 15:21:18 +0200 Subject: [PATCH 09/12] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1c384be..78a79c150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Unreleased changes are available as `avenga/couper:edge` container. * **Changed** * More specific error log messages for [`oauth2`](https://docs.couper.io/configuration/block/oauth2) and [`beta_token_request`](https://docs.couper.io/configuration/block/token_request) token request errors ([#755](https://github.com/avenga/couper/pull/755)) + * In addition to having an appropriate JSON media type in the `Content-Type` header field, (backend) requests or backend responses for an endpoint are only JSON-parsed if indicated by a [`.json_body` reference](https://docs.couper.io/configuration/variables) in the endpoint configuration ([#749](https://github.com/avenga/couper/pull/749)) * **Fixed** * Erroneously sending an empty [`Server-Timing` header](https://docs.couper.io/configuration/command-line#oberservation-options) ([#700](https://github.com/avenga/couper/pull/700)) From 1817311348a38915b2dce767aef5a2ba7cb3999a Mon Sep 17 00:00:00 2001 From: Marcel Ludwig <1841067+malud@users.noreply.github.com> Date: Tue, 16 May 2023 09:24:02 +0200 Subject: [PATCH 10/12] use methods for buffer options check --- eval/buffer.go | 20 ++++++++++++++++---- eval/context.go | 13 +++++++------ eval/http.go | 2 +- handler/transport/backend.go | 2 +- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/eval/buffer.go b/eval/buffer.go index 3127f9d57..a118a5bed 100644 --- a/eval/buffer.go +++ b/eval/buffer.go @@ -22,6 +22,22 @@ const ( JSONParseResponse BufferOption = 8 ) +func (i BufferOption) Request() bool { + return i&BufferRequest == BufferRequest +} + +func (i BufferOption) JSONRequest() bool { + return i&JSONParseRequest == JSONParseRequest +} + +func (i BufferOption) Response() bool { + return i&BufferResponse == BufferResponse +} + +func (i BufferOption) JSONResponse() bool { + return i&JSONParseResponse == JSONParseResponse +} + func (i BufferOption) GoString() string { var result []string for _, o := range []BufferOption{BufferRequest, BufferResponse, JSONParseRequest, JSONParseResponse} { @@ -35,10 +51,6 @@ func (i BufferOption) GoString() string { return strings.Join(result, "|") } -func (i BufferOption) Response() bool { - return i&BufferResponse == BufferResponse -} - // MustBuffer determines if any of the hcl.bodies makes use of 'body', 'form_body' or 'json_body' or // of known attributes and variables which require a parsed client-request or backend-response body. func MustBuffer(bodies ...hcl.Body) BufferOption { diff --git a/eval/context.go b/eval/context.go index e0a07ef63..411e26f4a 100644 --- a/eval/context.go +++ b/eval/context.go @@ -256,11 +256,12 @@ func newBerespValues(ctx context.Context, readBody bool, beresp *http.Response) port, _ := strconv.ParseInt(p, 10, 64) bufferOption, bOk := bereq.Context().Value(request.BufferOptions).(BufferOption) - var body, jsonBody cty.Value - if bOk && (bufferOption&BufferRequest) == BufferRequest { - body, jsonBody = parseReqBody(bereq, (bufferOption&JSONParseRequest) == JSONParseRequest) + var body, jsonBody cty.Value + if bOk && bufferOption.Request() { + body, jsonBody = parseReqBody(bereq, bufferOption.JSONRequest()) } + bereqVal = cty.ObjectVal(ContextMap{ Method: cty.StringVal(bereq.Method), URL: cty.StringVal(bereq.URL.String()), @@ -277,10 +278,10 @@ func newBerespValues(ctx context.Context, readBody bool, beresp *http.Response) var respBody, respJSONBody cty.Value if readBody && !IsUpgradeResponse(bereq, beresp) { - if bOk && (bufferOption&BufferResponse) == BufferResponse { - respBody, respJSONBody = parseRespBody(beresp, (bufferOption&JSONParseResponse) == JSONParseResponse) + if bOk && bufferOption.Response() { + respBody, respJSONBody = parseRespBody(beresp, bufferOption.JSONResponse()) } - } else if bOk && (bufferOption&BufferResponse) != BufferResponse { + } else if bOk && !bufferOption.Response() { hasBlock, _ := bereq.Context().Value(request.ResponseBlock).(bool) ws, _ := bereq.Context().Value(request.WebsocketsAllowed).(bool) if name != "default" || (name == "default" && hasBlock) { diff --git a/eval/http.go b/eval/http.go index be05a59de..840ed1e09 100644 --- a/eval/http.go +++ b/eval/http.go @@ -53,7 +53,7 @@ func SetGetBody(req *http.Request, bufferOpts BufferOption, bodyLimit int64) err return nil } - if (bufferOpts & BufferRequest) != BufferRequest { + if !bufferOpts.Request() { return nil } diff --git a/handler/transport/backend.go b/handler/transport/backend.go index ef5f22ad6..cabb449ce 100644 --- a/handler/transport/backend.go +++ b/handler/transport/backend.go @@ -247,7 +247,7 @@ func (b *Backend) RoundTrip(req *http.Request) (*http.Response, error) { // from this result. evalCtx := eval.ContextFromRequest(req) // has own body variable reference? - readBody := eval.MustBuffer(b.context)&eval.BufferResponse == eval.BufferResponse + readBody := eval.MustBuffer(b.context).Response() evalCtx = evalCtx.WithBeresp(beresp, backendVal, readBody) clfValue, err := eval.EvalCustomLogFields(evalCtx.HCLContext(), ctxBody) From 481ba008c86d7048da3598ca261f90a3148adba2 Mon Sep 17 00:00:00 2001 From: Marcel Ludwig <1841067+malud@users.noreply.github.com> Date: Tue, 16 May 2023 10:01:34 +0200 Subject: [PATCH 11/12] early bufferOpts readout --- eval/context.go | 10 +++++++--- eval/context_test.go | 20 ++++++++++++++----- handler/endpoint_test.go | 12 +++++++---- server/http.go | 12 ++++++----- .../integration/endpoint_eval/15_couper.hcl | 3 +++ .../integration/endpoint_eval/19_couper.hcl | 3 +++ 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/eval/context.go b/eval/context.go index 411e26f4a..6f2e04215 100644 --- a/eval/context.go +++ b/eval/context.go @@ -161,8 +161,12 @@ func (c *Context) WithClientRequest(req *http.Request) *Context { } } port, _ := strconv.ParseInt(p, 10, 64) - // TODO how to set request.BufferOptions appropriately before WithClientRequest() is called - body, jsonBody := parseReqBody(req, true) + + var parseJSON bool + if opts, ok := ctx.Value(request.BufferOptions).(BufferOption); ok { + parseJSON = opts.JSONRequest() + } + body, jsonBody := parseReqBody(req, parseJSON) origin := NewRawOrigin(req.URL) ctx.eval.Variables[ClientRequest] = cty.ObjectVal(ctxMap.Merge(ContextMap{ @@ -472,7 +476,7 @@ func mergeBackendVariables(etx *hcl.EvalContext, key string, cmap ContextMap) { const defaultMaxMemory = 32 << 20 // 32 MB // parseForm populates the request PostForm field. -// As Proxy we should not consume the request body. +// As Proxy, we should not consume the request body. // Rewind body via GetBody method. func parseForm(r *http.Request) *http.Request { if r.GetBody == nil || r.Form != nil { diff --git a/eval/context_test.go b/eval/context_test.go index 524061340..ccf71bee6 100644 --- a/eval/context_test.go +++ b/eval/context_test.go @@ -11,7 +11,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsimple" + "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/zclconf/go-cty/cty" "github.com/avenga/couper/config/configload" @@ -87,10 +89,18 @@ func TestNewHTTPContext(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(subT *testing.T) { + file, diags := hclsyntax.ParseConfig([]byte(tt.hcl), "test.hcl", hcl.InitialPos) + if diags.HasErrors() { + subT.Fatal(diags) + } + bufferOption := eval.MustBuffer(file.Body) + helper := test.New(subT) req := httptest.NewRequest(tt.reqMethod, "https://couper.io/"+tt.query, tt.body) - *req = *req.Clone(context.WithValue(req.Context(), request.Endpoint, "couper-proxy")) + ctx := context.WithValue(req.Context(), request.Endpoint, "couper-proxy") + ctx = context.WithValue(ctx, request.BufferOptions, bufferOption) + *req = *req.Clone(ctx) for k, v := range tt.reqHeader { req.Header[k] = v @@ -99,13 +109,13 @@ func TestNewHTTPContext(t *testing.T) { bereq := req.Clone(context.Background()) beresp := newBeresp(bereq) - helper.Must(eval.SetGetBody(req, eval.BufferRequest, 512)) + helper.Must(eval.SetGetBody(req, bufferOption, 512)) - ctx := baseCtx.WithClientRequest(req).WithBeresp(beresp, cty.NilVal, false).HCLContext() - ctx.Functions = nil // we are not interested in a functions test + hclCtx := baseCtx.WithClientRequest(req).WithBeresp(beresp, cty.NilVal, false).HCLContext() + hclCtx.Functions = nil // we are not interested in a functions test var resultMap map[string]cty.Value - _ = hclsimple.Decode(tt.name+".hcl", []byte(tt.hcl), ctx, &resultMap) + _ = hclsimple.Decode(tt.name+".hcl", []byte(tt.hcl), hclCtx, &resultMap) for k, v := range tt.want { cv, ok := resultMap[k] diff --git a/handler/endpoint_test.go b/handler/endpoint_test.go index 7e5b9fa55..346eff253 100644 --- a/handler/endpoint_test.go +++ b/handler/endpoint_test.go @@ -206,9 +206,9 @@ func TestEndpoint_RoundTripContext_Variables_json_body(t *testing.T) { for _, method := range tt.methods { t.Run(method+" "+tt.name, func(subT *testing.T) { helper := test.New(subT) - + iBody := helper.NewInlineContext(tt.inlineCtx) backend := transport.NewBackend( - helper.NewInlineContext(tt.inlineCtx), + iBody, &transport.Config{NoProxyFromEnv: true}, nil, logger) ep := handler.NewEndpoint(&handler.EndpointOptions{ @@ -226,9 +226,12 @@ func TestEndpoint_RoundTripContext_Variables_json_body(t *testing.T) { req := httptest.NewRequest(method, "/", body) tt.header.Set(req) + bufferOption := eval.MustBuffer(iBody) + // normally injected by server/http - helper.Must(eval.SetGetBody(req, eval.BufferRequest, 1024)) - *req = *req.WithContext(eval.NewDefaultContext().WithClientRequest(req)) + helper.Must(eval.SetGetBody(req, bufferOption, 1024)) + ctx := context.WithValue(req.Context(), request.BufferOptions, bufferOption) + *req = *req.WithContext(eval.NewDefaultContext().WithClientRequest(req.WithContext(ctx))) rec := httptest.NewRecorder() rw := writer.NewResponseWriter(rec, "") // crucial for working ep due to res.Write() @@ -347,6 +350,7 @@ func TestEndpoint_RoundTripContext_Null_Eval(t *testing.T) { } else { req.Header.Set("Content-Type", "application/json") } + req = req.WithContext(context.WithValue(context.Background(), request.BufferOptions, bufOpts)) req = req.WithContext(eval.NewDefaultContext().WithClientRequest(req)) rec := httptest.NewRecorder() diff --git a/server/http.go b/server/http.go index 042c3aa40..8208bb7f9 100644 --- a/server/http.go +++ b/server/http.go @@ -252,7 +252,8 @@ func (s *HTTPServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { ctx = context.WithValue(ctx, request.Handler, hs.String()) } - if err = s.setGetBody(h, req); err != nil { + bufferOption, err := s.setGetBody(h, req) + if err != nil { h = mux.opts.ServerOptions.ServerErrTpl.WithError(err) } @@ -283,20 +284,21 @@ func (s *HTTPServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } + ctx = context.WithValue(ctx, request.BufferOptions, bufferOption) // due to the middleware callee stack we have to update the 'req' value. *req = *req.WithContext(s.evalCtx.WithClientRequest(req.WithContext(ctx))) h.ServeHTTP(rw, req) } -func (s *HTTPServer) setGetBody(h http.Handler, req *http.Request) error { +func (s *HTTPServer) setGetBody(h http.Handler, req *http.Request) (opt eval.BufferOption, err error) { inner := getChildHandler(h) - var err error if limitHandler, ok := inner.(handler.BodyLimit); ok { - err = eval.SetGetBody(req, limitHandler.BufferOptions(), limitHandler.RequestLimit()) + opt = limitHandler.BufferOptions() + err = eval.SetGetBody(req, opt, limitHandler.RequestLimit()) } - return err + return opt, err } // getHost configures the host from the incoming request host based on diff --git a/server/testdata/integration/endpoint_eval/15_couper.hcl b/server/testdata/integration/endpoint_eval/15_couper.hcl index 6e58351bf..b1600997d 100644 --- a/server/testdata/integration/endpoint_eval/15_couper.hcl +++ b/server/testdata/integration/endpoint_eval/15_couper.hcl @@ -2,6 +2,9 @@ server "bodies" { endpoint "/req" { response { status = 200 + headers = { + x-json: request.json_body + } json_body = request } } diff --git a/server/testdata/integration/endpoint_eval/19_couper.hcl b/server/testdata/integration/endpoint_eval/19_couper.hcl index 8d349c937..c36086e1b 100644 --- a/server/testdata/integration/endpoint_eval/19_couper.hcl +++ b/server/testdata/integration/endpoint_eval/19_couper.hcl @@ -1,6 +1,9 @@ server "request" { endpoint "/**" { response { + headers = { + x-json = request.json_body + } json_body = request } } From ea7197ad28d1dadc4c88c8cd12f72e0c0eac3fcf Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Tue, 16 May 2023 16:42:38 +0200 Subject: [PATCH 12/12] add test case for non-json-parsed JSON body because there is no reference to request.json_body --- server/http_integration_test.go | 93 +++++++++++-------- .../integration/endpoint_eval/15_couper.hcl | 6 ++ 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/server/http_integration_test.go b/server/http_integration_test.go index 805c060d6..90c2e4064 100644 --- a/server/http_integration_test.go +++ b/server/http_integration_test.go @@ -2033,53 +2033,68 @@ func TestHTTPServer_Endpoint_Response_JSONBody_Evaluation(t *testing.T) { shutdown, _ := newCouper(confPath, test.New(t)) defer shutdown() - helper := test.New(t) + type testCase struct { + name string + path string + expJSONBody map[string]interface{} + } - req, err := http.NewRequest(http.MethodGet, "http://example.com:8080/req?foo=bar", strings.NewReader(`{"data": true}`)) - helper.Must(err) - req.Header.Set("User-Agent", "") - req.Header.Set("Content-Type", "application/json") + for _, tc := range []testCase{ + { + "json-parsed", "/req", map[string]interface{}{"data": true}, + }, + { + "not json-parsed", "/req2", map[string]interface{}{}, + }, + } { + t.Run(tc.name, func(subT *testing.T) { + helper := test.New(subT) - res, err := client.Do(req) - helper.Must(err) + req, err := http.NewRequest(http.MethodGet, "http://example.com:8080"+tc.path+"?foo=bar", strings.NewReader(`{"data": true}`)) + helper.Must(err) + req.Header.Set("User-Agent", "") + req.Header.Set("Content-Type", "application/json") - resBytes, err := io.ReadAll(res.Body) - helper.Must(err) + res, err := client.Do(req) + helper.Must(err) - _ = res.Body.Close() + resBytes, err := io.ReadAll(res.Body) + helper.Must(err) - type Expectation struct { - JSONBody map[string]interface{} `json:"json_body"` - Headers test.Header `json:"headers"` - Method string `json:"method"` - Query url.Values `json:"query"` - URL string `json:"url"` - } + _ = 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)) - } + type Expectation struct { + JSONBody map[string]interface{} `json:"json_body"` + Headers test.Header `json:"headers"` + Method string `json:"method"` + Query url.Values `json:"query"` + URL string `json:"url"` + } - delete(jsonResult.Headers, "couper-request-id") + var jsonResult Expectation + err = json.Unmarshal(resBytes, &jsonResult) + if err != nil { + t.Errorf("unmarshal json: %v: got:\n%s", err, string(resBytes)) + } - exp := Expectation{ - Method: http.MethodGet, - JSONBody: map[string]interface{}{ - "data": true, - }, - Headers: map[string]string{ - "content-length": "14", - "content-type": "application/json", - }, - Query: map[string][]string{ - "foo": {"bar"}, - }, - URL: "http://example.com:8080/req?foo=bar", - } - if !reflect.DeepEqual(jsonResult, exp) { - t.Errorf("\nwant:\t%#v\ngot:\t%#v\npayload: %s", exp, jsonResult, string(resBytes)) + delete(jsonResult.Headers, "couper-request-id") + + exp := Expectation{ + Method: http.MethodGet, + JSONBody: tc.expJSONBody, + Headers: map[string]string{ + "content-length": "14", + "content-type": "application/json", + }, + Query: map[string][]string{ + "foo": {"bar"}, + }, + URL: "http://example.com:8080" + tc.path + "?foo=bar", + } + if !reflect.DeepEqual(jsonResult, exp) { + t.Errorf("\nwant:\t%#v\ngot:\t%#v\npayload: %s", exp, jsonResult, string(resBytes)) + } + }) } } diff --git a/server/testdata/integration/endpoint_eval/15_couper.hcl b/server/testdata/integration/endpoint_eval/15_couper.hcl index b1600997d..404cf4422 100644 --- a/server/testdata/integration/endpoint_eval/15_couper.hcl +++ b/server/testdata/integration/endpoint_eval/15_couper.hcl @@ -8,4 +8,10 @@ server "bodies" { json_body = request } } + endpoint "/req2" { + response { + status = 200 + json_body = request + } + } }