diff options
author | Francis Lavoie <[email protected]> | 2021-05-02 14:39:06 -0400 |
---|---|---|
committer | GitHub <[email protected]> | 2021-05-02 12:39:06 -0600 |
commit | e4a22de9d1c4d7aa83126ee13e40b61e7b0e9df0 (patch) | |
tree | 82e3134b3c83d258fb08299e917cfa9b9d980ff7 /modules/caddyhttp/responsematchers.go | |
parent | e6f6d3a4765565b09f95a29a2e75be34e1d70359 (diff) | |
download | caddy-e4a22de9d1c4d7aa83126ee13e40b61e7b0e9df0.tar.gz caddy-e4a22de9d1c4d7aa83126ee13e40b61e7b0e9df0.zip |
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 <[email protected]>
* caddyhttp: Reorganize response matchers
* reverseproxy: Reintroduce caddyfile.Unmarshaler
* reverseproxy: Add comment mentioning Finalize should be called
Co-authored-by: Maxime Soulé <[email protected]>
Co-authored-by: Matt Holt <[email protected]>
Diffstat (limited to 'modules/caddyhttp/responsematchers.go')
-rw-r--r-- | modules/caddyhttp/responsematchers.go | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/modules/caddyhttp/responsematchers.go b/modules/caddyhttp/responsematchers.go new file mode 100644 index 000000000..d9ad8480b --- /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 +} |