summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/responsematchers.go
diff options
context:
space:
mode:
authorFrancis Lavoie <lavofr@gmail.com>2021-05-02 14:39:06 -0400
committerGitHub <noreply@github.com>2021-05-02 12:39:06 -0600
commite4a22de9d1c4d7aa83126ee13e40b61e7b0e9df0 (patch)
tree82e3134b3c83d258fb08299e917cfa9b9d980ff7 /modules/caddyhttp/responsematchers.go
parente6f6d3a4765565b09f95a29a2e75be34e1d70359 (diff)
reverseproxy: Add `handle_response` blocks to `reverse_proxy` (#3710) (#4021)
* reverseproxy: Add `handle_response` blocks to `reverse_proxy` (#3710) * reverseproxy: complete handle_response test * reverseproxy: Change handle_response matchers to use named matchers reverseproxy: Add support for changing status code * fastcgi: Remove obsolete TODO We already have d.Err("transport already specified") in the reverse_proxy parsing code which covers this case * reverseproxy: Fix support for "4xx" type status codes * Apply suggestions from code review Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * caddyhttp: Reorganize response matchers * reverseproxy: Reintroduce caddyfile.Unmarshaler * reverseproxy: Add comment mentioning Finalize should be called Co-authored-by: Maxime Soulé <btik-git@scoubidou.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Diffstat (limited to 'modules/caddyhttp/responsematchers.go')
-rw-r--r--modules/caddyhttp/responsematchers.go122
1 files changed, 122 insertions, 0 deletions
diff --git a/modules/caddyhttp/responsematchers.go b/modules/caddyhttp/responsematchers.go
new file mode 100644
index 0000000..d9ad848
--- /dev/null
+++ b/modules/caddyhttp/responsematchers.go
@@ -0,0 +1,122 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package caddyhttp
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+)
+
+// ResponseMatcher is a type which can determine if an
+// HTTP response matches some criteria.
+type ResponseMatcher struct {
+ // If set, one of these status codes would be required.
+ // A one-digit status can be used to represent all codes
+ // in that class (e.g. 3 for all 3xx codes).
+ StatusCode []int `json:"status_code,omitempty"`
+
+ // If set, each header specified must be one of the
+ // specified values, with the same logic used by the
+ // request header matcher.
+ Headers http.Header `json:"headers,omitempty"`
+}
+
+// Match returns true if the given statusCode and hdr match rm.
+func (rm ResponseMatcher) Match(statusCode int, hdr http.Header) bool {
+ if !rm.matchStatusCode(statusCode) {
+ return false
+ }
+ return matchHeaders(hdr, rm.Headers, "", nil)
+}
+
+func (rm ResponseMatcher) matchStatusCode(statusCode int) bool {
+ if rm.StatusCode == nil {
+ return true
+ }
+ for _, code := range rm.StatusCode {
+ if StatusCodeMatches(statusCode, code) {
+ return true
+ }
+ }
+ return false
+}
+
+// ParseNamedResponseMatcher parses the tokens of a named response matcher.
+//
+// @name {
+// header <field> [<value>]
+// status <code...>
+// }
+//
+// Or, single line syntax:
+//
+// @name [header <field> [<value>]] | [status <code...>]
+//
+func ParseNamedResponseMatcher(d *caddyfile.Dispenser, matchers map[string]ResponseMatcher) error {
+ for d.Next() {
+ definitionName := d.Val()
+
+ if _, ok := matchers[definitionName]; ok {
+ return d.Errf("matcher is defined more than once: %s", definitionName)
+ }
+
+ matcher := ResponseMatcher{}
+ for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
+ switch d.Val() {
+ case "header":
+ if matcher.Headers == nil {
+ matcher.Headers = http.Header{}
+ }
+
+ // reuse the header request matcher's unmarshaler
+ headerMatcher := MatchHeader(matcher.Headers)
+ err := headerMatcher.UnmarshalCaddyfile(d.NewFromNextSegment())
+ if err != nil {
+ return err
+ }
+
+ matcher.Headers = http.Header(headerMatcher)
+ case "status":
+ if matcher.StatusCode == nil {
+ matcher.StatusCode = []int{}
+ }
+
+ args := d.RemainingArgs()
+ if len(args) == 0 {
+ return d.ArgErr()
+ }
+
+ for _, arg := range args {
+ if len(arg) == 3 && strings.HasSuffix(arg, "xx") {
+ arg = arg[:1]
+ }
+ statusNum, err := strconv.Atoi(arg)
+ if err != nil {
+ return d.Errf("bad status value '%s': %v", arg, err)
+ }
+ matcher.StatusCode = append(matcher.StatusCode, statusNum)
+ }
+ default:
+ return d.Errf("unrecognized response matcher %s", d.Val())
+ }
+ }
+
+ matchers[definitionName] = matcher
+ }
+ return nil
+}