mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
caddyhttp: New experimental handler for intercepting responses (#6232)
* feat: add generic response interceptors * fix: cs * rename intercept * add some docs * @francislavoie review (first round) * Update modules/caddyhttp/intercept/intercept.go Co-authored-by: Francis Lavoie <lavofr@gmail.com> * shorthands: ir to resp * mark exported symbols as experimental --------- Co-authored-by: Francis Lavoie <lavofr@gmail.com>
This commit is contained in:
parent
583c585c81
commit
fb63e2e40c
6 changed files with 617 additions and 0 deletions
|
@ -74,6 +74,7 @@ var defaultDirectiveOrder = []string{
|
||||||
"request_header",
|
"request_header",
|
||||||
"encode",
|
"encode",
|
||||||
"push",
|
"push",
|
||||||
|
"intercept",
|
||||||
"templates",
|
"templates",
|
||||||
|
|
||||||
// special routing & dispatching directives
|
// special routing & dispatching directives
|
||||||
|
|
|
@ -36,6 +36,7 @@ func NewShorthandReplacer() ShorthandReplacer {
|
||||||
{regexp.MustCompile(`{re\.([\w-\.]*)}`), "{http.regexp.$1}"},
|
{regexp.MustCompile(`{re\.([\w-\.]*)}`), "{http.regexp.$1}"},
|
||||||
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
||||||
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
||||||
|
{regexp.MustCompile(`{resp\.([\w-\.]*)}`), "{http.intercept.$1}"},
|
||||||
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
||||||
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
|
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
localhost
|
||||||
|
|
||||||
|
respond "To intercept"
|
||||||
|
|
||||||
|
intercept {
|
||||||
|
@500 status 500
|
||||||
|
replace_status @500 400
|
||||||
|
|
||||||
|
@all status 2xx 3xx 4xx 5xx
|
||||||
|
replace_status @all {http.error.status_code}
|
||||||
|
|
||||||
|
replace_status {http.error.status_code}
|
||||||
|
|
||||||
|
@accel header X-Accel-Redirect *
|
||||||
|
handle_response @accel {
|
||||||
|
respond "Header X-Accel-Redirect!"
|
||||||
|
}
|
||||||
|
|
||||||
|
@another {
|
||||||
|
header X-Another *
|
||||||
|
}
|
||||||
|
handle_response @another {
|
||||||
|
respond "Header X-Another!"
|
||||||
|
}
|
||||||
|
|
||||||
|
@401 status 401
|
||||||
|
handle_response @401 {
|
||||||
|
respond "Status 401!"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_response {
|
||||||
|
respond "Any! This should be last in the JSON!"
|
||||||
|
}
|
||||||
|
|
||||||
|
@403 {
|
||||||
|
status 403
|
||||||
|
}
|
||||||
|
handle_response @403 {
|
||||||
|
respond "Status 403!"
|
||||||
|
}
|
||||||
|
|
||||||
|
@multi {
|
||||||
|
status 401 403
|
||||||
|
status 404
|
||||||
|
header Foo *
|
||||||
|
header Bar *
|
||||||
|
}
|
||||||
|
handle_response @multi {
|
||||||
|
respond "Headers Foo, Bar AND statuses 401, 403 and 404!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handle_response": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"status_code": [
|
||||||
|
500
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status_code": 400
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"status_code": [
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status_code": "{http.error.status_code}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"headers": {
|
||||||
|
"X-Accel-Redirect": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Header X-Accel-Redirect!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"headers": {
|
||||||
|
"X-Another": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Header X-Another!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"status_code": [
|
||||||
|
401
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Status 401!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"status_code": [
|
||||||
|
403
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Status 403!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"headers": {
|
||||||
|
"Bar": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"Foo": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status_code": [
|
||||||
|
401,
|
||||||
|
403,
|
||||||
|
404
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Headers Foo, Bar AND statuses 401, 403 and 404!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status_code": "{http.error.status_code}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Any! This should be last in the JSON!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handler": "intercept"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "To intercept",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
caddytest/integration/intercept_test.go
Normal file
34
caddytest/integration/intercept_test.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIntercept(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
tester.InitServer(`{
|
||||||
|
skip_install_trust
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
https_port 9443
|
||||||
|
grace_period 1ns
|
||||||
|
}
|
||||||
|
|
||||||
|
localhost:9080 {
|
||||||
|
respond /intercept "I'm a teapot" 408
|
||||||
|
respond /no-intercept "I'm not a teapot"
|
||||||
|
|
||||||
|
intercept {
|
||||||
|
@teapot status 408
|
||||||
|
handle_response @teapot {
|
||||||
|
respond /intercept "I'm a combined coffee/tea pot that is temporarily out of coffee" 503
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, "caddyfile")
|
||||||
|
|
||||||
|
tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
|
||||||
|
tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot")
|
||||||
|
}
|
350
modules/caddyhttp/intercept/intercept.go
Normal file
350
modules/caddyhttp/intercept/intercept.go
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
// 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 intercept
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(Intercept{})
|
||||||
|
httpcaddyfile.RegisterHandlerDirective("intercept", parseCaddyfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept is a middleware that intercepts then replaces or modifies the original response.
|
||||||
|
// It can, for instance, be used to implement X-Sendfile/X-Accel-Redirect-like features
|
||||||
|
// when using modules like FrankenPHP or Caddy Snake.
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL: Subject to change or removal.
|
||||||
|
type Intercept struct {
|
||||||
|
// List of handlers and their associated matchers to evaluate
|
||||||
|
// after successful response generation.
|
||||||
|
// The first handler that matches the original response will
|
||||||
|
// be invoked. The original response body will not be
|
||||||
|
// written to the client;
|
||||||
|
// it is up to the handler to finish handling the response.
|
||||||
|
//
|
||||||
|
// Three new placeholders are available in this handler chain:
|
||||||
|
// - `{http.intercept.status_code}` The status code from the response
|
||||||
|
// - `{http.intercept.status_text}` The status text from the response
|
||||||
|
// - `{http.intercept.header.*}` The headers from the response
|
||||||
|
HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"`
|
||||||
|
|
||||||
|
// Holds the named response matchers from the Caddyfile while adapting
|
||||||
|
responseMatchers map[string]caddyhttp.ResponseMatcher
|
||||||
|
|
||||||
|
// Holds the handle_response Caddyfile tokens while adapting
|
||||||
|
handleResponseSegments []*caddyfile.Dispenser
|
||||||
|
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL: Subject to change or removal.
|
||||||
|
func (Intercept) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "http.handlers.intercept",
|
||||||
|
New: func() caddy.Module { return new(Intercept) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision ensures that i is set up properly before use.
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL: Subject to change or removal.
|
||||||
|
func (irh *Intercept) Provision(ctx caddy.Context) error {
|
||||||
|
// set up any response routes
|
||||||
|
for i, rh := range irh.HandleResponse {
|
||||||
|
err := rh.Provision(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("provisioning response handler %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
irh.logger = ctx.Logger()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle status code replacement
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL: Subject to change or removal.
|
||||||
|
type interceptedResponseHandler struct {
|
||||||
|
caddyhttp.ResponseRecorder
|
||||||
|
replacer *caddy.Replacer
|
||||||
|
handler caddyhttp.ResponseHandler
|
||||||
|
handlerIndex int
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPERIMENTAL: Subject to change or removal.
|
||||||
|
func (irh interceptedResponseHandler) WriteHeader(statusCode int) {
|
||||||
|
if irh.statusCode != 0 && (statusCode < 100 || statusCode >= 200) {
|
||||||
|
irh.ResponseRecorder.WriteHeader(irh.statusCode)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
irh.ResponseRecorder.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPERIMENTAL: Subject to change or removal.
|
||||||
|
func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
|
buf.Reset()
|
||||||
|
defer bufPool.Put(buf)
|
||||||
|
|
||||||
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
rec := interceptedResponseHandler{replacer: repl}
|
||||||
|
rec.ResponseRecorder = caddyhttp.NewResponseRecorder(w, buf, func(status int, header http.Header) bool {
|
||||||
|
// see if any response handler is configured for this original response
|
||||||
|
for i, rh := range ir.HandleResponse {
|
||||||
|
if rh.Match != nil && !rh.Match.Match(status, header) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rec.handler = rh
|
||||||
|
rec.handlerIndex = i
|
||||||
|
|
||||||
|
// if configured to only change the status code,
|
||||||
|
// do that then stream
|
||||||
|
if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" {
|
||||||
|
sc, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, ""))
|
||||||
|
if err != nil {
|
||||||
|
rec.statusCode = http.StatusInternalServerError
|
||||||
|
} else {
|
||||||
|
rec.statusCode = sc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rec.statusCode == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := next.ServeHTTP(rec, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !rec.Buffered() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up the replacer so that parts of the original response can be
|
||||||
|
// used for routing decisions
|
||||||
|
for field, value := range r.Header {
|
||||||
|
repl.Set("http.intercept.header."+field, strings.Join(value, ","))
|
||||||
|
}
|
||||||
|
repl.Set("http.intercept.status_code", rec.Status())
|
||||||
|
|
||||||
|
ir.logger.Debug("handling response", zap.Int("handler", rec.handlerIndex))
|
||||||
|
|
||||||
|
// pass the request through the response handler routes
|
||||||
|
return rec.handler.Routes.Compile(next).ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
|
//
|
||||||
|
// intercept [<matcher>] {
|
||||||
|
// # intercept original responses
|
||||||
|
// @name {
|
||||||
|
// status <code...>
|
||||||
|
// header <field> [<value>]
|
||||||
|
// }
|
||||||
|
// replace_status [<matcher>] <status_code>
|
||||||
|
// handle_response [<matcher>] {
|
||||||
|
// <directives...>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The FinalizeUnmarshalCaddyfile method should be called after this
|
||||||
|
// to finalize parsing of "handle_response" blocks, if possible.
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL: Subject to change or removal.
|
||||||
|
func (i *Intercept) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
// collect the response matchers defined as subdirectives
|
||||||
|
// prefixed with "@" for use with "handle_response" blocks
|
||||||
|
i.responseMatchers = make(map[string]caddyhttp.ResponseMatcher)
|
||||||
|
|
||||||
|
d.Next() // consume the directive name
|
||||||
|
for d.NextBlock(0) {
|
||||||
|
// if the subdirective has an "@" prefix then we
|
||||||
|
// parse it as a response matcher for use with "handle_response"
|
||||||
|
if strings.HasPrefix(d.Val(), matcherPrefix) {
|
||||||
|
err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), i.responseMatchers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch d.Val() {
|
||||||
|
case "handle_response":
|
||||||
|
// delegate the parsing of handle_response to the caller,
|
||||||
|
// since we need the httpcaddyfile.Helper to parse subroutes.
|
||||||
|
// See h.FinalizeUnmarshalCaddyfile
|
||||||
|
i.handleResponseSegments = append(i.handleResponseSegments, d.NewFromNextSegment())
|
||||||
|
|
||||||
|
case "replace_status":
|
||||||
|
args := d.RemainingArgs()
|
||||||
|
if len(args) != 1 && len(args) != 2 {
|
||||||
|
return d.Errf("must have one or two arguments: an optional response matcher, and a status code")
|
||||||
|
}
|
||||||
|
|
||||||
|
responseHandler := caddyhttp.ResponseHandler{}
|
||||||
|
|
||||||
|
if len(args) == 2 {
|
||||||
|
if !strings.HasPrefix(args[0], matcherPrefix) {
|
||||||
|
return d.Errf("must use a named response matcher, starting with '@'")
|
||||||
|
}
|
||||||
|
foundMatcher, ok := i.responseMatchers[args[0]]
|
||||||
|
if !ok {
|
||||||
|
return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
|
||||||
|
}
|
||||||
|
responseHandler.Match = &foundMatcher
|
||||||
|
responseHandler.StatusCode = caddyhttp.WeakString(args[1])
|
||||||
|
} else if len(args) == 1 {
|
||||||
|
responseHandler.StatusCode = caddyhttp.WeakString(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure there's no block, cause it doesn't make sense
|
||||||
|
if nesting := d.Nesting(); d.NextBlock(nesting) {
|
||||||
|
return d.Errf("cannot define routes for 'replace_status', use 'handle_response' instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
i.HandleResponse = append(
|
||||||
|
i.HandleResponse,
|
||||||
|
responseHandler,
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinalizeUnmarshalCaddyfile finalizes the Caddyfile parsing which
|
||||||
|
// requires having an httpcaddyfile.Helper to function, to parse subroutes.
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL: Subject to change or removal.
|
||||||
|
func (i *Intercept) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error {
|
||||||
|
for _, d := range i.handleResponseSegments {
|
||||||
|
// consume the "handle_response" token
|
||||||
|
d.Next()
|
||||||
|
args := d.RemainingArgs()
|
||||||
|
|
||||||
|
// TODO: Remove this check at some point in the future
|
||||||
|
if len(args) == 2 {
|
||||||
|
return d.Errf("configuring 'handle_response' for status code replacement is no longer supported. Use 'replace_status' instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 1 {
|
||||||
|
return d.Errf("too many arguments for 'handle_response': %s", args)
|
||||||
|
}
|
||||||
|
|
||||||
|
var matcher *caddyhttp.ResponseMatcher
|
||||||
|
if len(args) == 1 {
|
||||||
|
// the first arg should always be a matcher.
|
||||||
|
if !strings.HasPrefix(args[0], matcherPrefix) {
|
||||||
|
return d.Errf("must use a named response matcher, starting with '@'")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundMatcher, ok := i.responseMatchers[args[0]]
|
||||||
|
if !ok {
|
||||||
|
return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
|
||||||
|
}
|
||||||
|
matcher = &foundMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the block as routes
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
i.HandleResponse = append(
|
||||||
|
i.HandleResponse,
|
||||||
|
caddyhttp.ResponseHandler{
|
||||||
|
Match: matcher,
|
||||||
|
Routes: subroute.Routes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the handle_response entries without a matcher to the end.
|
||||||
|
// we can't use sort.SliceStable because it will reorder the rest of the
|
||||||
|
// entries which may be undesirable because we don't have a good
|
||||||
|
// heuristic to use for sorting.
|
||||||
|
withoutMatchers := []caddyhttp.ResponseHandler{}
|
||||||
|
withMatchers := []caddyhttp.ResponseHandler{}
|
||||||
|
for _, hr := range i.HandleResponse {
|
||||||
|
if hr.Match == nil {
|
||||||
|
withoutMatchers = append(withoutMatchers, hr)
|
||||||
|
} else {
|
||||||
|
withMatchers = append(withMatchers, hr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.HandleResponse = append(withMatchers, withoutMatchers...)
|
||||||
|
|
||||||
|
// clean up the bits we only needed for adapting
|
||||||
|
i.handleResponseSegments = nil
|
||||||
|
i.responseMatchers = nil
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const matcherPrefix = "@"
|
||||||
|
|
||||||
|
func parseCaddyfile(helper httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
|
var ir Intercept
|
||||||
|
if err := ir.UnmarshalCaddyfile(helper.Dispenser); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ir.FinalizeUnmarshalCaddyfile(helper); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface guards
|
||||||
|
var (
|
||||||
|
_ caddy.Provisioner = (*Intercept)(nil)
|
||||||
|
_ caddyfile.Unmarshaler = (*Intercept)(nil)
|
||||||
|
_ caddyhttp.MiddlewareHandler = (*Intercept)(nil)
|
||||||
|
)
|
|
@ -10,6 +10,7 @@ import (
|
||||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/zstd"
|
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/zstd"
|
||||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
|
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
|
||||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||||
|
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/intercept"
|
||||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/logging"
|
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/logging"
|
||||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/map"
|
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/map"
|
||||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/proxyprotocol"
|
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/proxyprotocol"
|
||||||
|
|
Loading…
Reference in a new issue