Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend variables #430

Merged
merged 19 commits into from
Feb 17, 2022
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Unreleased changes are available as `avenga/couper:edge` container.

* **Added**
* `disable_private_caching` attribute for the [JWT Block](./docs/REFERENCE.md#jwt-block) ([#418](https://github.com/avenga/couper/pull/418))
* [`backend_request`](./docs/REFERENCE.md#backend_request) and [`backend_response`](./docs/REFERENCE.md#backend_response) variables ([#430](https://github.com/avenga/couper/pull/430))

* **Fixed**
* missing upstream log field value for `request.proto` ([#421](https://github.com/avenga/couper/pull/421))
Expand Down
16 changes: 16 additions & 0 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
- [couper](#couper)
- [env](#env)
- [request](#request)
- [backend_request](#backend_request)
- [backend_requests](#backend_requests)
- [backend_response](#backend_response)
- [backend_responses](#backend_responses)
- [Functions](#functions)
- [Modifiers](#modifiers)
Expand Down Expand Up @@ -649,6 +651,13 @@ and for OIDC additionally:
- `id_token_claims`: a map of claims from the ID token
- `userinfo`: a map of claims retrieved from the userinfo endpoint

### `backend_request`

`backend_request` is a list of request variables like `backend_requests.<label>`
(see [backend_requests](#backend_requests) below). The `backend_request` is only
available in a [Backend Block](#backend-block) and represents the request of this
block.

### `backend_requests`

`backend_requests.<label>` is a list of all backend requests, and their variables.
alex-schneider marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -674,6 +683,13 @@ To access the HTTP method of the `default` request use `backend_requests.default
| `port` | integer | Port of the backend request URL | `443` |
| `path` | string | Backend request URL path | `/path/to` |

### `backend_response`

`backend_response` is a list of responses variables like `backend_responses.<label>`
(see [backend_responses](#backend_responses) below). The `backend_response` is only
available in a [Backend Block](#backend-block) and represents the response of this
block.

### `backend_responses`

`backend_responses.<label>` is a list of all backend responses, and their variables. Same behaviour as for `backend_requests`.
alex-schneider marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
10 changes: 5 additions & 5 deletions eval/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func (c *Context) WithClientRequest(req *http.Request) *Context {
return ctx
}

func (c *Context) WithBeresps(beresps ...*http.Response) *Context {
func (c *Context) WithBeresps(beresp *http.Response) *Context {
alex-schneider marked this conversation as resolved.
Show resolved Hide resolved
ctx := &Context{
eval: c.cloneEvalContext(),
inner: c.inner,
Expand All @@ -180,14 +180,14 @@ func (c *Context) WithBeresps(beresps ...*http.Response) *Context {

resps := make(ContextMap)
bereqs := make(ContextMap)
for _, beresp := range beresps {
if beresp == nil {
continue
}

if beresp != nil {
name, bereqVal, berespVal := newBerespValues(ctx, false, beresp)
bereqs[name] = bereqVal
resps[name] = berespVal

ctx.eval.Variables[BackendRequest] = bereqVal
ctx.eval.Variables[BackendResponse] = berespVal
}

// Prevent overriding existing variables with successive calls to this method.
Expand Down
3 changes: 2 additions & 1 deletion eval/sync.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package eval

import (
"github.com/zclconf/go-cty/cty"
"net/http"
"sync"

"github.com/zclconf/go-cty/cty"
)

type SyncedVariables struct {
Expand Down
2 changes: 2 additions & 0 deletions eval/variables.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package eval

const (
BackendRequest = "backend_request"
BackendRequests = "backend_requests"
BackendResponse = "backend_response"
BackendResponses = "backend_responses"
BackendDefault = "default"
Body = "body"
Expand Down
24 changes: 23 additions & 1 deletion logging/hooks/custom_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,29 @@ func fire(entry *logrus.Entry, bodyKey request.ContextKey) {
return
}

if fields := eval.ApplyCustomLogs(evalCtx.HCLContextSync(), hclBodies, entry); len(fields) > 0 {
ctx := evalCtx.HCLContextSync()

if request.LogCustomUpstream == bodyKey {
if _, ok := ctx.Variables[eval.BackendRequests]; ok {
for k, v := range ctx.Variables[eval.BackendRequests].AsValueMap() {
if k == entry.Context.Value(request.RoundTripName) {
ctx.Variables[eval.BackendRequest] = v
break
}
}
}

if _, ok := ctx.Variables[eval.BackendResponses]; ok {
for k, v := range ctx.Variables[eval.BackendResponses].AsValueMap() {
if k == entry.Context.Value(request.RoundTripName) {
ctx.Variables[eval.BackendResponse] = v
break
}
}
}
}

if fields := eval.ApplyCustomLogs(ctx, hclBodies, entry); len(fields) > 0 {
entry.Data[customLogField] = fields
}
}
61 changes: 61 additions & 0 deletions server/http_endpoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/sirupsen/logrus"

"github.com/avenga/couper/config/configload"
"github.com/avenga/couper/internal/test"
Expand All @@ -27,6 +28,66 @@ import (

const testdataPath = "testdata/endpoints"

func TestBackend_BackendVariable(t *testing.T) {
alex-schneider marked this conversation as resolved.
Show resolved Hide resolved
client := newClient()
helper := test.New(t)

shutdown, hook := newCouper("testdata/integration/backend/01_couper.hcl", helper)
defer shutdown()

req, err := http.NewRequest(http.MethodGet, "http://example.com:8080/", nil)
helper.Must(err)

req.Header.Set("Cookie", "Cookie")
req.Header.Set("User-Agent", "Couper")

hook.Reset()
_, err = client.Do(req)
helper.Must(err)

var check int

for _, entry := range hook.AllEntries() {
if entry.Data["type"] != "couper_backend" {
continue
}

name := entry.Data["request"].(logging.Fields)["name"]
data := entry.Data["custom"].(logrus.Fields)
// The Cookie request header is not proxied, so *-req is not set in log.

if name == "default" {
check++

if len(data) != 2 || data["default-res"] != "application/json" || data["default-ua"] != "Couper" {
t.Errorf("unexpected data given: %#v", data)
}
} else if name == "request" {
check++

if len(data) != 2 || data["request-res"] != "text/plain; charset=utf-8" || data["request-ua"] != "" {
t.Errorf("unexpected data given: %#v", data)
}
} else if name == "r1" {
check++

if len(data) != 2 || data["definitions-res"] != "text/plain; charset=utf-8" || data["definitions-ua"] != "" {
t.Errorf("unexpected data given: %#v", data)
}
} else if name == "r2" {
check++

if len(data) != 2 || data["definitions-res"] != "application/json" || data["definitions-ua"] != "" {
t.Errorf("unexpected data given: %#v", data)
}
}
}

if check != 4 {
t.Error("missing 4 backend logs")
}
}

func TestEndpoints_Protected404(t *testing.T) {
client := newClient()

Expand Down
47 changes: 47 additions & 0 deletions server/testdata/integration/backend/01_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
server {
endpoint "/" {
proxy "default" {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything"

backend {
custom_log_fields = {
default-res = backend_response.headers.content-type
default-req = backend_request.headers.cookie
default-ua = backend_request.headers.user-agent
}
}
}

request "request" {
url = "${env.COUPER_TEST_BACKEND_ADDR}/small"

backend {
custom_log_fields = {
request-res = backend_response.headers.content-type
request-req = backend_request.headers.cookie
request-ua = backend_request.headers.user-agent
}
}
}

request "r1" {
url = "${env.COUPER_TEST_BACKEND_ADDR}/small"
backend = "BE"
}

request "r2" {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything"
backend = "BE"
}
}
}

definitions {
backend "BE" {
custom_log_fields = {
definitions-res = backend_response.headers.content-type
definitions-req = backend_request.headers.cookie
definitions-ua = backend_request.headers.user-agent
}
}
}