From 5e01a34040f4cfa7d4e05c9aa2d3dc8fc875551d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Soul=C3=A9?= Date: Sun, 6 Sep 2020 18:07:46 +0200 Subject: [PATCH] reverseproxy: Add `handle_response` blocks to `reverse_proxy` (#3710) --- caddyconfig/httpcaddyfile/directives.go | 7 ++ .../reverse_proxy_handle_response.txt | 90 +++++++++++++++++++ modules/caddyhttp/reverseproxy/caddyfile.go | 53 ++++++++++- .../reverseproxy/fastcgi/caddyfile.go | 2 +- 4 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 caddytest/integration/caddyfile_adapt/reverse_proxy_handle_response.txt diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index 7f77f49f2bbc..646190ec5a48 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -264,6 +264,13 @@ func (h Helper) NewBindAddresses(addrs []string) []ConfigValue { return []ConfigValue{{Class: "bind", Value: addrs}} } +// WithDispenser returns a new instance based on d. All others Helper +// fields are copied, so typically maps are shared with this new instance. +func (h Helper) WithDispenser(d *caddyfile.Dispenser) Helper { + h.Dispenser = d + return h +} + // ParseSegmentAsSubroute parses the segment such that its subdirectives // are themselves treated as directives, from which a subroute is built // and returned. diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_handle_response.txt b/caddytest/integration/caddyfile_adapt/reverse_proxy_handle_response.txt new file mode 100644 index 000000000000..7fee0bdf9dec --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_handle_response.txt @@ -0,0 +1,90 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + handle_response header X-Accel-Redirect { + respond "Header!" + } + handle_response status 401 { + respond "Status!" + } + handle_response { + respond "Any!" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handle_response": [ + { + "match": { + "headers": { + "X-Accel-Redirect": [] + } + }, + "routes": [ + { + "handle": [ + { + "body": "Header!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "status_code": [ + 401 + ] + }, + "routes": [ + { + "handle": [ + { + "body": "Status!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": {}, + "routes": [ + { + "handle": [ + { + "body": "Any!", + "handler": "static_response" + } + ] + } + ] + } + ], + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go index 71ed21fa2c5b..55d4a57f7f4b 100644 --- a/modules/caddyhttp/reverseproxy/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/caddyfile.go @@ -38,14 +38,14 @@ func init() { func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { rp := new(Handler) - err := rp.UnmarshalCaddyfile(h.Dispenser) + err := rp.ParseCaddyfileReverseProxy(h) if err != nil { return nil, err } return rp, nil } -// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// ParseCaddyfileReverseProxy sets up the handler from Caddyfile tokens. Syntax: // // reverse_proxy [] [] { // # upstreams @@ -86,13 +86,20 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) // transport { // ... // } +// +// # handle responses +// handle_response [header |status ] { +// ... +// } // } // // Proxy upstream addresses should be network dial addresses such // as `host:port`, or a URL such as `scheme://host:port`. Scheme // and port may be inferred from other parts of the address/URL; if // either are missing, defaults to HTTP. -func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { +func (h *Handler) ParseCaddyfileReverseProxy(helper httpcaddyfile.Helper) error { + d := helper.Dispenser + // currently, all backends must use the same scheme/protocol (the // underlying JSON does not yet support per-backend transports) var commonScheme string @@ -597,6 +604,45 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } transport = rt + case "handle_response": + var rm caddyhttp.ResponseMatcher + args := d.RemainingArgs() + switch len(args) { + case 2: + switch args[0] { + case "header": + rm = caddyhttp.ResponseMatcher{ + Headers: http.Header{args[1]: []string{}}, + } + case "status": + statusNum, err := strconv.Atoi(args[1]) + if err != nil { + return d.Errf("bad status value '%s': %v", args[1], err) + } + rm = caddyhttp.ResponseMatcher{ + StatusCode: []int{statusNum}, + } + default: + return d.Err("handle_response only accepts header|status") + } + case 0: // Any status or header + default: + return d.ArgErr() + } + handler, err := httpcaddyfile.ParseSegmentAsSubroute(helper.WithDispenser(d.NewFromNextSegment())) + if err != nil { + return err + } + subroute, ok := handler.(*caddyhttp.Subroute) + if !ok { + return helper.Errf("segment was not parsed as a subroute") + } + h.HandleResponse = append(h.HandleResponse, + caddyhttp.ResponseHandler{ + Match: &rm, + Routes: subroute.Routes, + }) + default: return d.Errf("unrecognized subdirective %s", d.Val()) } @@ -874,6 +920,5 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // Interface guards var ( - _ caddyfile.Unmarshaler = (*Handler)(nil) _ caddyfile.Unmarshaler = (*HTTPTransport)(nil) ) diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go index 4d0b23b29d9a..486fdcf2d659 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go @@ -355,7 +355,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error // using the reverse_proxy directive syntax // TODO: this can overwrite our fcgiTransport that we encoded and // set on the rpHandler... even with a non-fastcgi transport! - err = rpHandler.UnmarshalCaddyfile(dispenser) + err = rpHandler.ParseCaddyfileReverseProxy(h.WithDispenser(dispenser)) if err != nil { return nil, err }