Repository: avenga/couper
Pull request #278 (2021-07-28 13:36:29)

Properties of request and backend_requests.*
The `request` and `backend_requests.*` variables should have mostly the same properties. So `protocol`, `host`, `port`, `origin`, `body` and `json_body` should be added to `backend_requests.*`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1109a49..10e91f5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,10 +8,15 @@ Unreleased changes are available as `avenga/couper:edge` container.
   * [Run option](./docs/CLI.md#run-options) `-accept-forwarded-url` and [setting](./docs/REFERENCE.md#settings-block) `accept_forwarded_url` to accept `proto`, `host`, or `port` from `X-Forwarded-Proto`, `X-Forwarded-Host` or `X-Forwarded-Port` request headers, affecting new [client request variables](./docs/REFERENCE.md#request) `request.url`, `request.origin`, `request.protocol`, `request.host` and `request.port` ([#255](https://github.com/avenga/couper/pull/255))
   * [`couper.version` variable](docs/REFERENCE.md#couper) ([#274](https://github.com/avenga/couper/pull/274))
   * Default values for environment variable by means of `environment_variables` within `defaults` block. ([#271](https://github.com/avenga/couper/pull/271))
+  * `protocol`, `host`, `port`, `origin`, `body`, `json_body` to [`backend_requests`](./docs/REFERENCE.md#backend_requests) ([#278](https://github.com/avenga/couper/pull/278))
 
 * **Fixed**
   * No GZIP compression for small response bodies ([#186](https://github.com/avenga/couper/issues/186))
   * Missing error type for [request](docs/REFERENCE.md#request-block)/[response](docs/REFERENCE.md#response-block) body, json_body or form_body related HCL evaluation errors ([#276](https://github.com/avenga/couper/pull/276))
+  * [`request.url`](./docs/REFERENCE.md#request) and [`backend_requests.<label>.url`](./docs/REFERENCE.md#backend_requests) now contain a query string if present ([#278](https://github.com/avenga/couper/pull/278))
+  * [`backend_responses.<label>.status`](./docs/REFERENCE.md#backend_responses) is now integer ([#278](https://github.com/avenga/couper/pull/278))
+  * [`backend_requests.<label>.form_body`](./docs/REFERENCE.md#backend_requests) was always empty ([#278](https://github.com/avenga/couper/pull/278))
+  * Documentation of [`request.query.<name>`](./docs/REFERENCE.md#request) ([#278](https://github.com/avenga/couper/pull/278))
 
 * [**Beta**](./docs/BETA.md)
   * OAuth2 Authorization Code Grant Flow: [`beta_oauth2 {}` block](./docs/REFERENCE.md#oauth2-ac-block-beta);  [`beta_oauth_authorization_url()`](./docs/REFERENCE.md#functions) and [`beta_oauth_verifier()`](./docs/REFERENCE.md#functions) ([#247](https://github.com/avenga/couper/pull/247))
diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md
index 1750baa5..dfabba43 100644
--- a/docs/REFERENCE.md
+++ b/docs/REFERENCE.md
@@ -490,9 +490,9 @@ Both durations can be configured via environment variable. Please refer to the [
 
 ### `couper`
 
-| Variable                         | Description                                                                                                                                                                                                                                                                         |
-| :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `version`                        | Couper's version number                                                                                                                                                                                                                                                             |
+| Variable                         | Type   | Description                                                                                                                                                                                                                                                                         | Example |
+| :------------------------------- | :----- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ |
+| `version`                        | string | Couper's version number                                                                                                                                                                                                                                                             | `version = "1.3.1"` |
 
 ### `env`
 
@@ -515,25 +515,24 @@ defaults {
 
 ### `request`
 
-| Variable                         | Description                                                                                                                                                                                                                                                                         |
-| :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `id`                             | Unique request id                                                                                                                                                                                                                                                                   |
-| `method`                         | HTTP method                                                                                                                                                                                                                                                                         |
-| `path`                           | URL path                                                                                                                                                                                                                                                                            |
-| `endpoint`                       | Matched endpoint pattern                                                                                                                                                                                                                                                            |
-| `headers.<name>`                 | HTTP request header value for requested lower-case key                                                                                                                                                                                                                              |
-| `cookies.<name>`                 | Value from `Cookie` request header for requested key (&#9888; last wins!)                                                                                                                                                                                                           |
-| `query.<name>`                   | Query parameter values (&#9888; last wins!)                                                                                                                                                                                                                                         |
-| `path_params.<name>`             | Value from a named path parameter defined within an endpoint path label                                                                                                                                                                                                             |
-| `body`                           | The request message body                                                                                                                                                                                                                                                            |
-| `form_body.<name>`               | Parameter in a `application/x-www-form-urlencoded` body                                                                                                                                                                                                                             |
-| `json_body.<name>`               | Access json decoded object properties. Media type must be `application/json` or `application/*+json`.                                                                                                                                                                               |
-| `context.<name>.<property_name>` | Request context containing information from the [Access Control](#access-control). |
-| `url`                            | Request URL |
-| `origin`                         | Origin of the request URL |
-| `protocol`                       | The request protocol (`http` or `https`) |
-| `host`                           | Host of the request URL |
-| `port`                           | Port of the request URL |
+| Variable                         | Type            | Description                                                                                                                                                                                                                                                                         | Example                                     |
+| :------------------------------- | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------ |
+| `id`                             | string          | Unique request id                                                                                                                                                                                                                                                                   |                                             |
+| `method`                         | string          | HTTP method                                                                                                                                                                                                                                                                         | `method = "GET"`                                       |
+| `headers.<name>`                 | string          | HTTP request header value for requested lower-case key                                                                                                                                                                                                                              |                                             |
+| `cookies.<name>`                 | string          | Value from `Cookie` request header for requested key (&#9888; last wins!)                                                                                                                                                                                                           |                                             |
+| `query.<name>`                   | tuple of string | Query parameter values                                                                                                                                                                                                                                                              |                                             |
+| `path_params.<name>`             | string          | Value from a named path parameter defined within an endpoint path label                                                                                                                                                                                                             |                                             |
+| `body`                           | string          | Request message body                                                                                                                                                                                                                                                                |                                             |
+| `form_body.<name>`               | tuple of string | Parameter in a `application/x-www-form-urlencoded` body                                                                                                                                                                                                                             |                                             |
+| `json_body.<name>`               | various         | Access json decoded object properties. Media type must be `application/json` or `application/*+json`.                                                                                                                                                                               |                                             |
+| `context.<name>.<property_name>` | various         | Request context containing information from the [Access Control](#access-control).                                                                                                                                                                                                  |                                             |
+| `url`                            | string          | Request URL                                                                                                                                                                                                                                                                         | `url = "https://www.example.com/path/to?q=val&a=1"` |
+| `origin`                         | string          | Origin of the request URL                                                                                                                                                                                                                                                           | `origin = "https://www.example.com"`                   |
+| `protocol`                       | string          | Request protocol                                                                                                                                                                                                                                                                    | `protocol = "https"`                                     |
+| `host`                           | string          | Host of the request URL                                                                                                                                                                                                                                                             | `host = "www.example.com"`                           |
+| `port`                           | integer         | Port of the request URL                                                                                                                                                                                                                                                             | `port = 443`                                       |
+| `path`                           | string          | Request URL path                                                                                                                                                                                                                                                                    | `oath = "/path/to"`                                  |
 
 The value of `context.<name>` depends on the type of block referenced by `<name>`.
 
@@ -565,17 +564,23 @@ To access a specific request use the related label. [Request](#request-block) an
 [Proxy](#proxy-block) blocks without a label will be available as `default`.
 To access the HTTP method of the `default` request use `backend_requests.default.method` .
 
-| Variable                         | Description                                                                                                                                                                                                                                                                         |
-| :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `id`                             | Unique request id                                                                                                                                                                                                                                                                   |
-| `method`                         | HTTP method                                                                                                                                                                                                                                                                         |
-| `path`                           | URL path                                                                                                                                                                                                                                                                            |
-| `headers.<name>`                 | HTTP request header value for requested lower-case key                                                                                                                                                                                                                              |
-| `cookies.<name>`                 | Value from `Cookie` request header for requested key (&#9888; last wins!)                                                                                                                                                                                                           |
-| `query.<name>`                   | Query parameter values (&#9888; last wins!)                                                                                                                                                                                                                                         |
-| `form_body.<name>`               | Parameter in a `application/x-www-form-urlencoded` body                                                                                                                                                                                                                             |
-| `context.<name>.<property_name>` | Request context containing claims from JWT used for [Access Control](#access-control) or information from a SAML assertion, `<name>` being the [JWT Block's](#jwt-block) or [SAML Block's](#saml-block) label and `property_name` being the claim's or assertion information's name |
-| `url`                            | Backend origin URL                                                                                                                                                                                                                                                                  |
+| Variable                         | Type            | Description                                                                                                                                                                                                                                                                          | Example                                     |
+| :------------------------------- | :-------------- |  :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------ |
+| `id`                             | string          | Unique request id                                                                                                                                                                                                                                                                    |                                             |
+| `method`                         | string          | HTTP method                                                                                                                                                                                                                                                                          | `method = "GET"`                                       |
+| `headers.<name>`                 | string          | HTTP request header value for requested lower-case key                                                                                                                                                                                                                               |                                             |
+| `cookies.<name>`                 | string          | Value from `Cookie` request header for requested key (&#9888; last wins!)                                                                                                                                                                                                            |                                             |
+| `query.<name>`                   | tuple of string | Query parameter values                                                                                                                                                                                                                                                               |                                             |
+| `body`                           | string          | Backend request message body                                                                                                                                                                                                                                                         |                                             |
+| `form_body.<name>`               | tuple of string | Parameter in a `application/x-www-form-urlencoded` body                                                                                                                                                                                                                              |                                             |
+| `json_body.<name>`               | various         | Access json decoded object properties. Media type must be `application/json` or `application/*+json`.                                                                                                                                                                                |                                             |
+| `context.<name>.<property_name>` | various         | Request context containing claims from JWT used for [Access Control](#access-control) or information from a SAML assertion, `<name>` being the [JWT Block's](#jwt-block) or [SAML Block's](#saml-block) label and `property_name` being the claim's or assertion information's name  |                                             |
+| `url`                            | string          | Backend request URL                                                                                                                                                                                                                                                                  | `url = "https://www.example.com/path/to?q=val&a=1"` |
+| `origin`                         | string          | Origin of the backend request URL                                                                                                                                                                                                                                                    | `origin = "https://www.example.com"`                   |
+| `protocol`                       | string          | Backend request protocol                                                                                                                                                                                                                                                             | `protocol = "https"`                                     |
+| `host`                           | string          | Host of the backend request URL                                                                                                                                                                                                                                                      | `host = "www.example.com"`                           |
+| `port`                           | integer         | Port of the backend request URL                                                                                                                                                                                                                                                      | `port = 443`                                       |
+| `path`                           | string          | Backend request URL path                                                                                                                                                                                                                                                             | `path = "/path/to"`                                  |
 
 ### `backend_responses`
 
@@ -584,32 +589,32 @@ Use the related label to access a specific response.
 [Request](#request-block) and [Proxy](#proxy-block) blocks without a label will be available as `default`.
 To access the HTTP status code of the `default` response use `backend_responses.default.status` .
 
-| Variable           | Description                                                                                           |
-| :----------------- | :---------------------------------------------------------------------------------------------------- |
-| `status`           | HTTP status code                                                                                      |
-| `headers.<name>`   | HTTP response header value for requested lower-case key                                               |
-| `cookies.<name>`   | Value from `Set-Cookie` response header for requested key (&#9888; last wins!)                        |
-| `body`             | The response message body                                                                             |
-| `json_body.<name>` | Access json decoded object properties. Media type must be `application/json` or `application/*+json`. |
+| Variable           | Type    | Description                                                                                           | Example |
+| :----------------- | :------ | :---------------------------------------------------------------------------------------------------- | :------ |
+| `status`           | integer | HTTP status code                                                                                      | `status = "200"` |
+| `headers.<name>`   | string  | HTTP response header value for requested lower-case key                                               | |
+| `cookies.<name>`   | string  | Value from `Set-Cookie` response header for requested key (&#9888; last wins!)                        | |
+| `body`             | string  | The response message body                                                                             | |
+| `json_body.<name>` | various | Access json decoded object properties. Media type must be `application/json` or `application/*+json`. | |
 
 ## Functions
 
-| Name            | Description                                                                                                                                                                                                                                                                                          |
-| :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `base64_decode` | Decodes Base64 data, as specified in RFC 4648.                                                                                                                                                                                                                                                       |
-| `base64_encode` | Encodes Base64 data, as specified in RFC 4648.                                                                                                                                                                                                                                                       |
-| `coalesce`      | Returns the first of the given arguments that is not null.                                                                                                                                                                                                                                           |
-| `json_decode`   | Parses the given JSON string and, if it is valid, returns the value it represents.                                                                                                                                                                                                                   |
-| `json_encode`   | Returns a JSON serialization of the given value.                                                                                                                                                                                                                                                     |
-| `jwt_sign`      | jwt_sign creates and signs a JSON Web Token (JWT) from information from a referenced [JWT Signing Profile Block](#jwt-signing-profile-block) and additional claims provided as a function parameter.                                                                                                 |
-| `merge`         | Deep-merges two or more of either objects or tuples. `null` arguments are ignored. A `null` attribute value in an object removes the previous attribute value. An attribute value with a different type than the current value is set as the new value. `merge()` with no parameters returns `null`. |
-| `beta_oauth_authorization_url` | Creates an OAuth2 authorization URL from a referenced [OAuth2 AC Block](#oauth2-ac-block-beta).                                                                                                                                                                                            |
-| `beta_oauth_verifier`  | Creates a cryptographically random key as specified in RFC 7636, applicable for all verifier methods; e.g. to be set as a cookie and read into `verifier_value`. Multiple calls of this function in the same client request context return the same value.                                                          |
-| `saml_sso_url`  | Creates a SAML SingleSignOn URL (including the `SAMLRequest` parameter) from a referenced [SAML Block](#saml-block).                                                                                                                                                                                 |
-| `to_lower`      | Converts a given string to lowercase.                                                                                                                                                                                                                                                                |
-| `to_upper`      | Converts a given string to uppercase.                                                                                                                                                                                                                                                                |
-| `unixtime`      | Retrieves the current UNIX timestamp in seconds.                                                                                                                                                                                                                                                     |
-| `url_encode`    | URL-encodes a given string according to RFC 3986.                                                                                                                                                                                                                                                    |
+| Name                           | Type            | Description                                                                                                                                                                                                                                                                                          | Arguments                           | Example                                              |
+| :----------------------------- | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------- | :--------------------------------------------------- |
+| `base64_decode`                | string          | Decodes Base64 data, as specified in RFC 4648.                                                                                                                                                                                                                                                       | `encoded` (string)                  | `base64_decode("Zm9v")`                              |
+| `base64_encode`                | string          | Encodes Base64 data, as specified in RFC 4648.                                                                                                                                                                                                                                                       | `decoded` (string)                  | `base64_encode("foo")`                               |
+| `coalesce`                     |                 | Returns the first of the given arguments that is not null.                                                                                                                                                                                                                                           | `arg...` (various)                  | `coalesce(request.cookies.foo, "bar")`               |
+| `json_decode`                  | various         | Parses the given JSON string and, if it is valid, returns the value it represents.                                                                                                                                                                                                                   | `encoded` (string)                  | `json_decode("{\"foo\": 1}")`                        |
+| `json_encode`                  | string          | Returns a JSON serialization of the given value.                                                                                                                                                                                                                                                     | `val` (various)                     | `json_encode(request.context.myJWT)`                 |
+| `jwt_sign`                     | string          | jwt_sign creates and signs a JSON Web Token (JWT) from information from a referenced [JWT Signing Profile Block](#jwt-signing-profile-block) and additional claims provided as a function parameter.                                                                                                 | `label` (string), `claims` (object) | `jwt_sign("myJWT")`                                  |
+| `merge`                        | object or tuple | Deep-merges two or more of either objects or tuples. `null` arguments are ignored. A `null` attribute value in an object removes the previous attribute value. An attribute value with a different type than the current value is set as the new value. `merge()` with no parameters returns `null`. | `arg...` (object or tuple)          | `merge(request.headers, { x-additional = "myval" })` |
+| `beta_oauth_authorization_url` | string          | Creates an OAuth2 authorization URL from a referenced [OAuth2 AC Block](#oauth2-ac-block-beta) or [OIDC Block](#oidc-block-beta).                                                                                                                                                                                                      | `label` (string)                    | `beta_oauth_authorization_url("myOAuth2")`           |
+| `beta_oauth_verifier`          | string          | Creates a cryptographically random key as specified in RFC 7636, applicable for all verifier methods; e.g. to be set as a cookie and read into `verifier_value`. Multiple calls of this function in the same client request context return the same value.                                           |                                     | `beta_oauth_verifier()`                         |
+| `saml_sso_url`                 | string          | Creates a SAML SingleSignOn URL (including the `SAMLRequest` parameter) from a referenced [SAML Block](#saml-block).                                                                                                                                                                                 | `label` (string)                    | `saml_sso_url("mySAML")`                             |
+| `to_lower`                     | string          | Converts a given string to lowercase.                                                                                                                                                                                                                                                                | `s` (string)                        | `to_lower(request.cookies.name)`                     |
+| `to_upper`                     | string          | Converts a given string to uppercase.                                                                                                                                                                                                                                                                | `s` (string)                        | `to_upper("CamelCase")`                              |
+| `unixtime`                     | integer         | Retrieves the current UNIX timestamp in seconds.                                                                                                                                                                                                                                                     |                                     | `unixtime()`                                         |
+| `url_encode`                   | string          | URL-encodes a given string according to RFC 3986.                                                                                                                                                                                                                                                    | `s` (string)                        | `url_encode("abc%&,123")`                            |
 
 ## Modifiers
 
diff --git a/eval/context.go b/eval/context.go
index 831a1f91..a08f85ce 100644
--- a/eval/context.go
+++ b/eval/context.go
@@ -139,19 +139,19 @@ func (c *Context) WithClientRequest(req *http.Request) *Context {
 	port, _ := strconv.ParseInt(p, 10, 64)
 	body, jsonBody := parseReqBody(req)
 	ctx.eval.Variables[ClientRequest] = cty.ObjectVal(ctxMap.Merge(ContextMap{
-		FormBody:  seetie.ValuesMapToValue(parseForm(req).PostForm),
 		ID:        cty.StringVal(id),
-		Body:      body,
-		JsonBody:  jsonBody,
 		Method:    cty.StringVal(req.Method),
-		Path:      cty.StringVal(req.URL.Path),
 		PathParam: seetie.MapToValue(pathParams),
-		Query:     seetie.ValuesMapToValue(req.URL.Query()),
-		URL:       cty.StringVal(newRawURL(req.URL).String()),
+		URL:       cty.StringVal(req.URL.String()),
 		Origin:    cty.StringVal(newRawOrigin(req.URL).String()),
 		Protocol:  cty.StringVal(req.URL.Scheme),
 		Host:      cty.StringVal(req.URL.Hostname()),
 		Port:      cty.NumberIntVal(port),
+		Path:      cty.StringVal(req.URL.Path),
+		Query:     seetie.ValuesMapToValue(req.URL.Query()),
+		Body:      body,
+		JsonBody:  jsonBody,
+		FormBody:  seetie.ValuesMapToValue(parseForm(req).PostForm),
 	}.Merge(newVariable(ctx.inner, req.Cookies(), req.Header))))
 
 	updateFunctions(ctx)
@@ -183,22 +183,38 @@ func (c *Context) WithBeresps(beresps ...*http.Response) *Context {
 		if n, ok := bereq.Context().Value(request.RoundTripName).(string); ok {
 			name = n
 		}
+		p := bereq.URL.Port()
+		if p == "" {
+			if bereq.URL.Scheme == "https" {
+				p = "443"
+			} else {
+				p = "80"
+			}
+		}
+		port, _ := strconv.ParseInt(p, 10, 64)
+		body, jsonBody := parseReqBody(bereq)
 		bereqs[name] = cty.ObjectVal(ContextMap{
-			FormBody: seetie.ValuesMapToValue(parseForm(bereq).PostForm),
 			Method:   cty.StringVal(bereq.Method),
+			URL:      cty.StringVal(bereq.URL.String()),
+			Origin:   cty.StringVal(newRawOrigin(bereq.URL).String()),
+			Protocol: cty.StringVal(bereq.URL.Scheme),
+			Host:     cty.StringVal(bereq.URL.Hostname()),
+			Port:     cty.NumberIntVal(port),
 			Path:     cty.StringVal(bereq.URL.Path),
 			Query:    seetie.ValuesMapToValue(bereq.URL.Query()),
-			URL:      cty.StringVal(newRawURL(bereq.URL).String()),
+			Body:     body,
+			JsonBody: jsonBody,
+			FormBody: seetie.ValuesMapToValue(parseForm(bereq).PostForm),
 		}.Merge(newVariable(ctx.inner, bereq.Cookies(), bereq.Header)))
 
-		var body, jsonBody cty.Value
+		var respBody, respJsonBody cty.Value
 		if (ctx.bufferOption & BufferResponse) == BufferResponse {
-			body, jsonBody = parseRespBody(beresp)
+			respBody, respJsonBody = parseRespBody(beresp)
 		}
 		resps[name] = cty.ObjectVal(ContextMap{
-			HttpStatus: cty.StringVal(strconv.Itoa(beresp.StatusCode)),
-			JsonBody:   jsonBody,
-			Body:       body,
+			HttpStatus: cty.NumberIntVal(int64(beresp.StatusCode)),
+			JsonBody:   respJsonBody,
+			Body:       respBody,
 		}.Merge(newVariable(ctx.inner, beresp.Cookies(), beresp.Header)))
 	}
 
@@ -364,16 +380,11 @@ func parseJSONBytes(b []byte) cty.Value {
 	return val
 }
 
-func newRawURL(u *url.URL) *url.URL {
-	rawURL := *u
-	rawURL.RawQuery = ""
-	rawURL.Fragment = ""
-	return &rawURL
-}
-
 func newRawOrigin(u *url.URL) *url.URL {
-	rawOrigin := *newRawURL(u)
+	rawOrigin := *u
 	rawOrigin.Path = ""
+	rawOrigin.RawQuery = ""
+	rawOrigin.Fragment = ""
 	return &rawOrigin
 }
 
diff --git a/eval/http.go b/eval/http.go
index 11971c11..f8b93ef3 100644
--- a/eval/http.go
+++ b/eval/http.go
@@ -72,10 +72,6 @@ func SetGetBody(req *http.Request, bodyLimit int64) error {
 		// reset body initially, additional body reads which are not depending on http.Request
 		// internals like form parsing should just call GetBody() and use the returned reader.
 		SetBody(req, buf.Bytes())
-
-		// parsing form data now since they read/write request attributes which could be
-		// difficult with multiple routines later on.
-		parseForm(req)
 	}
 
 	return nil
@@ -96,6 +92,10 @@ func SetBody(req *http.Request, body []byte) {
 	cl := len(bodyBytes)
 	req.Header.Set("Content-Length", strconv.Itoa(cl))
 	req.ContentLength = int64(cl)
+
+	// parsing form data now since they read/write request attributes which could be
+	// difficult with multiple routines later on.
+	parseForm(req)
 }
 
 func ApplyRequestContext(ctx context.Context, body hcl.Body, req *http.Request) error {
diff --git a/handler/producer/request.go b/handler/producer/request.go
index e5ceefe0..1ec02d1e 100644
--- a/handler/producer/request.go
+++ b/handler/producer/request.go
@@ -90,12 +90,12 @@ func (r Requests) Produce(ctx context.Context, req *http.Request, results chan<-
 			continue
 		}
 
-		eval.SetBody(outreq, []byte(body))
-
 		if defaultContentType != "" {
 			outreq.Header.Set("Content-Type", defaultContentType)
 		}
 
+		eval.SetBody(outreq, []byte(body))
+
 		*outreq = *outreq.WithContext(outCtx)
 		err = eval.ApplyRequestContext(outCtx, or.Context, outreq)
 		if err != nil {


Test patch:
diff --git a/eval/lib/jwt_test.go b/eval/lib/jwt_test.go
index e191979e..c1a8e4a2 100644
--- a/eval/lib/jwt_test.go
+++ b/eval/lib/jwt_test.go
@@ -461,8 +461,9 @@ func TestJwtSignDynamic(t *testing.T) {
 			if resultClaims["x-status"] == nil {
 				t.Errorf("Expected x-status claim, got: %#v", body)
 			}
-			if resultClaims["x-status"] != "200" {
-				t.Errorf("Expected: %d, got: %s", http.StatusOK, resultClaims["x-status"])
+			status := int64(resultClaims["x-status"].(float64))
+			if status != 200 {
+				t.Errorf("Expected: %d, got: %d", http.StatusOK, status)
 			}
 		})
 	}
diff --git a/server/http_integration_test.go b/server/http_integration_test.go
index 7ad88177..ef5a9c36 100644
--- a/server/http_integration_test.go
+++ b/server/http_integration_test.go
@@ -1807,7 +1807,7 @@ func TestHTTPServer_Endpoint_Response_FormQuery_Evaluation(t *testing.T) {
 		Query: map[string][]string{
 			"foo": {"bar"},
 		},
-		Url: "http://example.com:8080/req",
+		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))
@@ -1861,7 +1861,7 @@ func TestHTTPServer_Endpoint_Response_JSONBody_Evaluation(t *testing.T) {
 		Query: map[string][]string{
 			"foo": {"bar"},
 		},
-		Url: "http://example.com:8080/req",
+		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))
@@ -1922,7 +1922,7 @@ func TestHTTPServer_Endpoint_Response_JSONBody_Array_Evaluation(t *testing.T) {
 		Query: map[string][]string{
 			"foo": {"bar"},
 		},
-		Url: "http://example.com:8080/req",
+		Url: "http://example.com:8080/req?foo=bar",
 	}
 
 	if fmt.Sprint(jsonResult) != fmt.Sprint(exp) {
@@ -2228,6 +2228,255 @@ func TestHTTPServer_XFH_AcceptingForwardedUrl(t *testing.T) {
 	}
 }
 
+func TestHTTPServer_backend_requests_variables(t *testing.T) {
+	client := newClient()
+
+	ResourceOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+		rw.WriteHeader(http.StatusNoContent)
+	}))
+	defer ResourceOrigin.Close()
+
+	confPath := path.Join("testdata/integration/endpoint_eval/18_couper.hcl")
+	shutdown, hook := newCouperWithTemplate(confPath, test.New(t), map[string]interface{}{"rsOrigin": ResourceOrigin.URL})
+	defer shutdown()
+
+	type expectation struct {
+		Method   string                 `json:"method"`
+		Protocol string                 `json:"protocol"`
+		Host     string                 `json:"host"`
+		Port     int64                  `json:"port"`
+		Path     string                 `json:"path"`
+		Query    map[string][]string    `json:"query"`
+		Origin   string                 `json:"origin"`
+		Url      string                 `json:"url"`
+		Body     string                 `json:"body"`
+		JsonBody map[string]interface{} `json:"json_body"`
+		FormBody map[string][]string    `json:"form_body"`
+	}
+
+	type testCase struct {
+		name   string
+		relUrl string
+		header http.Header
+		body   io.Reader
+		exp    expectation
+	}
+
+	helper := test.New(t)
+	resourceOrigin, err := url.Parse(ResourceOrigin.URL)
+	helper.Must(err)
+
+	port, _ := strconv.ParseInt(resourceOrigin.Port(), 10, 64)
+
+	for _, tc := range []testCase{
+		{
+			"body",
+			"/body",
+			http.Header{},
+			strings.NewReader(`abcd1234`),
+			expectation{
+				Method:   "POST",
+				Protocol: resourceOrigin.Scheme,
+				Host:     resourceOrigin.Hostname(),
+				Port:     port,
+				Path:     "/resource",
+				Query:    map[string][]string{"foo": []string{"bar"}},
+				Origin:   ResourceOrigin.URL,
+				Url:      ResourceOrigin.URL + "/resource?foo=bar",
+				Body:     "abcd1234",
+				JsonBody: map[string]interface{}{},
+				FormBody: map[string][]string{},
+			},
+		},
+		{
+			"json_body",
+			"/json_body",
+			http.Header{"Content-Type": []string{"application/json"}},
+			strings.NewReader(`{"s":"abcd1234"}`),
+			expectation{
+				Method:   "POST",
+				Protocol: resourceOrigin.Scheme,
+				Host:     resourceOrigin.Hostname(),
+				Port:     port,
+				Path:     "/resource",
+				Query:    map[string][]string{"foo": []string{"bar"}},
+				Origin:   ResourceOrigin.URL,
+				Url:      ResourceOrigin.URL + "/resource?foo=bar",
+				Body:     `{"s":"abcd1234"}`,
+				JsonBody: map[string]interface{}{"s": "abcd1234"},
+				FormBody: map[string][]string{},
+			},
+		},
+		{
+			"form_body",
+			"/form_body",
+			http.Header{"Content-Type": []string{"application/x-www-form-urlencoded"}},
+			strings.NewReader(`s=abcd1234`),
+			expectation{
+				Method:   "POST",
+				Protocol: resourceOrigin.Scheme,
+				Host:     resourceOrigin.Hostname(),
+				Port:     port,
+				Path:     "/resource",
+				Query:    map[string][]string{"foo": []string{"bar"}},
+				Origin:   ResourceOrigin.URL,
+				Url:      ResourceOrigin.URL + "/resource?foo=bar",
+				Body:     `s=abcd1234`,
+				JsonBody: map[string]interface{}{},
+				FormBody: map[string][]string{"s": []string{"abcd1234"}},
+			},
+		},
+	} {
+		t.Run(tc.name, func(subT *testing.T) {
+			h := test.New(subT)
+			hook.Reset()
+
+			req, err := http.NewRequest(http.MethodPost, "http://localhost:8080"+tc.relUrl, tc.body)
+			h.Must(err)
+
+			for k, v := range tc.header {
+				req.Header.Set(k, v[0])
+			}
+
+			res, err := client.Do(req)
+			h.Must(err)
+
+			resBytes, err := io.ReadAll(res.Body)
+			h.Must(err)
+
+			_ = res.Body.Close()
+
+			var jsonResult expectation
+			err = json.Unmarshal(resBytes, &jsonResult)
+			if err != nil {
+				t.Errorf("%s: unmarshal json: %v: got:\n%s", tc.name, err, string(resBytes))
+			}
+			if !reflect.DeepEqual(jsonResult, tc.exp) {
+				t.Errorf("%s\nwant:\t%#v\ngot:\t%#v\npayload: %s", tc.name, tc.exp, jsonResult, string(resBytes))
+			}
+		})
+	}
+}
+
+func TestHTTPServer_request_variables(t *testing.T) {
+	client := newClient()
+
+	confPath := path.Join("testdata/integration/endpoint_eval/19_couper.hcl")
+	shutdown, hook := newCouper(confPath, test.New(t))
+	defer shutdown()
+
+	type expectation struct {
+		Method   string                 `json:"method"`
+		Protocol string                 `json:"protocol"`
+		Host     string                 `json:"host"`
+		Port     int64                  `json:"port"`
+		Path     string                 `json:"path"`
+		Query    map[string][]string    `json:"query"`
+		Origin   string                 `json:"origin"`
+		Url      string                 `json:"url"`
+		Body     string                 `json:"body"`
+		JsonBody map[string]interface{} `json:"json_body"`
+		FormBody map[string][]string    `json:"form_body"`
+	}
+
+	type testCase struct {
+		name   string
+		relUrl string
+		header http.Header
+		body   io.Reader
+		exp    expectation
+	}
+
+	for _, tc := range []testCase{
+		{
+			"body",
+			"/body?foo=bar",
+			http.Header{},
+			strings.NewReader(`abcd1234`),
+			expectation{
+				Method:   "POST",
+				Protocol: "http",
+				Host:     "localhost",
+				Port:     8080,
+				Path:     "/body",
+				Query:    map[string][]string{"foo": []string{"bar"}},
+				Origin:   "http://localhost:8080",
+				Url:      "http://localhost:8080/body?foo=bar",
+				Body:     "abcd1234",
+				JsonBody: map[string]interface{}{},
+				FormBody: map[string][]string{},
+			},
+		},
+		{
+			"json_body",
+			"/json_body?foo=bar",
+			http.Header{"Content-Type": []string{"application/json"}},
+			strings.NewReader(`{"s":"abcd1234"}`),
+			expectation{
+				Method:   "POST",
+				Protocol: "http",
+				Host:     "localhost",
+				Port:     8080,
+				Path:     "/json_body",
+				Query:    map[string][]string{"foo": []string{"bar"}},
+				Origin:   "http://localhost:8080",
+				Url:      "http://localhost:8080/json_body?foo=bar",
+				Body:     `{"s":"abcd1234"}`,
+				JsonBody: map[string]interface{}{"s": "abcd1234"},
+				FormBody: map[string][]string{},
+			},
+		},
+		{
+			"form_body",
+			"/form_body?foo=bar",
+			http.Header{"Content-Type": []string{"application/x-www-form-urlencoded"}},
+			strings.NewReader(`s=abcd1234`),
+			expectation{
+				Method:   "POST",
+				Protocol: "http",
+				Host:     "localhost",
+				Port:     8080,
+				Path:     "/form_body",
+				Query:    map[string][]string{"foo": []string{"bar"}},
+				Origin:   "http://localhost:8080",
+				Url:      "http://localhost:8080/form_body?foo=bar",
+				Body:     `s=abcd1234`,
+				JsonBody: map[string]interface{}{},
+				FormBody: map[string][]string{"s": []string{"abcd1234"}},
+			},
+		},
+	} {
+		t.Run(tc.name, func(subT *testing.T) {
+			helper := test.New(subT)
+			hook.Reset()
+
+			req, err := http.NewRequest(http.MethodPost, "http://localhost:8080"+tc.relUrl, tc.body)
+			helper.Must(err)
+
+			for k, v := range tc.header {
+				req.Header.Set(k, v[0])
+			}
+
+			res, err := client.Do(req)
+			helper.Must(err)
+
+			resBytes, err := io.ReadAll(res.Body)
+			helper.Must(err)
+
+			_ = res.Body.Close()
+
+			var jsonResult expectation
+			err = json.Unmarshal(resBytes, &jsonResult)
+			if err != nil {
+				t.Errorf("%s: unmarshal json: %v: got:\n%s", tc.name, err, string(resBytes))
+			}
+			if !reflect.DeepEqual(jsonResult, tc.exp) {
+				t.Errorf("%s\nwant:\t%#v\ngot:\t%#v\npayload: %s", tc.name, tc.exp, jsonResult, string(resBytes))
+			}
+		})
+	}
+}
+
 func TestHTTPServer_Endpoint_Evaluation_Inheritance(t *testing.T) {
 	client := newClient()
 
diff --git a/server/testdata/integration/endpoint_eval/18_couper.hcl b/server/testdata/integration/endpoint_eval/18_couper.hcl
new file mode 100644
index 00000000..db3ff024
--- /dev/null
+++ b/server/testdata/integration/endpoint_eval/18_couper.hcl
@@ -0,0 +1,32 @@
+server "backend_requests" {
+  endpoint "/body" {
+    request {
+      method = "POST"
+      url    = "{{.rsOrigin}}/resource?foo=bar"
+      body   = request.body
+    }
+    response {
+      json_body = backend_requests.default
+    }
+  }
+  endpoint "/json_body" {
+    request {
+      method    = "POST"
+      url       = "{{.rsOrigin}}/resource?foo=bar"
+      json_body = request.json_body
+    }
+    response {
+      json_body = backend_requests.default
+    }
+  }
+  endpoint "/form_body" {
+    request {
+      method    = "POST"
+      url       = "{{.rsOrigin}}/resource?foo=bar"
+      form_body = request.form_body
+    }
+    response {
+      json_body = backend_requests.default
+    }
+  }
+}
diff --git a/server/testdata/integration/endpoint_eval/19_couper.hcl b/server/testdata/integration/endpoint_eval/19_couper.hcl
new file mode 100644
index 00000000..8d349c93
--- /dev/null
+++ b/server/testdata/integration/endpoint_eval/19_couper.hcl
@@ -0,0 +1,7 @@
+server "request" {
+  endpoint "/**" {
+    response {
+      json_body = request
+    }
+  }
+}