Skip to content

Commit

Permalink
reverseproxy: Active health checks request body option (#6520)
Browse files Browse the repository at this point in the history
* Add an option to specify the body used for active health checks

* Replacer on request body
  • Loading branch information
jbro authored Aug 19, 2024
1 parent 043fe41 commit 54a0c8f
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
:8884

reverse_proxy 127.0.0.1:65535 {
health_uri /health
health_request_body "test body"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"health_checks": {
"active": {
"body": "test body",
"uri": "/health"
}
},
"upstreams": [
{
"dial": "127.0.0.1:65535"
}
]
}
]
}
]
}
}
}
}
}
31 changes: 22 additions & 9 deletions modules/caddyhttp/reverseproxy/caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,20 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// lb_retry_match <request-matcher>
//
// # active health checking
// health_uri <uri>
// health_port <port>
// health_interval <interval>
// health_passes <num>
// health_fails <num>
// health_timeout <duration>
// health_status <status>
// health_body <regexp>
// health_uri <uri>
// health_port <port>
// health_interval <interval>
// health_passes <num>
// health_fails <num>
// health_timeout <duration>
// health_status <status>
// health_body <regexp>
// health_method <value>
// health_request_body <value>
// health_follow_redirects
// health_headers {
// <field> [<values...>]
// }
// health_method <value>
//
// # passive health checking
// fail_duration <duration>
Expand Down Expand Up @@ -425,6 +426,18 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
h.HealthChecks.Active.Method = d.Val()

case "health_request_body":
if !d.NextArg() {
return d.ArgErr()
}
if h.HealthChecks == nil {
h.HealthChecks = new(HealthChecks)
}
if h.HealthChecks.Active == nil {
h.HealthChecks.Active = new(ActiveHealthChecks)
}
h.HealthChecks.Active.Body = d.Val()

case "health_interval":
if !d.NextArg() {
return d.ArgErr()
Expand Down
19 changes: 16 additions & 3 deletions modules/caddyhttp/reverseproxy/healthchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"regexp"
"runtime/debug"
"strconv"
"strings"
"time"

"go.uber.org/zap"
Expand Down Expand Up @@ -93,6 +94,9 @@ type ActiveHealthChecks struct {
// The HTTP method to use for health checks (default "GET").
Method string `json:"method,omitempty"`

// The body to send with the health check request.
Body string `json:"body,omitempty"`

// Whether to follow HTTP redirects in response to active health checks (default off).
FollowRedirects bool `json:"follow_redirects,omitempty"`

Expand Down Expand Up @@ -396,22 +400,31 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
u.Path = h.HealthChecks.Active.Path
}

// replacer used for both body and headers. Only globals (env vars, system info, etc.) are available
repl := caddy.NewReplacer()

// if body is provided, create a reader for it, otherwise nil
var requestBody io.Reader
if h.HealthChecks.Active.Body != "" {
// set body, using replacer
requestBody = strings.NewReader(repl.ReplaceAll(h.HealthChecks.Active.Body, ""))
}

// attach dialing information to this request, as well as context values that
// may be expected by handlers of this request
ctx := h.ctx.Context
ctx = context.WithValue(ctx, caddy.ReplacerCtxKey, caddy.NewReplacer())
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{
dialInfoVarKey: dialInfo,
})
req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), nil)
req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), requestBody)
if err != nil {
return fmt.Errorf("making request: %v", err)
}
ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *req)
req = req.WithContext(ctx)

// set headers, using a replacer with only globals (env vars, system info, etc.)
repl := caddy.NewReplacer()
// set headers, using replacer
repl.Set("http.reverse_proxy.active.target_upstream", networkAddr)
for key, vals := range h.HealthChecks.Active.Headers {
key = repl.ReplaceAll(key, "")
Expand Down

0 comments on commit 54a0c8f

Please sign in to comment.