Skip to content

Commit

Permalink
Merge pull request #250 from avenga/set-response-status
Browse files Browse the repository at this point in the history
Add new set_response_status attribute
  • Loading branch information
Marcel Ludwig authored Jun 16, 2021
2 parents 0304046 + 6bf0601 commit 0d826d6
Show file tree
Hide file tree
Showing 10 changed files with 478 additions and 351 deletions.
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**
* Modifier (`set/add/remove_form_params`) for the form parameters ([#223](https://github.com/avenga/couper/pull/223))
* Modifier (`set_response_status`) to be able to modify the response HTTP status code ([#250](https://github.com/avenga/couper/pull/250))

* **Changed**
* Stronger configuration check for `path` and `path_prefix` attributes, possibly resulting in configuration errors ([#232](https://github.com/avenga/couper/pull/232))
Expand Down
11 changes: 6 additions & 5 deletions config/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ func (b Backend) Schema(inline bool) *hcl.BodySchema {

type Inline struct {
meta.Attributes
BasicAuth string `hcl:"basic_auth,optional"`
Hostname string `hcl:"hostname,optional"`
Origin string `hcl:"origin,optional"`
PathPrefix string `hcl:"path_prefix,optional"`
ProxyURL string `hcl:"proxy,optional"`
BasicAuth string `hcl:"basic_auth,optional"`
Hostname string `hcl:"hostname,optional"`
Origin string `hcl:"origin,optional"`
PathPrefix string `hcl:"path_prefix,optional"`
ProxyURL string `hcl:"proxy,optional"`
ResponseStatus *uint8 `hcl:"set_response_status,optional"`
}

schema, _ = gohcl.ImpliedBodySchema(&Inline{})
Expand Down
5 changes: 3 additions & 2 deletions config/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ func (e Endpoint) Schema(inline bool) *hcl.BodySchema {

type Inline struct {
meta.Attributes
Requests Requests `hcl:"request,block"`
Proxies Proxies `hcl:"proxy,block"`
Proxies Proxies `hcl:"proxy,block"`
Requests Requests `hcl:"request,block"`
ResponseStatus *uint8 `hcl:"set_response_status,optional"`
}
schema, _ := gohcl.ImpliedBodySchema(&Inline{})
return schema
Expand Down
5 changes: 3 additions & 2 deletions config/error_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ func (e ErrorHandler) Schema(inline bool) *hcl.BodySchema {

type Inline struct {
meta.Attributes
Proxies Proxies `hcl:"proxy,block"`
Requests Requests `hcl:"request,block"`
Proxies Proxies `hcl:"proxy,block"`
Requests Requests `hcl:"request,block"`
ResponseStatus *uint8 `hcl:"set_response_status,optional"`
}

schema, _ := gohcl.ImpliedBodySchema(&Inline{})
Expand Down
14 changes: 7 additions & 7 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,33 +102,33 @@ the gateway. There are a large number of options, but let's focus on the main st
```hcl
server "my_project" {
files {
...
# ...
}
spa {
...
# ...
}
api {
access_control = ["foo"]
endpoint "/bar" {
proxy {
backend {...}
backend { }
}
request "sub-request" {
backend {...}
backend { }
}
response {...}
response { }
}
}
}
definitions {
...
# ...
}
settings {
...
# ...
}
```

Expand Down
667 changes: 338 additions & 329 deletions docs/REFERENCE.md

Large diffs are not rendered by default.

37 changes: 36 additions & 1 deletion eval/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function/stdlib"

"github.com/avenga/couper/config"
"github.com/avenga/couper/config/meta"
"github.com/avenga/couper/config/request"
"github.com/avenga/couper/errors"
Expand Down Expand Up @@ -290,7 +291,41 @@ func ApplyResponseContext(ctx context.Context, body hcl.Body, beresp *http.Respo
return nil
}

return ApplyResponseHeaderOps(ctx, body, beresp.Header)
if err := ApplyResponseHeaderOps(ctx, body, beresp.Header); err != nil {
return err
}

content, _, _ := body.PartialContent(config.BackendInlineSchema)
if attr, ok := content.Attributes["set_response_status"]; ok {
var httpCtx *hcl.EvalContext
if c, ok := ctx.Value(ContextType).(*Context); ok {
httpCtx = c.eval
}

val, attrDiags := attr.Expr.Value(httpCtx)
if seetie.SetSeverityLevel(attrDiags).HasErrors() {
return attrDiags
}

status := seetie.ValueToInt(val)
if status < 100 || status > 599 {
return errors.Configuration.Label("set_response_status").Messagef("invalid http status code: %d", status)
}

if status == 204 {
beresp.Request.Context().
Value(request.LogEntry).(*logrus.Entry).
Warn("set_response_status: removing body, if any due to status-code 204")

beresp.Body = io.NopCloser(bytes.NewBuffer([]byte{}))
beresp.ContentLength = -1
beresp.Header.Del("Content-Length")
}

beresp.StatusCode = int(status)
}

return nil
}

func ApplyResponseHeaderOps(ctx context.Context, body hcl.Body, headers ...http.Header) error {
Expand Down
3 changes: 2 additions & 1 deletion handler/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ func (e *Endpoint) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

// always apply before write: redirect, response
if err = eval.ApplyResponseContext(evalContext, e.opts.Context, clientres); err != nil {
log.WithError(err).Error()
e.opts.Error.ServeError(err).ServeHTTP(rw, req)
return
}

select {
Expand Down
60 changes: 56 additions & 4 deletions server/modifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
func TestIntegration_ResponseHeaders(t *testing.T) {
const confFile = "testdata/integration/modifier/01_couper.hcl"

shutdown, _ := newCouper(confFile, test.New(t))
defer shutdown()

client := newClient()

type testCase struct {
Expand Down Expand Up @@ -46,10 +49,7 @@ func TestIntegration_ResponseHeaders(t *testing.T) {
},
} {
t.Run(tc.path, func(subT *testing.T) {
helper := test.New(t)

shutdown, _ := newCouper(confFile, helper)
defer shutdown()
helper := test.New(subT)

req, err := http.NewRequest(http.MethodGet, "http://example.com:8080"+tc.path, nil)
helper.Must(err)
Expand All @@ -65,3 +65,55 @@ func TestIntegration_ResponseHeaders(t *testing.T) {
})
}
}

func TestIntegration_SetResponseStatus(t *testing.T) {
const confFile = "testdata/integration/modifier/02_couper.hcl"

shutdown, hook := newCouper(confFile, test.New(t))
defer shutdown()

client := newClient()

type testCase struct {
path string
expMessage string
expStatus int
}

for _, tc := range []testCase{
{
path: "/204",
expMessage: "set_response_status: removing body, if any due to status-code 204",
expStatus: 204,
},
{
path: "/201",
expMessage: "",
expStatus: 201,
},
{
path: "/600",
expMessage: "configuration error: set_response_status: invalid http status code: 600",
expStatus: 500,
},
} {
t.Run(tc.path, func(subT *testing.T) {
helper := test.New(subT)

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

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

if res.StatusCode != tc.expStatus {
t.Errorf("Expected status code %d, given: %d", tc.expStatus, res.StatusCode)
}

if hook.Entries[0].Message != tc.expMessage {
t.Errorf("Unexpected message given: %s", hook.Entries[0].Message)
}
})
}
}
26 changes: 26 additions & 0 deletions server/testdata/integration/modifier/02_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
server "set-response-status" {
endpoint "/204" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything"
backend {
set_response_status = 204
}
}
}
endpoint "/201" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything"
backend {
set_response_status = 201
}
}
}
endpoint "/600" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything"
backend {
set_response_status = 600
}
}
}
}

0 comments on commit 0d826d6

Please sign in to comment.