Go API Documentation

github.com/caddyserver/caddy/v2/modules/caddyhttp

No package summary is available.

Package

Files: 23. Third party imports: 23. Imports from organisation: 1. Tests: 0. Benchmarks: 0.

Constants

// The name of the CEL function which accesses Replacer values.
const CELPlaceholderFuncName = "ph"
// The name of the CEL request variable.
const CELRequestVarName = "req"
// ErrorCtxKey is the context key to use when storing
// an error (for use with context.Context).
const ErrorCtxKey = caddy.CtxKey("handler_chain_error")
// MatcherErrorVarKey is the key used for the variable that
// holds an optional error emitted from a request matcher,
// to short-circuit the handler chain, since matchers cannot
// return errors via the RequestMatcher interface.
//
// Deprecated: Matchers should implement RequestMatcherWithError
// which can return an error directly, instead of smuggling it
// through the vars map.
const MatcherErrorVarKey = "matchers.error"
const MatcherNameCtxKey = "matcher_name"
const (
	// DefaultHTTPPort is the default port for HTTP.
	DefaultHTTPPort	= 80

	// DefaultHTTPSPort is the default port for HTTPS.
	DefaultHTTPSPort	= 443
)
const (
	// defaultIdleTimeout is the default HTTP server timeout
	// for closing idle connections; useful to avoid resource
	// exhaustion behind hungry CDNs, for example (we've had
	// several complaints without this).
	defaultIdleTimeout	= caddy.Duration(5 * time.Minute)

	// defaultReadHeaderTimeout is the default timeout for
	// reading HTTP headers from clients. Headers are generally
	// small, often less than 1 KB, so it shouldn't take a
	// long time even on legitimately slow connections or
	// busy servers to read it.
	defaultReadHeaderTimeout	= caddy.Duration(time.Minute)
)
const (
	// Variable name used to indicate that this request
	// should be omitted from the access logs
	LogSkipVar	string	= "log_skip"

	// For adding additional fields to the access logs
	ExtraLogFieldsCtxKey	caddy.CtxKey	= "extra_log_fields"

	// Variable name used to indicate the logger to be used
	AccessLoggerNameVarKey	string	= "access_logger_names"
)
const (
	reqCookieReplPrefix		= "http.request.cookie."
	reqHeaderReplPrefix		= "http.request.header."
	reqHostLabelsReplPrefix		= "http.request.host.labels."
	reqTLSReplPrefix		= "http.request.tls."
	reqURIPathReplPrefix		= "http.request.uri.path."
	reqURIQueryReplPrefix		= "http.request.uri.query."
	respHeaderReplPrefix		= "http.response.header."
	varsReplPrefix			= "http.vars."
	reqOrigURIPathReplPrefix	= "http.request.orig_uri.path."
)
// Context keys for HTTP request context values.
const (
	// For referencing the server instance
	ServerCtxKey	caddy.CtxKey	= "server"

	// For the request's variable table
	VarsCtxKey	caddy.CtxKey	= "vars"

	// For a partial copy of the unmodified request that
	// originally came into the server's entry handler
	OriginalRequestCtxKey	caddy.CtxKey	= "original_request"

	// For referencing underlying net.Conn
	ConnCtxKey	caddy.CtxKey	= "conn"

	// For tracking whether the client is a trusted proxy
	TrustedProxyVarKey	string	= "trusted_proxy"

	// For tracking the real client IP (affected by trusted_proxy)
	ClientIPVarKey	string	= "client_ip"
)
const regexpPlaceholderPrefix = "http.regexp"
const separator = string(filepath.Separator)
const shutdownPollIntervalMax = 500 * time.Millisecond

Vars

ErrNotImplemented is returned when an underlying ResponseWriter does not implement the required method.

var ErrNotImplemented = fmt.Errorf("method not implemented")
var (
	_	caddy.App		= (*App)(nil)
	_	caddy.Provisioner	= (*App)(nil)
	_	caddy.Validator		= (*App)(nil)
)
var (
	// The placeholder may not be preceded by a backslash; the expansion
	// will include the preceding character if it is not a backslash.
	placeholderRegexp	= regexp.MustCompile(`([^\\]|^){([a-zA-Z][\w.-]+)}`)
	placeholderExpansion	= `${1}ph(req, "${2}")`

	// As a second pass, we need to strip the escape character in front of
	// the placeholder, if it exists.
	escapedPlaceholderRegexp	= regexp.MustCompile(`\\{([a-zA-Z][\w.-]+)}`)
	escapedPlaceholderExpansion	= `{${1}}`

	CELTypeJSON	= cel.MapType(cel.StringType, cel.DynType)
)

emptyHandler is used as a no-op handler.

var emptyHandler Handler = HandlerFunc(func(_ http.ResponseWriter, req *http.Request) error {
	SetVar(req.Context(), "unhandled", true)
	return nil
})

An implicit suffix middleware that, if reached, sets the StatusCode to the error stored in the ErrorCtxKey. This is to prevent situations where the Error chain does not actually handle the error (for instance, it matches only on some errors). See #3053

var errorEmptyHandler Handler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
	httpError := r.Context().Value(ErrorCtxKey)
	if handlerError, ok := httpError.(HandlerError); ok {
		w.WriteHeader(handlerError.StatusCode)
	} else {
		w.WriteHeader(http.StatusInternalServerError)
	}
	return nil
})

httpRequestCELType is the type representation of a native HTTP request.

var httpRequestCELType = cel.ObjectType("http.Request", traits.ReceiverType)
var httpRequestObjectType = cel.ObjectType("http.Request")
var networkTypesHTTP3 = map[string]string{
	"unixgram":	"unixgram",
	"udp":		"udp",
	"udp4":		"udp4",
	"udp6":		"udp6",
	"tcp":		"udp",
	"tcp4":		"udp4",
	"tcp6":		"udp6",
	"fdgram":	"fdgram",
}
var pkixNameCELType = cel.ObjectType("pkix.Name", traits.ReceiverType)
var routeGroupCtxKey = caddy.CtxKey("route_group")
var wordRE = regexp.MustCompile(`\w+`)

Types

App

App is a robust, production-ready HTTP server.

HTTPS is enabled by default if host matchers with qualifying names are used in any of routes; certificates are automatically provisioned and renewed. Additionally, automatic HTTPS will also enable HTTPS for servers that listen only on the HTTPS port but which do not have any TLS connection policies defined by adding a good, default TLS connection policy.

In HTTP routes, additional placeholders are available (replace any *):

Placeholder | Description ------------|--------------- {http.request.body} | The request body (⚠️ inefficient; use only for debugging) {http.request.cookie.*} | HTTP request cookie {http.request.duration} | Time up to now spent handling the request (after decoding headers from client) {http.request.duration_ms} | Same as 'duration', but in milliseconds. {http.request.uuid} | The request unique identifier {http.request.header.*} | Specific request header field {http.request.host} | The host part of the request's Host header {http.request.host.labels.*} | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo {http.request.hostport} | The host and port from the request's Host header {http.request.method} | The request method {http.request.orig_method} | The request's original method {http.request.orig_uri} | The request's original URI {http.request.orig_uri.path} | The request's original path {http.request.orig_uri.path.*} | Parts of the original path, split by / (0-based from left) {http.request.orig_uri.path.dir} | The request's original directory {http.request.orig_uri.path.file} | The request's original filename {http.request.orig_uri.query} | The request's original query string (without ?) {http.request.port} | The port part of the request's Host header {http.request.proto} | The protocol of the request {http.request.local.host} | The host (IP) part of the local address the connection arrived on {http.request.local.port} | The port part of the local address the connection arrived on {http.request.local} | The local address the connection arrived on {http.request.remote.host} | The host (IP) part of the remote client's address {http.request.remote.port} | The port part of the remote client's address {http.request.remote} | The address of the remote client {http.request.scheme} | The request scheme, typically http or https {http.request.tls.version} | The TLS version name {http.request.tls.cipher_suite} | The TLS cipher suite {http.request.tls.resumed} | The TLS connection resumed a previous connection {http.request.tls.proto} | The negotiated next protocol {http.request.tls.proto_mutual} | The negotiated next protocol was advertised by the server {http.request.tls.server_name} | The server name requested by the client, if any {http.request.tls.client.fingerprint} | The SHA256 checksum of the client certificate {http.request.tls.client.public_key} | The public key of the client certificate. {http.request.tls.client.public_key_sha256} | The SHA256 checksum of the client's public key. {http.request.tls.client.certificate_pem} | The PEM-encoded value of the certificate. {http.request.tls.client.certificate_der_base64} | The base64-encoded value of the certificate. {http.request.tls.client.issuer} | The issuer DN of the client certificate {http.request.tls.client.serial} | The serial number of the client certificate {http.request.tls.client.subject} | The subject DN of the client certificate {http.request.tls.client.san.dns_names.*} | SAN DNS names(index optional) {http.request.tls.client.san.emails.*} | SAN email addresses (index optional) {http.request.tls.client.san.ips.*} | SAN IP addresses (index optional) {http.request.tls.client.san.uris.*} | SAN URIs (index optional) {http.request.uri} | The full request URI {http.request.uri.path} | The path component of the request URI {http.request.uri.path.*} | Parts of the path, split by / (0-based from left) {http.request.uri.path.dir} | The directory, excluding leaf filename {http.request.uri.path.file} | The filename of the path, excluding directory {http.request.uri.query} | The query string (without ?) {http.request.uri.query.*} | Individual query string value {http.response.header.*} | Specific response header field {http.vars.*} | Custom variables in the HTTP handler chain {http.shutting_down} | True if the HTTP app is shutting down {http.time_until_shutdown} | Time until HTTP server shutdown, if scheduled

type App struct {
	// HTTPPort specifies the port to use for HTTP (as opposed to HTTPS),
	// which is used when setting up HTTP->HTTPS redirects or ACME HTTP
	// challenge solvers. Default: 80.
	HTTPPort	int	`json:"http_port,omitempty"`

	// HTTPSPort specifies the port to use for HTTPS, which is used when
	// solving the ACME TLS-ALPN challenges, or whenever HTTPS is needed
	// but no specific port number is given. Default: 443.
	HTTPSPort	int	`json:"https_port,omitempty"`

	// GracePeriod is how long to wait for active connections when shutting
	// down the servers. During the grace period, no new connections are
	// accepted, idle connections are closed, and active connections will
	// be given the full length of time to become idle and close.
	// Once the grace period is over, connections will be forcefully closed.
	// If zero, the grace period is eternal. Default: 0.
	GracePeriod	caddy.Duration	`json:"grace_period,omitempty"`

	// ShutdownDelay is how long to wait before initiating the grace
	// period. When this app is stopping (e.g. during a config reload or
	// process exit), all servers will be shut down. Normally this immediately
	// initiates the grace period. However, if this delay is configured, servers
	// will not be shut down until the delay is over. During this time, servers
	// continue to function normally and allow new connections. At the end, the
	// grace period will begin. This can be useful to allow downstream load
	// balancers time to move this instance out of the rotation without hiccups.
	//
	// When shutdown has been scheduled, placeholders {http.shutting_down} (bool)
	// and {http.time_until_shutdown} (duration) may be useful for health checks.
	ShutdownDelay	caddy.Duration	`json:"shutdown_delay,omitempty"`

	// Servers is the list of servers, keyed by arbitrary names chosen
	// at your discretion for your own convenience; the keys do not
	// affect functionality.
	Servers	map[string]*Server	`json:"servers,omitempty"`

	// If set, metrics observations will be enabled.
	// This setting is EXPERIMENTAL and subject to change.
	Metrics	*Metrics	`json:"metrics,omitempty"`

	ctx	caddy.Context
	logger	*zap.Logger
	tlsApp	*caddytls.TLS

	// used temporarily between phases 1 and 2 of auto HTTPS
	allCertDomains	[]string
}

AutoHTTPSConfig

AutoHTTPSConfig is used to disable automatic HTTPS or certain aspects of it for a specific server. HTTPS is enabled automatically and by default when qualifying hostnames are available from the config.

type AutoHTTPSConfig struct {
	// If true, automatic HTTPS will be entirely disabled,
	// including certificate management and redirects.
	Disabled	bool	`json:"disable,omitempty"`

	// If true, only automatic HTTP->HTTPS redirects will
	// be disabled, but other auto-HTTPS features will
	// remain enabled.
	DisableRedir	bool	`json:"disable_redirects,omitempty"`

	// If true, automatic certificate management will be
	// disabled, but other auto-HTTPS features will
	// remain enabled.
	DisableCerts	bool	`json:"disable_certificates,omitempty"`

	// Hosts/domain names listed here will not be included
	// in automatic HTTPS (they will not have certificates
	// loaded nor redirects applied).
	Skip	[]string	`json:"skip,omitempty"`

	// Hosts/domain names listed here will still be enabled
	// for automatic HTTPS (unless in the Skip list), except
	// that certificates will not be provisioned and managed
	// for these names.
	SkipCerts	[]string	`json:"skip_certificates,omitempty"`

	// By default, automatic HTTPS will obtain and renew
	// certificates for qualifying hostnames. However, if
	// a certificate with a matching SAN is already loaded
	// into the cache, certificate management will not be
	// enabled. To force automated certificate management
	// regardless of loaded certificates, set this to true.
	IgnoreLoadedCerts	bool	`json:"ignore_loaded_certificates,omitempty"`

	// If true, automatic HTTPS will prefer wildcard names
	// and ignore non-wildcard names if both are available.
	// This allows for writing a config with top-level host
	// matchers without having those names produce certificates.
	PreferWildcard	bool	`json:"prefer_wildcard,omitempty"`
}

CELLibraryProducer

CELLibraryProducer provide CEL libraries that expose a Matcher implementation as a first class function within the CEL expression matcher.

type CELLibraryProducer interface {
	// CELLibrary creates a cel.Library which makes it possible to use the
	// target object within CEL expression matchers.
	CELLibrary(caddy.Context) (cel.Library, error)
}

CELMatcherFactory

CELMatcherFactory converts a constant CEL value into a RequestMatcher. Deprecated: Use CELMatcherWithErrorFactory instead.

type CELMatcherFactory = func(data ref.Val) (RequestMatcher, error)

CELMatcherWithErrorFactory

CELMatcherWithErrorFactory converts a constant CEL value into a RequestMatcherWithError.

type CELMatcherWithErrorFactory = func(data ref.Val) (RequestMatcherWithError, error)

ExtraLogFields

ExtraLogFields is a list of extra fields to log with every request.

type ExtraLogFields struct {
	fields []zapcore.Field
}

HTTPErrorConfig

HTTPErrorConfig determines how to handle errors from the HTTP handlers.

type HTTPErrorConfig struct {
	// The routes to evaluate after the primary handler
	// chain returns an error. In an error route, extra
	// placeholders are available:
	//
	// Placeholder | Description
	// ------------|---------------
	// `{http.error.status_code}` | The recommended HTTP status code
	// `{http.error.status_text}` | The status text associated with the recommended status code
	// `{http.error.message}`     | The error message
	// `{http.error.trace}`       | The origin of the error
	// `{http.error.id}`          | An identifier for this occurrence of the error
	Routes RouteList `json:"routes,omitempty"`
}

HTTPRedirectListenerWrapper

HTTPRedirectListenerWrapper provides HTTP->HTTPS redirects for connections that come on the TLS port as an HTTP request, by detecting using the first few bytes that it's not a TLS handshake, but instead an HTTP request.

This is especially useful when using a non-standard HTTPS port. A user may simply type the address in their browser without the https:// scheme, which would cause the browser to attempt the connection over HTTP, but this would cause a "Client sent an HTTP request to an HTTPS server" error response.

This listener wrapper must be placed BEFORE the "tls" listener wrapper, for it to work properly.

type HTTPRedirectListenerWrapper struct {
	// MaxHeaderBytes is the maximum size to parse from a client's
	// HTTP request headers. Default: 1 MB
	MaxHeaderBytes int64 `json:"max_header_bytes,omitempty"`
}

Handler

Handler is like http.Handler except ServeHTTP may return an error.

If any handler encounters an error, it should be returned for proper handling. Return values should be propagated down the middleware chain by returning it unchanged. Returned errors should not be re-wrapped if they are already HandlerError values.

type Handler interface {
	ServeHTTP(http.ResponseWriter, *http.Request) error
}

HandlerError

HandlerError is a serializable representation of an error from within an HTTP handler.

type HandlerError struct {
	Err		error	// the original error value and message
	StatusCode	int	// the HTTP status code to associate with this error

	ID	string	// generated; for identifying this error in logs
	Trace	string	// produced from call stack
}

HandlerFunc

HandlerFunc is a convenience type like http.HandlerFunc.

type HandlerFunc func(http.ResponseWriter, *http.Request) error

IPRangeSource

IPRangeSource gets a list of IP ranges.

The request is passed as an argument to allow plugin implementations to have more flexibility. But, a plugin MUST NOT modify the request. The caller will have read the r.RemoteAddr before getting IP ranges.

This should be a very fast function -- instant if possible. The list of IP ranges should be sourced as soon as possible if loaded from an external source (i.e. initially loaded during Provisioning), so that it's ready to be used when requests start getting handled. A read lock should probably be used to get the cached value if the ranges can change at runtime (e.g. periodically refreshed). Using a caddy.UsagePool may be a good idea to avoid having refetch the values when a config reload occurs, which would waste time.

If the list of IP ranges cannot be sourced, then provisioning SHOULD fail. Getting the IP ranges at runtime MUST NOT fail, because it would cancel incoming requests. If refreshing the list fails, then the previous list of IP ranges should continue to be returned so that the server can continue to operate normally.

type IPRangeSource interface {
	GetIPRanges(*http.Request) []netip.Prefix
}

Invoke

Invoke implements a handler that compiles and executes a named route that was defined on the server.

EXPERIMENTAL: Subject to change or removal.

type Invoke struct {
	// Name is the key of the named route to execute
	Name string `json:"name,omitempty"`
}

LoggableHTTPHeader

LoggableHTTPHeader makes an HTTP header loggable with zap.Object(). Headers with potentially sensitive information (Cookie, Set-Cookie, Authorization, and Proxy-Authorization) are logged with empty values.

type LoggableHTTPHeader struct {
	http.Header

	ShouldLogCredentials	bool
}

LoggableHTTPRequest

LoggableHTTPRequest makes an HTTP request loggable with zap.Object().

type LoggableHTTPRequest struct {
	*http.Request

	ShouldLogCredentials	bool
}

LoggableStringArray

LoggableStringArray makes a slice of strings marshalable for logging.

type LoggableStringArray []string

LoggableTLSConnState

LoggableTLSConnState makes a TLS connection state loggable with zap.Object().

type LoggableTLSConnState tls.ConnectionState

MatchClientIP

MatchClientIP matches requests by the client IP address, i.e. the resolved address, considering trusted proxies.

type MatchClientIP struct {
	// The IPs or CIDR ranges to match.
	Ranges	[]string	`json:"ranges,omitempty"`

	// cidrs and zones vars should aligned always in the same
	// length and indexes for matching later
	cidrs	[]*netip.Prefix
	zones	[]string
	logger	*zap.Logger
}

MatchExpression

MatchExpression matches requests by evaluating a CEL expression. This enables complex logic to be expressed using a comfortable, familiar syntax. Please refer to the standard definitions of CEL functions and operators.

This matcher's JSON interface is actually a string, not a struct. The generated docs are not correct because this type has custom marshaling logic.

COMPATIBILITY NOTE: This module is still experimental and is not subject to Caddy's compatibility guarantee.

type MatchExpression struct {
	// The CEL expression to evaluate. Any Caddy placeholders
	// will be expanded and situated into proper CEL function
	// calls before evaluating.
	Expr	string	`json:"expr,omitempty"`

	// Name is an optional name for this matcher.
	// This is used to populate the name for regexp
	// matchers that appear in the expression.
	Name	string	`json:"name,omitempty"`

	expandedExpr	string
	prg		cel.Program
	ta		types.Adapter

	log	*zap.Logger
}

MatchRegexp

MatchRegexp is an embedable type for matching using regular expressions. It adds placeholders to the request's replacer.

type MatchRegexp struct {
	// A unique name for this regular expression. Optional,
	// but useful to prevent overwriting captures from other
	// regexp matchers.
	Name	string	`json:"name,omitempty"`

	// The regular expression to evaluate, in RE2 syntax,
	// which is the same general syntax used by Go, Perl,
	// and Python. For details, see
	// [Go's regexp package](https://golang.org/pkg/regexp/).
	// Captures are accessible via placeholders. Unnamed
	// capture groups are exposed as their numeric, 1-based
	// index, while named capture groups are available by
	// the capture group name.
	Pattern	string	`json:"pattern"`

	compiled	*regexp.Regexp
}

MatchRemoteIP

MatchRemoteIP matches requests by the remote IP address, i.e. the IP address of the direct connection to Caddy.

type MatchRemoteIP struct {
	// The IPs or CIDR ranges to match.
	Ranges	[]string	`json:"ranges,omitempty"`

	// cidrs and zones vars should aligned always in the same
	// length and indexes for matching later
	cidrs	[]*netip.Prefix
	zones	[]string
	logger	*zap.Logger
}

MatchVarsRE

MatchVarsRE matches the value of the context variables by a given regular expression.

Upon a match, it adds placeholders to the request: {http.regexp.name.capture_group} where name is the regular expression's name, and capture_group is either the named or positional capture group from the expression itself. If no name is given, then the placeholder omits the name: {http.regexp.capture_group} (potentially leading to collisions).

type MatchVarsRE map[string]*MatchRegexp

MatcherSet

MatcherSet is a set of matchers which must all match in order for the request to be matched successfully.

type MatcherSet []any

MatcherSets

MatcherSets is a group of matcher sets capable of checking whether a request matches any of the sets.

type MatcherSets []MatcherSet

Metrics

Metrics configures metrics observations. EXPERIMENTAL and subject to change or removal.

type Metrics struct {
	// Enable per-host metrics. Enabling this option may
	// incur high-memory consumption, depending on the number of hosts
	// managed by Caddy.
	PerHost	bool	`json:"per_host,omitempty"`

	init		sync.Once
	httpMetrics	*httpMetrics	`json:"-"`
}

Middleware

Middleware chains one Handler to the next by being passed the next Handler in the chain.

type Middleware func(Handler) Handler

MiddlewareHandler

MiddlewareHandler is like Handler except it takes as a third argument the next handler in the chain. The next handler will never be nil, but may be a no-op handler if this is the last handler in the chain. Handlers which act as middleware should call the next handler's ServeHTTP method so as to propagate the request down the chain properly. Handlers which act as responders (content origins) need not invoke the next handler, since the last handler in the chain should be the first to write the response.

type MiddlewareHandler interface {
	ServeHTTP(http.ResponseWriter, *http.Request, Handler) error
}

RawMatcherSets

RawMatcherSets is a group of matcher sets in their raw, JSON form.

type RawMatcherSets []caddy.ModuleMap

RequestMatcher

RequestMatcher is a type that can match to a request. A route matcher MUST NOT modify the request, with the only exception being its context.

Deprecated: Matchers should now implement RequestMatcherWithError. You may remove any interface guards for RequestMatcher but keep your Match() methods for backwards compatibility.

type RequestMatcher interface {
	Match(*http.Request) bool
}

RequestMatcherWithError

RequestMatcherWithError is like RequestMatcher but can return an error. An error during matching will abort the request middleware chain and invoke the error middleware chain.

This will eventually replace RequestMatcher. Matcher modules should implement both interfaces, and once all modules have been updated to use RequestMatcherWithError, the RequestMatcher interface may eventually be dropped.

type RequestMatcherWithError interface {
	MatchWithError(*http.Request) (bool, error)
}

ResponseHandler

ResponseHandler pairs a response matcher with custom handling logic. Either the status code can be changed to something else while using the original response body, or, if a status code is not set, it can execute a custom route list; this is useful for executing handler routes based on the properties of an HTTP response that has not been written out to the client yet.

To use this type, provision it at module load time, then when ready to use, match the response against its matcher; if it matches (or doesn't have a matcher), change the status code on the response if configured; otherwise invoke the routes by calling rh.Routes.Compile(next).ServeHTTP(rw, req) (or similar).

type ResponseHandler struct {
	// The response matcher for this handler. If empty/nil,
	// it always matches.
	Match	*ResponseMatcher	`json:"match,omitempty"`

	// To write the original response body but with a different
	// status code, set this field to the desired status code.
	// If set, this takes priority over routes.
	StatusCode	WeakString	`json:"status_code,omitempty"`

	// The list of HTTP routes to execute if no status code is
	// specified. If evaluated, the original response body
	// will not be written.
	Routes	RouteList	`json:"routes,omitempty"`
}

ResponseMatcher

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](/docs/json/apps/http/servers/routes/match/header/).
	Headers	http.Header	`json:"headers,omitempty"`
}

ResponseRecorder

ResponseRecorder is a http.ResponseWriter that records responses instead of writing them to the client. See docs for NewResponseRecorder for proper usage.

type ResponseRecorder interface {
	http.ResponseWriter
	Status() int
	Buffer() *bytes.Buffer
	Buffered() bool
	Size() int
	WriteResponse() error
}

ResponseWriterWrapper

ResponseWriterWrapper wraps an underlying ResponseWriter and promotes its Pusher method as well. To use this type, embed a pointer to it within your own struct type that implements the http.ResponseWriter interface, then call methods on the embedded value.

type ResponseWriterWrapper struct {
	http.ResponseWriter
}

Route

Route consists of a set of rules for matching HTTP requests, a list of handlers to execute, and optional flow control parameters which customize the handling of HTTP requests in a highly flexible and performant manner.

type Route struct {
	// Group is an optional name for a group to which this
	// route belongs. Grouping a route makes it mutually
	// exclusive with others in its group; if a route belongs
	// to a group, only the first matching route in that group
	// will be executed.
	Group	string	`json:"group,omitempty"`

	// The matcher sets which will be used to qualify this
	// route for a request (essentially the "if" statement
	// of this route). Each matcher set is OR'ed, but matchers
	// within a set are AND'ed together.
	MatcherSetsRaw	RawMatcherSets	`json:"match,omitempty" caddy:"namespace=http.matchers"`

	// The list of handlers for this route. Upon matching a request, they are chained
	// together in a middleware fashion: requests flow from the first handler to the last
	// (top of the list to the bottom), with the possibility that any handler could stop
	// the chain and/or return an error. Responses flow back through the chain (bottom of
	// the list to the top) as they are written out to the client.
	//
	// Not all handlers call the next handler in the chain. For example, the reverse_proxy
	// handler always sends a request upstream or returns an error. Thus, configuring
	// handlers after reverse_proxy in the same route is illogical, since they would never
	// be executed. You will want to put handlers which originate the response at the very
	// end of your route(s). The documentation for a module should state whether it invokes
	// the next handler, but sometimes it is common sense.
	//
	// Some handlers manipulate the response. Remember that requests flow down the list, and
	// responses flow up the list.
	//
	// For example, if you wanted to use both `templates` and `encode` handlers, you would
	// need to put `templates` after `encode` in your route, because responses flow up.
	// Thus, `templates` will be able to parse and execute the plain-text response as a
	// template, and then return it up to the `encode` handler which will then compress it
	// into a binary format.
	//
	// If `templates` came before `encode`, then `encode` would write a compressed,
	// binary-encoded response to `templates` which would not be able to parse the response
	// properly.
	//
	// The correct order, then, is this:
	//
	//     [
	//         {"handler": "encode"},
	//         {"handler": "templates"},
	//         {"handler": "file_server"}
	//     ]
	//
	// The request flows ⬇️ DOWN (`encode` -> `templates` -> `file_server`).
	//
	// 1. First, `encode` will choose how to `encode` the response and wrap the response.
	// 2. Then, `templates` will wrap the response with a buffer.
	// 3. Finally, `file_server` will originate the content from a file.
	//
	// The response flows ⬆️ UP (`file_server` -> `templates` -> `encode`):
	//
	// 1. First, `file_server` will write the file to the response.
	// 2. That write will be buffered and then executed by `templates`.
	// 3. Lastly, the write from `templates` will flow into `encode` which will compress the stream.
	//
	// If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes.
	HandlersRaw	[]json.RawMessage	`json:"handle,omitempty" caddy:"namespace=http.handlers inline_key=handler"`

	// If true, no more routes will be executed after this one.
	Terminal	bool	`json:"terminal,omitempty"`

	// decoded values
	MatcherSets	MatcherSets		`json:"-"`
	Handlers	[]MiddlewareHandler	`json:"-"`

	middleware	[]Middleware
}

RouteList

RouteList is a list of server routes that can create a middleware chain.

type RouteList []Route

Server

Server describes an HTTP server.

type Server struct {
	// Socket addresses to which to bind listeners. Accepts
	// [network addresses](/docs/conventions#network-addresses)
	// that may include port ranges. Listener addresses must
	// be unique; they cannot be repeated across all defined
	// servers.
	Listen	[]string	`json:"listen,omitempty"`

	// A list of listener wrapper modules, which can modify the behavior
	// of the base listener. They are applied in the given order.
	ListenerWrappersRaw	[]json.RawMessage	`json:"listener_wrappers,omitempty" caddy:"namespace=caddy.listeners inline_key=wrapper"`

	// How long to allow a read from a client's upload. Setting this
	// to a short, non-zero value can mitigate slowloris attacks, but
	// may also affect legitimately slow clients.
	ReadTimeout	caddy.Duration	`json:"read_timeout,omitempty"`

	// ReadHeaderTimeout is like ReadTimeout but for request headers.
	// Default is 1 minute.
	ReadHeaderTimeout	caddy.Duration	`json:"read_header_timeout,omitempty"`

	// WriteTimeout is how long to allow a write to a client. Note
	// that setting this to a small value when serving large files
	// may negatively affect legitimately slow clients.
	WriteTimeout	caddy.Duration	`json:"write_timeout,omitempty"`

	// IdleTimeout is the maximum time to wait for the next request
	// when keep-alives are enabled. If zero, a default timeout of
	// 5m is applied to help avoid resource exhaustion.
	IdleTimeout	caddy.Duration	`json:"idle_timeout,omitempty"`

	// KeepAliveInterval is the interval at which TCP keepalive packets
	// are sent to keep the connection alive at the TCP layer when no other
	// data is being transmitted. The default is 15s.
	KeepAliveInterval	caddy.Duration	`json:"keepalive_interval,omitempty"`

	// MaxHeaderBytes is the maximum size to parse from a client's
	// HTTP request headers.
	MaxHeaderBytes	int	`json:"max_header_bytes,omitempty"`

	// Enable full-duplex communication for HTTP/1 requests.
	// Only has an effect if Caddy was built with Go 1.21 or later.
	//
	// For HTTP/1 requests, the Go HTTP server by default consumes any
	// unread portion of the request body before beginning to write the
	// response, preventing handlers from concurrently reading from the
	// request and writing the response. Enabling this option disables
	// this behavior and permits handlers to continue to read from the
	// request while concurrently writing the response.
	//
	// For HTTP/2 requests, the Go HTTP server always permits concurrent
	// reads and responses, so this option has no effect.
	//
	// Test thoroughly with your HTTP clients, as some older clients may
	// not support full-duplex HTTP/1 which can cause them to deadlock.
	// See https://github.com/golang/go/issues/57786 for more info.
	//
	// TODO: This is an EXPERIMENTAL feature. Subject to change or removal.
	EnableFullDuplex	bool	`json:"enable_full_duplex,omitempty"`

	// Routes describes how this server will handle requests.
	// Routes are executed sequentially. First a route's matchers
	// are evaluated, then its grouping. If it matches and has
	// not been mutually-excluded by its grouping, then its
	// handlers are executed sequentially. The sequence of invoked
	// handlers comprises a compiled middleware chain that flows
	// from each matching route and its handlers to the next.
	//
	// By default, all unrouted requests receive a 200 OK response
	// to indicate the server is working.
	Routes	RouteList	`json:"routes,omitempty"`

	// Errors is how this server will handle errors returned from any
	// of the handlers in the primary routes. If the primary handler
	// chain returns an error, the error along with its recommended
	// status code are bubbled back up to the HTTP server which
	// executes a separate error route, specified using this property.
	// The error routes work exactly like the normal routes.
	Errors	*HTTPErrorConfig	`json:"errors,omitempty"`

	// NamedRoutes describes a mapping of reusable routes that can be
	// invoked by their name. This can be used to optimize memory usage
	// when the same route is needed for many subroutes, by having
	// the handlers and matchers be only provisioned once, but used from
	// many places. These routes are not executed unless they are invoked
	// from another route.
	//
	// EXPERIMENTAL: Subject to change or removal.
	NamedRoutes	map[string]*Route	`json:"named_routes,omitempty"`

	// How to handle TLS connections. At least one policy is
	// required to enable HTTPS on this server if automatic
	// HTTPS is disabled or does not apply.
	TLSConnPolicies	caddytls.ConnectionPolicies	`json:"tls_connection_policies,omitempty"`

	// AutoHTTPS configures or disables automatic HTTPS within this server.
	// HTTPS is enabled automatically and by default when qualifying names
	// are present in a Host matcher and/or when the server is listening
	// only on the HTTPS port.
	AutoHTTPS	*AutoHTTPSConfig	`json:"automatic_https,omitempty"`

	// If true, will require that a request's Host header match
	// the value of the ServerName sent by the client's TLS
	// ClientHello; often a necessary safeguard when using TLS
	// client authentication.
	StrictSNIHost	*bool	`json:"strict_sni_host,omitempty"`

	// A module which provides a source of IP ranges, from which
	// requests should be trusted. By default, no proxies are
	// trusted.
	//
	// On its own, this configuration will not do anything,
	// but it can be used as a default set of ranges for
	// handlers or matchers in routes to pick up, instead
	// of needing to configure each of them. See the
	// `reverse_proxy` handler for example, which uses this
	// to trust sensitive incoming `X-Forwarded-*` headers.
	TrustedProxiesRaw	json.RawMessage	`json:"trusted_proxies,omitempty" caddy:"namespace=http.ip_sources inline_key=source"`

	// The headers from which the client IP address could be
	// read from. These will be considered in order, with the
	// first good value being used as the client IP.
	// By default, only `X-Forwarded-For` is considered.
	//
	// This depends on `trusted_proxies` being configured and
	// the request being validated as coming from a trusted
	// proxy, otherwise the client IP will be set to the direct
	// remote IP address.
	ClientIPHeaders	[]string	`json:"client_ip_headers,omitempty"`

	// If greater than zero, enables strict ClientIPHeaders
	// (default X-Forwarded-For) parsing. If enabled, the
	// ClientIPHeaders will be parsed from right to left, and
	// the first value that is both valid and doesn't match the
	// trusted proxy list will be used as client IP. If zero,
	// the ClientIPHeaders will be parsed from left to right,
	// and the first value that is a valid IP address will be
	// used as client IP.
	//
	// This depends on `trusted_proxies` being configured.
	// This option is disabled by default.
	TrustedProxiesStrict	int	`json:"trusted_proxies_strict,omitempty"`

	// Enables access logging and configures how access logs are handled
	// in this server. To minimally enable access logs, simply set this
	// to a non-null, empty struct.
	Logs	*ServerLogConfig	`json:"logs,omitempty"`

	// Protocols specifies which HTTP protocols to enable.
	// Supported values are:
	//
	// - `h1` (HTTP/1.1)
	// - `h2` (HTTP/2)
	// - `h2c` (cleartext HTTP/2)
	// - `h3` (HTTP/3)
	//
	// If enabling `h2` or `h2c`, `h1` must also be enabled;
	// this is due to current limitations in the Go standard
	// library.
	//
	// HTTP/2 operates only over TLS (HTTPS). HTTP/3 opens
	// a UDP socket to serve QUIC connections.
	//
	// H2C operates over plain TCP if the client supports it;
	// however, because this is not implemented by the Go
	// standard library, other server options are not compatible
	// and will not be applied to H2C requests. Do not enable this
	// only to achieve maximum client compatibility. In practice,
	// very few clients implement H2C, and even fewer require it.
	// Enabling H2C can be useful for serving/proxying gRPC
	// if encryption is not possible or desired.
	//
	// We recommend for most users to simply let Caddy use the
	// default settings.
	//
	// Default: `[h1 h2 h3]`
	Protocols	[]string	`json:"protocols,omitempty"`

	// ListenProtocols overrides Protocols for each parallel address in Listen.
	// A nil value or element indicates that Protocols will be used instead.
	ListenProtocols	[][]string	`json:"listen_protocols,omitempty"`

	// If set, metrics observations will be enabled.
	// This setting is EXPERIMENTAL and subject to change.
	// DEPRECATED: Use the app-level `metrics` field.
	Metrics	*Metrics	`json:"metrics,omitempty"`

	name	string

	primaryHandlerChain	Handler
	errorHandlerChain	Handler
	listenerWrappers	[]caddy.ListenerWrapper
	listeners		[]net.Listener

	tlsApp		*caddytls.TLS
	events		*caddyevents.App
	logger		*zap.Logger
	accessLogger	*zap.Logger
	errorLogger	*zap.Logger
	traceLogger	*zap.Logger
	ctx		caddy.Context

	server		*http.Server
	h3server	*http3.Server
	h2listeners	[]*http2Listener
	addresses	[]caddy.NetworkAddress

	trustedProxies	IPRangeSource

	shutdownAt	time.Time
	shutdownAtMu	*sync.RWMutex

	// registered callback functions
	connStateFuncs		[]func(net.Conn, http.ConnState)
	connContextFuncs	[]func(ctx context.Context, c net.Conn) context.Context
	onShutdownFuncs		[]func()
	onStopFuncs		[]func(context.Context) error	// TODO: Experimental (Nov. 2023)
}

ServerLogConfig

ServerLogConfig describes a server's logging configuration. If enabled without customization, all requests to this server are logged to the default logger; logger destinations may be customized per-request-host.

type ServerLogConfig struct {
	// The default logger name for all logs emitted by this server for
	// hostnames that are not in the logger_names map.
	DefaultLoggerName	string	`json:"default_logger_name,omitempty"`

	// LoggerNames maps request hostnames to one or more custom logger
	// names. For example, a mapping of `"example.com": ["example"]` would
	// cause access logs from requests with a Host of example.com to be
	// emitted by a logger named "http.log.access.example". If there are
	// multiple logger names, then the log will be emitted to all of them.
	// If the logger name is an empty, the default logger is used, i.e.
	// the logger "http.log.access".
	//
	// Keys must be hostnames (without ports), and may contain wildcards
	// to match subdomains. The value is an array of logger names.
	//
	// For backwards compatibility, if the value is a string, it is treated
	// as a single-element array.
	LoggerNames	map[string]StringArray	`json:"logger_names,omitempty"`

	// By default, all requests to this server will be logged if
	// access logging is enabled. This field lists the request
	// hosts for which access logging should be disabled.
	SkipHosts	[]string	`json:"skip_hosts,omitempty"`

	// If true, requests to any host not appearing in the
	// logger_names map will not be logged.
	SkipUnmappedHosts	bool	`json:"skip_unmapped_hosts,omitempty"`

	// If true, credentials that are otherwise omitted, will be logged.
	// The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials,
	// and this includes some request and response headers, i.e `Cookie`,
	// `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
	ShouldLogCredentials	bool	`json:"should_log_credentials,omitempty"`

	// Log each individual handler that is invoked.
	// Requires that the log emit at DEBUG level.
	//
	// NOTE: This may log the configuration of your
	// HTTP handler modules; do not enable this in
	// insecure contexts when there is sensitive
	// data in the configuration.
	//
	// EXPERIMENTAL: Subject to change or removal.
	Trace	bool	`json:"trace,omitempty"`
}

ShouldBufferFunc

ShouldBufferFunc is a function that returns true if the response should be buffered, given the pending HTTP status code and response headers.

type ShouldBufferFunc func(status int, header http.Header) bool

StaticError

StaticError implements a simple handler that returns an error. This handler returns an error value, but does not write a response. This is useful when you want the server to act as if an error occurred; for example, to invoke your custom error handling logic.

Since this handler does not write a response, the error information is for use by the server to know how to handle the error.

type StaticError struct {
	// The error message. Optional. Default is no error message.
	Error	string	`json:"error,omitempty"`

	// The recommended HTTP status code. Can be either an integer or a
	// string if placeholders are needed. Optional. Default is 500.
	StatusCode	WeakString	`json:"status_code,omitempty"`
}

StaticIPRange

StaticIPRange provides a static range of IP address prefixes (CIDRs).

type StaticIPRange struct {
	// A static list of IP ranges (supports CIDR notation).
	Ranges	[]string	`json:"ranges,omitempty"`

	// Holds the parsed CIDR ranges from Ranges.
	ranges	[]netip.Prefix
}

StaticResponse

StaticResponse implements a simple responder for static responses.

type StaticResponse struct {
	// The HTTP status code to respond with. Can be an integer or,
	// if needing to use a placeholder, a string.
	//
	// If the status code is 103 (Early Hints), the response headers
	// will be written to the client immediately, the body will be
	// ignored, and the next handler will be invoked. This behavior
	// is EXPERIMENTAL while RFC 8297 is a draft, and may be changed
	// or removed.
	StatusCode	WeakString	`json:"status_code,omitempty"`

	// Header fields to set on the response; overwrites any existing
	// header fields of the same names after normalization.
	Headers	http.Header	`json:"headers,omitempty"`

	// The response body. If non-empty, the Content-Type header may
	// be added automatically if it is not explicitly configured nor
	// already set on the response; the default value is
	// "text/plain; charset=utf-8" unless the body is a valid JSON object
	// or array, in which case the value will be "application/json".
	// Other than those common special cases the Content-Type header
	// should be set explicitly if it is desired because MIME sniffing
	// is disabled for safety.
	Body	string	`json:"body,omitempty"`

	// If true, the server will close the client's connection
	// after writing the response.
	Close	bool	`json:"close,omitempty"`

	// Immediately and forcefully closes the connection without
	// writing a response. Interrupts any other HTTP streams on
	// the same connection.
	Abort	bool	`json:"abort,omitempty"`
}

StringArray

StringArray is a slices of strings, but also accepts a single string as a value when JSON unmarshaling, converting it to a slice of one string.

type StringArray []string

Subroute

Subroute implements a handler that compiles and executes routes. This is useful for a batch of routes that all inherit the same matchers, or for multiple routes that should be treated as a single route.

You can also use subroutes to handle errors from its handlers. First the primary routes will be executed, and if they return an error, the errors routes will be executed; in that case, an error is only returned to the entry point at the server if there is an additional error returned from the errors routes.

type Subroute struct {
	// The primary list of routes to compile and execute.
	Routes	RouteList	`json:"routes,omitempty"`

	// If the primary routes return an error, error handling
	// can be promoted to this configuration instead.
	Errors	*HTTPErrorConfig	`json:"errors,omitempty"`
}

VarsMatcher

VarsMatcher is an HTTP request matcher which can match requests based on variables in the context or placeholder values. The key is the placeholder or name of the variable, and the values are possible values the variable can be in order to match (logical OR'ed).

If the key is surrounded by { } it is assumed to be a placeholder. Otherwise, it will be considered a variable name.

Placeholders in the keys are not expanded, but placeholders in the values are.

type VarsMatcher map[string][]string

VarsMiddleware

VarsMiddleware is an HTTP middleware which sets variables to have values that can be used in the HTTP request handler chain. The primary way to access variables is with placeholders, which have the form: {http.vars.variable_name}, or with the vars and vars_regexp request matchers.

The key is the variable name, and the value is the value of the variable. Both the name and value may use or contain placeholders.

type VarsMiddleware map[string]any

WeakString

WeakString is a type that unmarshals any JSON value as a string literal, with the following exceptions:

  1. actual string values are decoded as strings; and
  2. null is decoded as empty string;

and provides methods for getting the value as various primitive types. However, using this type removes any type safety as far as deserializing JSON is concerned.

type WeakString string

MatchHost, MatchPath, MatchPathRE, MatchMethod, MatchQuery, MatchHeader, MatchHeaderRE, MatchProtocol, MatchTLS, MatchNot

This type doesn't have documentation.

type (
	// MatchHost matches requests by the Host value (case-insensitive).
	//
	// When used in a top-level HTTP route,
	// [qualifying domain names](/docs/automatic-https#hostname-requirements)
	// may trigger [automatic HTTPS](/docs/automatic-https), which automatically
	// provisions and renews certificates for you. Before doing this, you
	// should ensure that DNS records for these domains are properly configured,
	// especially A/AAAA pointed at your server.
	//
	// Automatic HTTPS can be
	// [customized or disabled](/docs/modules/http#servers/automatic_https).
	//
	// Wildcards (`*`) may be used to represent exactly one label of the
	// hostname, in accordance with RFC 1034 (because host matchers are also
	// used for automatic HTTPS which influences TLS certificates). Thus,
	// a host of `*` matches hosts like `localhost` or `internal` but not
	// `example.com`. To catch all hosts, omit the host matcher entirely.
	//
	// The wildcard can be useful for matching all subdomains, for example:
	// `*.example.com` matches `foo.example.com` but not `foo.bar.example.com`.
	//
	// Duplicate entries will return an error.
	MatchHost	[]string

	// MatchPath case-insensitively matches requests by the URI's path. Path
	// matching is exact, not prefix-based, giving you more control and clarity
	// over matching. Wildcards (`*`) may be used:
	//
	// - At the end only, for a prefix match (`/prefix/*`)
	// - At the beginning only, for a suffix match (`*.suffix`)
	// - On both sides only, for a substring match (`*/contains/*`)
	// - In the middle, for a globular match (`/accounts/*/info`)
	//
	// Slashes are significant; i.e. `/foo*` matches `/foo`, `/foo/`, `/foo/bar`,
	// and `/foobar`; but `/foo/*` does not match `/foo` or `/foobar`. Valid
	// paths start with a slash `/`.
	//
	// Because there are, in general, multiple possible escaped forms of any
	// path, path matchers operate in unescaped space; that is, path matchers
	// should be written in their unescaped form to prevent ambiguities and
	// possible security issues, as all request paths will be normalized to
	// their unescaped forms before matcher evaluation.
	//
	// However, escape sequences in a match pattern are supported; they are
	// compared with the request's raw/escaped path for those bytes only.
	// In other words, a matcher of `/foo%2Fbar` will match a request path
	// of precisely `/foo%2Fbar`, but not `/foo/bar`. It follows that matching
	// the literal percent sign (%) in normalized space can be done using the
	// escaped form, `%25`.
	//
	// Even though wildcards (`*`) operate in the normalized space, the special
	// escaped wildcard (`%*`), which is not a valid escape sequence, may be
	// used in place of a span that should NOT be decoded; that is, `/bands/%*`
	// will match `/bands/AC%2fDC` whereas `/bands/*` will not.
	//
	// Even though path matching is done in normalized space, the special
	// wildcard `%*` may be used in place of a span that should NOT be decoded;
	// that is, `/bands/%*/` will match `/bands/AC%2fDC/` whereas `/bands/*/`
	// will not.
	//
	// This matcher is fast, so it does not support regular expressions or
	// capture groups. For slower but more powerful matching, use the
	// path_regexp matcher. (Note that due to the special treatment of
	// escape sequences in matcher patterns, they may perform slightly slower
	// in high-traffic environments.)
	MatchPath	[]string

	// MatchPathRE matches requests by a regular expression on the URI's path.
	// Path matching is performed in the unescaped (decoded) form of the path.
	//
	// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
	// where `name` is the regular expression's name, and `capture_group` is either
	// the named or positional capture group from the expression itself. If no name
	// is given, then the placeholder omits the name: `{http.regexp.capture_group}`
	// (potentially leading to collisions).
	MatchPathRE	struct{ MatchRegexp }

	// MatchMethod matches requests by the method.
	MatchMethod	[]string

	// MatchQuery matches requests by the URI's query string. It takes a JSON object
	// keyed by the query keys, with an array of string values to match for that key.
	// Query key matches are exact, but wildcards may be used for value matches. Both
	// keys and values may be placeholders.
	//
	// An example of the structure to match `?key=value&topic=api&query=something` is:
	//
	// ```json
	// {
	// 	"key": ["value"],
	//	"topic": ["api"],
	//	"query": ["*"]
	// }
	// ```
	//
	// Invalid query strings, including those with bad escapings or illegal characters
	// like semicolons, will fail to parse and thus fail to match.
	//
	// **NOTE:** Notice that query string values are arrays, not singular values. This is
	// because repeated keys are valid in query strings, and each one may have a
	// different value. This matcher will match for a key if any one of its configured
	// values is assigned in the query string. Backend applications relying on query
	// strings MUST take into consideration that query string values are arrays and can
	// have multiple values.
	MatchQuery	url.Values

	// MatchHeader matches requests by header fields. The key is the field
	// name and the array is the list of field values. It performs fast,
	// exact string comparisons of the field values. Fast prefix, suffix,
	// and substring matches can also be done by suffixing, prefixing, or
	// surrounding the value with the wildcard `*` character, respectively.
	// If a list is null, the header must not exist. If the list is empty,
	// the field must simply exist, regardless of its value.
	//
	// **NOTE:** Notice that header values are arrays, not singular values. This is
	// because repeated fields are valid in headers, and each one may have a
	// different value. This matcher will match for a field if any one of its configured
	// values matches in the header. Backend applications relying on headers MUST take
	// into consideration that header field values are arrays and can have multiple
	// values.
	MatchHeader	http.Header

	// MatchHeaderRE matches requests by a regular expression on header fields.
	//
	// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
	// where `name` is the regular expression's name, and `capture_group` is either
	// the named or positional capture group from the expression itself. If no name
	// is given, then the placeholder omits the name: `{http.regexp.capture_group}`
	// (potentially leading to collisions).
	MatchHeaderRE	map[string]*MatchRegexp

	// MatchProtocol matches requests by protocol. Recognized values are
	// "http", "https", and "grpc" for broad protocol matches, or specific
	// HTTP versions can be specified like so: "http/1", "http/1.1",
	// "http/2", "http/3", or minimum versions: "http/2+", etc.
	MatchProtocol	string

	// MatchTLS matches HTTP requests based on the underlying
	// TLS connection state. If this matcher is specified but
	// the request did not come over TLS, it will never match.
	// If this matcher is specified but is empty and the request
	// did come in over TLS, it will always match.
	MatchTLS	struct {
		// Matches if the TLS handshake has completed. QUIC 0-RTT early
		// data may arrive before the handshake completes. Generally, it
		// is unsafe to replay these requests if they are not idempotent;
		// additionally, the remote IP of early data packets can more
		// easily be spoofed. It is conventional to respond with HTTP 425
		// Too Early if the request cannot risk being processed in this
		// state.
		HandshakeComplete *bool `json:"handshake_complete,omitempty"`
	}

	// MatchNot matches requests by negating the results of its matcher
	// sets. A single "not" matcher takes one or more matcher sets. Each
	// matcher set is OR'ed; in other words, if any matcher set returns
	// true, the final result of the "not" matcher is false. Individual
	// matchers within a set work the same (i.e. different matchers in
	// the same set are AND'ed).
	//
	// NOTE: The generated docs which describe the structure of this
	// module are wrong because of how this type unmarshals JSON in a
	// custom way. The correct structure is:
	//
	// ```json
	// [
	// 	{},
	// 	{}
	// ]
	// ```
	//
	// where each of the array elements is a matcher set, i.e. an
	// object keyed by matcher name.
	MatchNot	struct {
		MatcherSetsRaw	[]caddy.ModuleMap	`json:"-" caddy:"namespace=http.matchers"`
		MatcherSets	[]MatcherSet		`json:"-"`
	}
)

acmeCapable

This type doesn't have documentation.

type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }

celHTTPRequest

celHTTPRequest wraps an http.Request with ref.Val interface methods.

This type also implements the interpreter.Activation interface which drops allocation costs for CEL expression evaluations by roughly half.

type celHTTPRequest struct{ *http.Request }

celPkixName

celPkixName wraps an pkix.Name with methods to satisfy the ref.Val interface.

type celPkixName struct{ *pkix.Name }

celTypeAdapter

celTypeAdapter can adapt our custom types to a CEL value.

type celTypeAdapter struct{}

connectionStateConn

This type doesn't have documentation.

type connectionStateConn interface {
	net.Conn
	ConnectionState() tls.ConnectionState
}

hijackedConn

used to track the size of hijacked response writers

type hijackedConn struct {
	net.Conn
	rr	*responseRecorder
}

http2Listener

http2Listener wraps the listener to solve the following problems:

  1. server h2 natively without using h2c hack when listener handles tls connection but don't return *tls.Conn
  2. graceful shutdown. the shutdown logic is copied from stdlib http.Server, it's an extra maintenance burden but whatever, the shutdown logic maybe extracted to be used with h2c graceful shutdown. http2.Server supports graceful shutdown sending GO_AWAY frame to connected clients, but doesn't track connection status. It requires explicit call of http2.ConfigureServer

type http2Listener struct {
	cnt	uint64
	net.Listener
	server		*http.Server
	h2server	*http2.Server
}

httpMetrics

This type doesn't have documentation.

type httpMetrics struct {
	requestInFlight		*prometheus.GaugeVec
	requestCount		*prometheus.CounterVec
	requestErrors		*prometheus.CounterVec
	requestDuration		*prometheus.HistogramVec
	requestSize		*prometheus.HistogramVec
	responseSize		*prometheus.HistogramVec
	responseDuration	*prometheus.HistogramVec
}

httpRedirectConn

This type doesn't have documentation.

type httpRedirectConn struct {
	net.Conn
	once	bool
	limit	int64
	r	*bufio.Reader
}

httpRedirectListener

httpRedirectListener is listener that checks the first few bytes of the request when the server is intended to accept HTTPS requests, to respond to an HTTP request with a redirect.

type httpRedirectListener struct {
	net.Listener
	maxHeaderBytes	int64
}

lengthReader

lengthReader is an io.ReadCloser that keeps track of the number of bytes read from the request body.

type lengthReader struct {
	Source	io.ReadCloser
	Length	int
}

matcherCELLibrary

matcherCELLibrary is a simplistic configurable cel.Library implementation.

type matcherCELLibrary struct {
	envOptions	[]cel.EnvOption
	programOptions	[]cel.ProgramOption
}

metricsInstrumentedHandler

This type doesn't have documentation.

type metricsInstrumentedHandler struct {
	handler	string
	mh	MiddlewareHandler
	metrics	*Metrics
}

requestID

This type doesn't have documentation.

type requestID struct {
	value string
}

responseRecorder

This type doesn't have documentation.

type responseRecorder struct {
	*ResponseWriterWrapper
	statusCode	int
	buf		*bytes.Buffer
	shouldBuffer	ShouldBufferFunc
	size		int
	wroteHeader	bool
	stream		bool

	readSize	*int
}

tlsPlaceholderWrapper

tlsPlaceholderWrapper is a no-op listener wrapper that marks where the TLS listener should be in a chain of listener wrappers. It should only be used if another listener wrapper must be placed in front of the TLS handshake.

type tlsPlaceholderWrapper struct{}

Functions

func CELMatcherDecorator

CELMatcherDecorator matches a call overload generated by a CEL macro that takes a single argument, and optimizes the implementation to precompile the matcher and return a function that references the precompiled and provisioned matcher.

func CELMatcherDecorator(funcName string, fac any) interpreter.InterpretableDecorator {
	return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
		call, ok := i.(interpreter.InterpretableCall)
		if !ok {
			return i, nil
		}
		if call.OverloadID() != funcName {
			return i, nil
		}
		callArgs := call.Args()
		reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute)
		if !ok {
			return nil, errors.New("missing 'req' argument")
		}
		nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute)
		if !ok {
			return nil, errors.New("missing 'req' argument")
		}
		varNames := nsAttr.CandidateVariableNames()
		if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != CELRequestVarName {
			return nil, errors.New("missing 'req' argument")
		}
		matcherData, ok := callArgs[1].(interpreter.InterpretableConst)
		if !ok {
			// If the matcher arguments are not constant, then this means
			// they contain a Caddy placeholder reference and the evaluation
			// and matcher provisioning should be handled at dynamically.
			return i, nil
		}

		if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
			matcher, err := factory(matcherData.Value())
			if err != nil {
				return nil, err
			}
			return interpreter.NewCall(
				i.ID(), funcName, funcName+"_opt",
				[]interpreter.Interpretable{reqAttr},
				func(args ...ref.Val) ref.Val {
					// The request value, guaranteed to be of type celHTTPRequest
					celReq := args[0]
					// If needed this call could be changed to convert the value
					// to a *http.Request using CEL's ConvertToNative method.
					httpReq := celReq.Value().(celHTTPRequest)
					match, err := matcher.MatchWithError(httpReq.Request)
					if err != nil {
						return types.WrapErr(err)
					}
					return types.Bool(match)
				},
			), nil
		}

		if factory, ok := fac.(CELMatcherFactory); ok {
			matcher, err := factory(matcherData.Value())
			if err != nil {
				return nil, err
			}
			return interpreter.NewCall(
				i.ID(), funcName, funcName+"_opt",
				[]interpreter.Interpretable{reqAttr},
				func(args ...ref.Val) ref.Val {
					// The request value, guaranteed to be of type celHTTPRequest
					celReq := args[0]
					// If needed this call could be changed to convert the value
					// to a *http.Request using CEL's ConvertToNative method.
					httpReq := celReq.Value().(celHTTPRequest)
					if m, ok := matcher.(RequestMatcherWithError); ok {
						match, err := m.MatchWithError(httpReq.Request)
						if err != nil {
							return types.WrapErr(err)
						}
						return types.Bool(match)
					}
					return types.Bool(matcher.Match(httpReq.Request))
				},
			), nil
		}

		return nil, fmt.Errorf("invalid matcher factory, must be CELMatcherFactory or CELMatcherWithErrorFactory: %T", fac)
	}
}

Cognitive complexity: 31, Cyclomatic complexity: 16

Uses: errors.New, fmt.Errorf, interpreter.Interpretable, interpreter.InterpretableAttribute, interpreter.InterpretableCall, interpreter.InterpretableConst, interpreter.NamespacedAttribute, interpreter.NewCall, ref.Val, types.Bool, types.WrapErr.

func CELMatcherImpl

CELMatcherImpl creates a new cel.Library based on the following pieces of data:

  • macroName: the function name to be used within CEL. This will be a macro and not a function proper.
  • funcName: the function overload name generated by the CEL macro used to represent the matcher.
  • matcherDataTypes: the argument types to the macro.
  • fac: a matcherFactory implementation which converts from CEL constant values to a Matcher instance.

Note, macro names and function names must not collide with other macros or functions exposed within CEL expressions, or an error will be produced during the expression matcher plan time.

The existing CELMatcherImpl support methods are configured to support a limited set of function signatures. For strong type validation you may need to provide a custom macro which does a more detailed analysis of the CEL literal provided to the macro as an argument.

func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac any) (cel.Library, error) {
	requestType := cel.ObjectType("http.Request")
	var macro parser.Macro
	switch len(matcherDataTypes) {
	case 1:
		matcherDataType := matcherDataTypes[0]
		switch matcherDataType.String() {
		case "list(string)":
			macro = parser.NewGlobalVarArgMacro(macroName, celMatcherStringListMacroExpander(funcName))
		case cel.StringType.String():
			macro = parser.NewGlobalMacro(macroName, 1, celMatcherStringMacroExpander(funcName))
		case CELTypeJSON.String():
			macro = parser.NewGlobalMacro(macroName, 1, celMatcherJSONMacroExpander(funcName))
		default:
			return nil, fmt.Errorf("unsupported matcher data type: %s", matcherDataType)
		}
	case 2:
		if matcherDataTypes[0] == cel.StringType && matcherDataTypes[1] == cel.StringType {
			macro = parser.NewGlobalMacro(macroName, 2, celMatcherStringListMacroExpander(funcName))
			matcherDataTypes = []*cel.Type{cel.ListType(cel.StringType)}
		} else {
			return nil, fmt.Errorf("unsupported matcher data type: %s, %s", matcherDataTypes[0], matcherDataTypes[1])
		}
	case 3:
		if matcherDataTypes[0] == cel.StringType && matcherDataTypes[1] == cel.StringType && matcherDataTypes[2] == cel.StringType {
			macro = parser.NewGlobalMacro(macroName, 3, celMatcherStringListMacroExpander(funcName))
			matcherDataTypes = []*cel.Type{cel.ListType(cel.StringType)}
		} else {
			return nil, fmt.Errorf("unsupported matcher data type: %s, %s, %s", matcherDataTypes[0], matcherDataTypes[1], matcherDataTypes[2])
		}
	}
	envOptions := []cel.EnvOption{
		cel.Macros(macro),
		cel.Function(funcName,
			cel.Overload(funcName, append([]*cel.Type{requestType}, matcherDataTypes...), cel.BoolType),
			cel.SingletonBinaryBinding(CELMatcherRuntimeFunction(funcName, fac))),
	}
	programOptions := []cel.ProgramOption{
		cel.CustomDecorator(CELMatcherDecorator(funcName, fac)),
	}
	return NewMatcherCELLibrary(envOptions, programOptions), nil
}

Cognitive complexity: 23, Cyclomatic complexity: 14

Uses: cel.BoolType, cel.CustomDecorator, cel.EnvOption, cel.Function, cel.ListType, cel.Macros, cel.ObjectType, cel.Overload, cel.ProgramOption, cel.SingletonBinaryBinding, cel.StringType, cel.Type, fmt.Errorf, parser.Macro, parser.NewGlobalMacro, parser.NewGlobalVarArgMacro.

func CELMatcherRuntimeFunction

CELMatcherRuntimeFunction creates a function binding for when the input to the matcher is dynamically resolved rather than a set of static constant values.

func CELMatcherRuntimeFunction(funcName string, fac any) functions.BinaryOp {
	return func(celReq, matcherData ref.Val) ref.Val {
		if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
			matcher, err := factory(matcherData)
			if err != nil {
				return types.WrapErr(err)
			}
			httpReq := celReq.Value().(celHTTPRequest)
			match, err := matcher.MatchWithError(httpReq.Request)
			if err != nil {
				return types.WrapErr(err)
			}
			return types.Bool(match)
		}
		if factory, ok := fac.(CELMatcherFactory); ok {
			matcher, err := factory(matcherData)
			if err != nil {
				return types.WrapErr(err)
			}
			httpReq := celReq.Value().(celHTTPRequest)
			if m, ok := matcher.(RequestMatcherWithError); ok {
				match, err := m.MatchWithError(httpReq.Request)
				if err != nil {
					return types.WrapErr(err)
				}
				return types.Bool(match)
			}
			return types.Bool(matcher.Match(httpReq.Request))
		}
		return types.NewErr("CELMatcherRuntimeFunction invalid matcher factory: %T", fac)
	}
}

Cognitive complexity: 15, Cyclomatic complexity: 8

Uses: ref.Val, types.Bool, types.NewErr, types.WrapErr.

func CELValueToMapStrList

CELValueToMapStrList converts a CEL value to a map[string][]string

Earlier validation stages should guarantee that the value has this type at compile time, and that the runtime value type is map[string]any. The reason for the slight difference in value type is that CEL allows for map literals containing heterogeneous values, in this case string and list of string.

func CELValueToMapStrList(data ref.Val) (map[string][]string, error) {
	mapStrType := reflect.TypeOf(map[string]any{})
	mapStrRaw, err := data.ConvertToNative(mapStrType)
	if err != nil {
		return nil, err
	}
	mapStrIface := mapStrRaw.(map[string]any)
	mapStrListStr := make(map[string][]string, len(mapStrIface))
	for k, v := range mapStrIface {
		switch val := v.(type) {
		case string:
			mapStrListStr[k] = []string{val}
		case types.String:
			mapStrListStr[k] = []string{string(val)}
		case []string:
			mapStrListStr[k] = val
		case []ref.Val:
			convVals := make([]string, len(val))
			for i, elem := range val {
				strVal, ok := elem.(types.String)
				if !ok {
					return nil, fmt.Errorf("unsupported value type in header match: %T", val)
				}
				convVals[i] = string(strVal)
			}
			mapStrListStr[k] = convVals
		default:
			return nil, fmt.Errorf("unsupported value type in header match: %T", val)
		}
	}
	return mapStrListStr, nil
}

Cognitive complexity: 19, Cyclomatic complexity: 10

Uses: fmt.Errorf, ref.Val, reflect.TypeOf, types.String.

func CIDRExpressionToPrefix

CIDRExpressionToPrefix takes a string which could be either a CIDR expression or a single IP address, and returns a netip.Prefix.

func CIDRExpressionToPrefix(expr string) (netip.Prefix, error) {
	// Having a slash means it should be a CIDR expression
	if strings.Contains(expr, "/") {
		prefix, err := netip.ParsePrefix(expr)
		if err != nil {
			return netip.Prefix{}, fmt.Errorf("parsing CIDR expression: '%s': %v", expr, err)
		}
		return prefix, nil
	}

	// Otherwise it's likely a single IP address
	parsed, err := netip.ParseAddr(expr)
	if err != nil {
		return netip.Prefix{}, fmt.Errorf("invalid IP address: '%s': %v", expr, err)
	}
	prefix := netip.PrefixFrom(parsed, parsed.BitLen())
	return prefix, nil
}

Cognitive complexity: 8, Cyclomatic complexity: 4

Uses: fmt.Errorf, netip.ParseAddr, netip.ParsePrefix, netip.Prefix, netip.PrefixFrom, strings.Contains.

func CleanPath

CleanPath cleans path p according to path.Clean(), but only merges repeated slashes if collapseSlashes is true, and always preserves trailing slashes.

func CleanPath(p string, collapseSlashes bool) string {
	if collapseSlashes {
		return cleanPath(p)
	}

	// insert an invalid/impossible URI character into each two consecutive
	// slashes to expand empty path segments; then clean the path as usual,
	// and then remove the remaining temporary characters.
	const tmpCh = 0xff
	var sb strings.Builder
	for i, ch := range p {
		if ch == '/' && i > 0 && p[i-1] == '/' {
			sb.WriteByte(tmpCh)
		}
		sb.WriteRune(ch)
	}
	halfCleaned := cleanPath(sb.String())
	halfCleaned = strings.ReplaceAll(halfCleaned, string([]byte{tmpCh}), "")

	return halfCleaned
}

Cognitive complexity: 8, Cyclomatic complexity: 6

Uses: strings.Builder, strings.ReplaceAll.

func Error

Error is a convenient way for a Handler to populate the essential fields of a HandlerError. If err is itself a HandlerError, then any essential fields that are not set will be populated.

func Error(statusCode int, err error) HandlerError {
	const idLen = 9
	var he HandlerError
	if errors.As(err, &he) {
		if he.ID == "" {
			he.ID = randString(idLen, true)
		}
		if he.Trace == "" {
			he.Trace = trace()
		}
		if he.StatusCode == 0 {
			he.StatusCode = statusCode
		}
		return he
	}
	return HandlerError{
		ID:		randString(idLen, true),
		StatusCode:	statusCode,
		Err:		err,
		Trace:		trace(),
	}
}

Cognitive complexity: 9, Cyclomatic complexity: 5

Uses: errors.As.

func GetVar

GetVar gets a value out of the context's variable table by key. If the key does not exist, the return value will be nil.

func GetVar(ctx context.Context, key string) any {
	varMap, ok := ctx.Value(VarsCtxKey).(map[string]any)
	if !ok {
		return nil
	}
	return varMap[key]
}

Cognitive complexity: 2, Cyclomatic complexity: 2

func NewMatcherCELLibrary

NewMatcherCELLibrary creates a matcherLibrary from option setes.

func NewMatcherCELLibrary(envOptions []cel.EnvOption, programOptions []cel.ProgramOption) cel.Library {
	return &matcherCELLibrary{
		envOptions:	envOptions,
		programOptions:	programOptions,
	}
}

Cognitive complexity: 1, Cyclomatic complexity: 1

func NewResponseRecorder

NewResponseRecorder returns a new ResponseRecorder that can be used instead of a standard http.ResponseWriter. The recorder is useful for middlewares which need to buffer a response and potentially process its entire body before actually writing the response to the underlying writer. Of course, buffering the entire body has a memory overhead, but sometimes there is no way to avoid buffering the whole response, hence the existence of this type. Still, if at all practical, handlers should strive to stream responses by wrapping Write and WriteHeader methods instead of buffering whole response bodies.

Buffering is actually optional. The shouldBuffer function will be called just before the headers are written. If it returns true, the headers and body will be buffered by this recorder and not written to the underlying writer; if false, the headers will be written immediately and the body will be streamed out directly to the underlying writer. If shouldBuffer is nil, the response will never be buffered and will always be streamed directly to the writer.

You can know if shouldBuffer returned true by calling Buffered().

The provided buffer buf should be obtained from a pool for best performance (see the sync.Pool type).

Proper usage of a recorder looks like this:

rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuffer)
err := next.ServeHTTP(rec, req)
if err != nil {
    return err
}
if !rec.Buffered() {
    return nil
}
// process the buffered response here

The header map is not buffered; i.e. the ResponseRecorder's Header() method returns the same header map of the underlying ResponseWriter. This is a crucial design decision to allow HTTP trailers to be flushed properly (https://github.com/caddyserver/caddy/issues/3236).

Once you are ready to write the response, there are two ways you can do it. The easier way is to have the recorder do it:

rec.WriteResponse()

This writes the recorded response headers as well as the buffered body. Or, you may wish to do it yourself, especially if you manipulated the buffered body. First you will need to write the headers with the recorded status code, then write the body (this example writes the recorder's body buffer, but you might have your own body to write instead):

w.WriteHeader(rec.Status())
io.Copy(w, rec.Buffer())

As a special case, 1xx responses are not buffered nor recorded because they are not the final response; they are passed through directly to the underlying ResponseWriter.

func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shouldBuffer ShouldBufferFunc) ResponseRecorder {
	return &responseRecorder{
		ResponseWriterWrapper:	&ResponseWriterWrapper{ResponseWriter: w},
		buf:			buf,
		shouldBuffer:		shouldBuffer,
	}
}

Cognitive complexity: 2, Cyclomatic complexity: 1

func NewTestReplacer

NewTestReplacer creates a replacer for an http.Request for use in tests that are not in this package

func NewTestReplacer(req *http.Request) *caddy.Replacer {
	repl := caddy.NewReplacer()
	ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
	*req = *req.WithContext(ctx)
	addHTTPVarsToReplacer(repl, req, nil)
	return repl
}

Cognitive complexity: 0, Cyclomatic complexity: 1

Uses: context.WithValue.

func ParseCaddyfileNestedMatcherSet

ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested matcher set, and returns its raw module map value.

func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
	matcherMap := make(map[string]any)

	// in case there are multiple instances of the same matcher, concatenate
	// their tokens (we expect that UnmarshalCaddyfile should be able to
	// handle more than one segment); otherwise, we'd overwrite other
	// instances of the matcher in this set
	tokensByMatcherName := make(map[string][]caddyfile.Token)
	for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
		matcherName := d.Val()
		tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
	}

	for matcherName, tokens := range tokensByMatcherName {
		mod, err := caddy.GetModule("http.matchers." + matcherName)
		if err != nil {
			return nil, d.Errf("getting matcher module '%s': %v", matcherName, err)
		}
		unm, ok := mod.New().(caddyfile.Unmarshaler)
		if !ok {
			return nil, d.Errf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
		}
		err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens))
		if err != nil {
			return nil, err
		}
		if rm, ok := unm.(RequestMatcherWithError); ok {
			matcherMap[matcherName] = rm
			continue
		}
		if rm, ok := unm.(RequestMatcher); ok {
			matcherMap[matcherName] = rm
			continue
		}
		return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
	}

	// we should now have a functional matcher, but we also
	// need to be able to marshal as JSON, otherwise config
	// adaptation will be missing the matchers!
	matcherSet := make(caddy.ModuleMap)
	for name, matcher := range matcherMap {
		jsonBytes, err := json.Marshal(matcher)
		if err != nil {
			return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
		}
		matcherSet[name] = jsonBytes
	}

	return matcherSet, nil
}

Cognitive complexity: 20, Cyclomatic complexity: 11

Uses: caddyfile.NewDispenser, caddyfile.Token, caddyfile.Unmarshaler, fmt.Errorf, json.Marshal.

func ParseNamedResponseMatcher

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 {
	d.Next()	// consume matcher name
	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
}

Cognitive complexity: 26, Cyclomatic complexity: 15

Uses: http.Header, strconv.Atoi, strings.HasSuffix.

func PrepareRequest

PrepareRequest fills the request r for use in a Caddy HTTP handler chain. w and s can be nil, but the handlers will lose response placeholders and access to the server.

func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter, s *Server) *http.Request {
	// set up the context for the request
	ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl)
	ctx = context.WithValue(ctx, ServerCtxKey, s)

	trusted, clientIP := determineTrustedProxy(r, s)
	ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{
		TrustedProxyVarKey:	trusted,
		ClientIPVarKey:		clientIP,
	})

	ctx = context.WithValue(ctx, routeGroupCtxKey, make(map[string]struct{}))

	var url2 url.URL	// avoid letting this escape to the heap
	ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2))

	ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields))
	r = r.WithContext(ctx)

	// once the pointer to the request won't change
	// anymore, finish setting up the replacer
	addHTTPVarsToReplacer(repl, r, w)

	return r
}

Cognitive complexity: 2, Cyclomatic complexity: 1

Uses: context.WithValue, url.URL.

func PrivateRangesCIDR

PrivateRangesCIDR returns a list of private CIDR range strings, which can be used as a configuration shortcut. Note: this function is used at least by mholt/caddy-l4.

func PrivateRangesCIDR() []string {
	return internal.PrivateRangesCIDR()
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func RegisterNetworkHTTP3

RegisterNetworkHTTP3 registers a mapping from non-HTTP/3 network to HTTP/3 network. This should be called during init() and will panic if the network type is standard, reserved, or already registered.

EXPERIMENTAL: Subject to change.

func RegisterNetworkHTTP3(originalNetwork, h3Network string) {
	if _, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)]; ok {
		panic("network type " + originalNetwork + " is already registered")
	}
	networkTypesHTTP3[originalNetwork] = h3Network
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: strings.ToLower.

func SanitizedPathJoin

SanitizedPathJoin performs filepath.Join(root, reqPath) that is safe against directory traversal attacks. It uses logic similar to that in the Go standard library, specifically in the implementation of http.Dir. The root is assumed to be a trusted path, but reqPath is not; and the output will never be outside of root. The resulting path can be used with the local file system. If root is empty, the current directory is assumed. If the cleaned request path is deemed not local according to lexical processing (i.e. ignoring links), it will be rejected as unsafe and only the root will be returned.

func SanitizedPathJoin(root, reqPath string) string {
	if root == "" {
		root = "."
	}

	relPath := path.Clean("/" + reqPath)[1:]	// clean path and trim the leading /
	if relPath != "" && !filepath.IsLocal(relPath) {
		// path is unsafe (see https://github.com/golang/go/issues/56336#issuecomment-1416214885)
		return root
	}

	path := filepath.Join(root, filepath.FromSlash(relPath))

	// filepath.Join also cleans the path, and cleaning strips
	// the trailing slash, so we need to re-add it afterwards.
	// if the length is 1, then it's a path to the root,
	// and that should return ".", so we don't append the separator.
	if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
		path += separator
	}

	return path
}

Cognitive complexity: 6, Cyclomatic complexity: 6

Uses: filepath.FromSlash, filepath.IsLocal, filepath.Join, path.Clean, strings.HasSuffix.

func SetVar

SetVar sets a value in the context's variable table with the given key. It overwrites any previous value with the same key.

If the value is nil (note: non-nil interface with nil underlying value does not count) and the key exists in the table, the key+value will be deleted from the table.

func SetVar(ctx context.Context, key string, value any) {
	varMap, ok := ctx.Value(VarsCtxKey).(map[string]any)
	if !ok {
		return
	}
	if value == nil {
		if _, ok := varMap[key]; ok {
			delete(varMap, key)
			return
		}
	}
	varMap[key] = value
}

Cognitive complexity: 6, Cyclomatic complexity: 4

func StatusCodeMatches

StatusCodeMatches returns true if a real HTTP status code matches the configured status code, which may be either a real HTTP status code or an integer representing a class of codes (e.g. 4 for all 4xx statuses).

func StatusCodeMatches(actual, configured int) bool {
	if actual == configured {
		return true
	}
	if configured < 100 &&
		actual >= configured*100 &&
		actual < (configured+1)*100 {
		return true
	}
	return false
}

Cognitive complexity: 4, Cyclomatic complexity: 5

func (*App) Provision

Provision sets up the app.

func (app *App) Provision(ctx caddy.Context) error {
	// store some references
	tlsAppIface, err := ctx.App("tls")
	if err != nil {
		return fmt.Errorf("getting tls app: %v", err)
	}
	app.tlsApp = tlsAppIface.(*caddytls.TLS)
	app.ctx = ctx
	app.logger = ctx.Logger()

	eventsAppIface, err := ctx.App("events")
	if err != nil {
		return fmt.Errorf("getting events app: %v", err)
	}

	repl := caddy.NewReplacer()

	// this provisions the matchers for each route,
	// and prepares auto HTTP->HTTPS redirects, and
	// is required before we provision each server
	err = app.automaticHTTPSPhase1(ctx, repl)
	if err != nil {
		return err
	}

	if app.Metrics != nil {
		app.Metrics.init = sync.Once{}
		app.Metrics.httpMetrics = &httpMetrics{}
	}
	// prepare each server
	oldContext := ctx.Context
	for srvName, srv := range app.Servers {
		ctx.Context = context.WithValue(oldContext, ServerCtxKey, srv)
		srv.name = srvName
		srv.tlsApp = app.tlsApp
		srv.events = eventsAppIface.(*caddyevents.App)
		srv.ctx = ctx
		srv.logger = app.logger.Named("log")
		srv.errorLogger = app.logger.Named("log.error")
		srv.shutdownAtMu = new(sync.RWMutex)

		if srv.Metrics != nil {
			srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
			app.Metrics = cmp.Or[*Metrics](app.Metrics, &Metrics{
				init:		sync.Once{},
				httpMetrics:	&httpMetrics{},
			})
			app.Metrics.PerHost = app.Metrics.PerHost || srv.Metrics.PerHost
		}

		// only enable access logs if configured
		if srv.Logs != nil {
			srv.accessLogger = app.logger.Named("log.access")
			if srv.Logs.Trace {
				srv.traceLogger = app.logger.Named("log.trace")
			}
		}

		// if no protocols configured explicitly, enable all except h2c
		if len(srv.Protocols) == 0 {
			srv.Protocols = []string{"h1", "h2", "h3"}
		}

		srvProtocolsUnique := map[string]struct{}{}
		for _, srvProtocol := range srv.Protocols {
			srvProtocolsUnique[srvProtocol] = struct{}{}
		}
		_, h1ok := srvProtocolsUnique["h1"]
		_, h2ok := srvProtocolsUnique["h2"]
		_, h2cok := srvProtocolsUnique["h2c"]

		// the Go standard library does not let us serve only HTTP/2 using
		// http.Server; we would probably need to write our own server
		if !h1ok && (h2ok || h2cok) {
			return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
		}

		if srv.ListenProtocols != nil {
			if len(srv.ListenProtocols) != len(srv.Listen) {
				return fmt.Errorf("server %s: listener protocols count does not match address count: %d != %d",
					srvName, len(srv.ListenProtocols), len(srv.Listen))
			}

			for i, lnProtocols := range srv.ListenProtocols {
				if lnProtocols != nil {
					// populate empty listen protocols with server protocols
					lnProtocolsDefault := false
					var lnProtocolsInclude []string
					srvProtocolsInclude := maps.Clone(srvProtocolsUnique)

					// keep existing listener protocols unless they are empty
					for _, lnProtocol := range lnProtocols {
						if lnProtocol == "" {
							lnProtocolsDefault = true
						} else {
							lnProtocolsInclude = append(lnProtocolsInclude, lnProtocol)
							delete(srvProtocolsInclude, lnProtocol)
						}
					}

					// append server protocols to listener protocols if any listener protocols were empty
					if lnProtocolsDefault {
						for _, srvProtocol := range srv.Protocols {
							if _, ok := srvProtocolsInclude[srvProtocol]; ok {
								lnProtocolsInclude = append(lnProtocolsInclude, srvProtocol)
							}
						}
					}

					lnProtocolsIncludeUnique := map[string]struct{}{}
					for _, lnProtocol := range lnProtocolsInclude {
						lnProtocolsIncludeUnique[lnProtocol] = struct{}{}
					}
					_, h1ok := lnProtocolsIncludeUnique["h1"]
					_, h2ok := lnProtocolsIncludeUnique["h2"]
					_, h2cok := lnProtocolsIncludeUnique["h2c"]

					// check if any listener protocols contain h2 or h2c without h1
					if !h1ok && (h2ok || h2cok) {
						return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i)
					}

					srv.ListenProtocols[i] = lnProtocolsInclude
				}
			}
		}

		// if not explicitly configured by the user, disallow TLS
		// client auth bypass (domain fronting) which could
		// otherwise be exploited by sending an unprotected SNI
		// value during a TLS handshake, then putting a protected
		// domain in the Host header after establishing connection;
		// this is a safe default, but we allow users to override
		// it for example in the case of running a proxy where
		// domain fronting is desired and access is not restricted
		// based on hostname
		if srv.StrictSNIHost == nil && srv.hasTLSClientAuth() {
			app.logger.Warn("enabling strict SNI-Host enforcement because TLS client auth is configured",
				zap.String("server_id", srvName))
			trueBool := true
			srv.StrictSNIHost = &trueBool
		}

		// set up the trusted proxies source
		for srv.TrustedProxiesRaw != nil {
			val, err := ctx.LoadModule(srv, "TrustedProxiesRaw")
			if err != nil {
				return fmt.Errorf("loading trusted proxies modules: %v", err)
			}
			srv.trustedProxies = val.(IPRangeSource)
		}

		// set the default client IP header to read from
		if srv.ClientIPHeaders == nil {
			srv.ClientIPHeaders = []string{"X-Forwarded-For"}
		}

		// process each listener address
		for i := range srv.Listen {
			lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)
			if err != nil {
				return fmt.Errorf("server %s, listener %d: %v", srvName, i, err)
			}
			srv.Listen[i] = lnOut
		}

		// set up each listener modifier
		if srv.ListenerWrappersRaw != nil {
			vals, err := ctx.LoadModule(srv, "ListenerWrappersRaw")
			if err != nil {
				return fmt.Errorf("loading listener wrapper modules: %v", err)
			}
			var hasTLSPlaceholder bool
			for i, val := range vals.([]any) {
				if _, ok := val.(*tlsPlaceholderWrapper); ok {
					if i == 0 {
						// putting the tls placeholder wrapper first is nonsensical because
						// that is the default, implicit setting: without it, all wrappers
						// will go after the TLS listener anyway
						return fmt.Errorf("it is unnecessary to specify the TLS listener wrapper in the first position because that is the default")
					}
					if hasTLSPlaceholder {
						return fmt.Errorf("TLS listener wrapper can only be specified once")
					}
					hasTLSPlaceholder = true
				}
				srv.listenerWrappers = append(srv.listenerWrappers, val.(caddy.ListenerWrapper))
			}
			// if any wrappers were configured but the TLS placeholder wrapper is
			// absent, prepend it so all defined wrappers come after the TLS
			// handshake; this simplifies logic when starting the server, since we
			// can simply assume the TLS placeholder will always be there
			if !hasTLSPlaceholder && len(srv.listenerWrappers) > 0 {
				srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
			}
		}
		// pre-compile the primary handler chain, and be sure to wrap it in our
		// route handler so that important security checks are done, etc.
		primaryRoute := emptyHandler
		if srv.Routes != nil {
			err := srv.Routes.ProvisionHandlers(ctx, app.Metrics)
			if err != nil {
				return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
			}
			primaryRoute = srv.Routes.Compile(emptyHandler)
		}
		srv.primaryHandlerChain = srv.wrapPrimaryRoute(primaryRoute)

		// pre-compile the error handler chain
		if srv.Errors != nil {
			err := srv.Errors.Routes.Provision(ctx)
			if err != nil {
				return fmt.Errorf("server %s: setting up error handling routes: %v", srvName, err)
			}
			srv.errorHandlerChain = srv.Errors.Routes.Compile(errorEmptyHandler)
		}

		// provision the named routes (they get compiled at runtime)
		for name, route := range srv.NamedRoutes {
			err := route.Provision(ctx, app.Metrics)
			if err != nil {
				return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
			}
		}

		// prepare the TLS connection policies
		err = srv.TLSConnPolicies.Provision(ctx)
		if err != nil {
			return fmt.Errorf("server %s: setting up TLS connection policies: %v", srvName, err)
		}

		// if there is no idle timeout, set a sane default; users have complained
		// before that aggressive CDNs leave connections open until the server
		// closes them, so if we don't close them it leads to resource exhaustion
		if srv.IdleTimeout == 0 {
			srv.IdleTimeout = defaultIdleTimeout
		}
		if srv.ReadHeaderTimeout == 0 {
			srv.ReadHeaderTimeout = defaultReadHeaderTimeout	// see #6663
		}
	}
	ctx.Context = oldContext
	return nil
}

Cognitive complexity: 115, Cyclomatic complexity: 52

Uses: caddyevents.App, caddytls.TLS, cmp.Or, context.WithValue, fmt.Errorf, maps.Clone, sync.Once, sync.RWMutex, zap.String.

func (*App) Start

Start runs the app. It finishes automatic HTTPS if enabled, including management of certificates.

func (app *App) Start() error {
	// get a logger compatible with http.Server
	serverLogger, err := zap.NewStdLogAt(app.logger.Named("stdlib"), zap.DebugLevel)
	if err != nil {
		return fmt.Errorf("failed to set up server logger: %v", err)
	}

	for srvName, srv := range app.Servers {
		srv.server = &http.Server{
			ReadTimeout:		time.Duration(srv.ReadTimeout),
			ReadHeaderTimeout:	time.Duration(srv.ReadHeaderTimeout),
			WriteTimeout:		time.Duration(srv.WriteTimeout),
			IdleTimeout:		time.Duration(srv.IdleTimeout),
			MaxHeaderBytes:		srv.MaxHeaderBytes,
			Handler:		srv,
			ErrorLog:		serverLogger,
			ConnContext: func(ctx context.Context, c net.Conn) context.Context {
				return context.WithValue(ctx, ConnCtxKey, c)
			},
		}
		h2server := new(http2.Server)

		// disable HTTP/2, which we enabled by default during provisioning
		if !srv.protocol("h2") {
			srv.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
			for _, cp := range srv.TLSConnPolicies {
				// the TLSConfig was already provisioned, so... manually remove it
				for i, np := range cp.TLSConfig.NextProtos {
					if np == "h2" {
						cp.TLSConfig.NextProtos = append(cp.TLSConfig.NextProtos[:i], cp.TLSConfig.NextProtos[i+1:]...)
						break
					}
				}
				// remove it from the parent connection policy too, just to keep things tidy
				for i, alpn := range cp.ALPN {
					if alpn == "h2" {
						cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...)
						break
					}
				}
			}
		} else {
			//nolint:errcheck
			http2.ConfigureServer(srv.server, h2server)
		}

		// this TLS config is used by the std lib to choose the actual TLS config for connections
		// by looking through the connection policies to find the first one that matches
		tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
		srv.configureServer(srv.server)

		// enable H2C if configured
		if srv.protocol("h2c") {
			srv.server.Handler = h2c.NewHandler(srv, h2server)
		}

		for lnIndex, lnAddr := range srv.Listen {
			listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
			if err != nil {
				return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
			}

			srv.addresses = append(srv.addresses, listenAddr)

			protocols := srv.Protocols
			if srv.ListenProtocols != nil && srv.ListenProtocols[lnIndex] != nil {
				protocols = srv.ListenProtocols[lnIndex]
			}

			protocolsUnique := map[string]struct{}{}
			for _, protocol := range protocols {
				protocolsUnique[protocol] = struct{}{}
			}
			_, h1ok := protocolsUnique["h1"]
			_, h2ok := protocolsUnique["h2"]
			_, h2cok := protocolsUnique["h2c"]
			_, h3ok := protocolsUnique["h3"]

			for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
				hostport := listenAddr.JoinHostPort(portOffset)

				// enable TLS if there is a policy and if this is not the HTTP port
				useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()

				// enable HTTP/3 if configured
				if h3ok && useTLS {
					app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
					if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
						return err
					}
				}

				if h3ok && !useTLS {
					// Can only serve h3 with TLS enabled
					app.logger.Warn("HTTP/3 skipped because it requires TLS",
						zap.String("network", listenAddr.Network),
						zap.String("addr", hostport))
				}

				if h1ok || h2ok && useTLS || h2cok {
					// create the listener for this socket
					lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
					if err != nil {
						return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
					}
					ln, ok := lnAny.(net.Listener)
					if !ok {
						return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network)
					}

					// wrap listener before TLS (up to the TLS placeholder wrapper)
					var lnWrapperIdx int
					for i, lnWrapper := range srv.listenerWrappers {
						if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok {
							lnWrapperIdx = i + 1	// mark the next wrapper's spot
							break
						}
						ln = lnWrapper.WrapListener(ln)
					}

					if useTLS {
						// create TLS listener - this enables and terminates TLS
						ln = tls.NewListener(ln, tlsCfg)
					}

					// finish wrapping listener where we left off before TLS
					for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ {
						ln = srv.listenerWrappers[i].WrapListener(ln)
					}

					// handle http2 if use tls listener wrapper
					if h2ok {
						http2lnWrapper := &http2Listener{
							Listener:	ln,
							server:		srv.server,
							h2server:	h2server,
						}
						srv.h2listeners = append(srv.h2listeners, http2lnWrapper)
						ln = http2lnWrapper
					}

					// if binding to port 0, the OS chooses a port for us;
					// but the user won't know the port unless we print it
					if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
						app.logger.Info("port 0 listener",
							zap.String("input_address", lnAddr),
							zap.String("actual_address", ln.Addr().String()))
					}

					app.logger.Debug("starting server loop",
						zap.String("address", ln.Addr().String()),
						zap.Bool("tls", useTLS),
						zap.Bool("http3", srv.h3server != nil))

					srv.listeners = append(srv.listeners, ln)

					// enable HTTP/1 if configured
					if h1ok {
						//nolint:errcheck
						go srv.server.Serve(ln)
					}
				}

				if h2ok && !useTLS {
					// Can only serve h2 with TLS enabled
					app.logger.Warn("HTTP/2 skipped because it requires TLS",
						zap.String("network", listenAddr.Network),
						zap.String("addr", hostport))
				}
			}
		}

		srv.logger.Info("server running",
			zap.String("name", srvName),
			zap.Strings("protocols", srv.Protocols))
	}

	// finish automatic HTTPS by finally beginning
	// certificate management
	err = app.automaticHTTPSPhase2()
	if err != nil {
		return fmt.Errorf("finalizing automatic HTTPS: %v", err)
	}

	return nil
}

Cognitive complexity: 75, Cyclomatic complexity: 41

Uses: context.Context, context.WithValue, fmt.Errorf, h2c.NewHandler, http.Handler, http.Server, http2.ConfigureServer, http2.Server, net.Conn, net.ListenConfig, net.Listener, time.Duration, tls.Conn, tls.NewListener, zap.Bool, zap.DebugLevel, zap.NewStdLogAt, zap.String, zap.Strings.

func (*App) Stop

Stop gracefully shuts down the HTTP server.

func (app *App) Stop() error {
	ctx := context.Background()

	// see if any listeners in our config will be closing or if they are continuing
	// through a reload; because if any are closing, we will enforce shutdown delay
	var delay bool
	scheduledTime := time.Now().Add(time.Duration(app.ShutdownDelay))
	if app.ShutdownDelay > 0 {
		for _, server := range app.Servers {
			for _, na := range server.addresses {
				for _, addr := range na.Expand() {
					if caddy.ListenerUsage(addr.Network, addr.JoinHostPort(0)) < 2 {
						app.logger.Debug("listener closing and shutdown delay is configured", zap.String("address", addr.String()))
						server.shutdownAtMu.Lock()
						server.shutdownAt = scheduledTime
						server.shutdownAtMu.Unlock()
						delay = true
					} else {
						app.logger.Debug("shutdown delay configured but listener will remain open", zap.String("address", addr.String()))
					}
				}
			}
		}
	}

	// honor scheduled/delayed shutdown time
	if delay {
		app.logger.Info("shutdown scheduled",
			zap.Duration("delay_duration", time.Duration(app.ShutdownDelay)),
			zap.Time("time", scheduledTime))
		time.Sleep(time.Duration(app.ShutdownDelay))
	}

	// enforce grace period if configured
	if app.GracePeriod > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, time.Duration(app.GracePeriod))
		defer cancel()
		app.logger.Info("servers shutting down; grace period initiated", zap.Duration("duration", time.Duration(app.GracePeriod)))
	} else {
		app.logger.Info("servers shutting down with eternal grace period")
	}

	// goroutines aren't guaranteed to be scheduled right away,
	// so we'll use one WaitGroup to wait for all the goroutines
	// to start their server shutdowns, and another to wait for
	// them to finish; we'll always block for them to start so
	// that when we return the caller can be confident* that the
	// old servers are no longer accepting new connections
	// (* the scheduler might still pause them right before
	// calling Shutdown(), but it's unlikely)
	var startedShutdown, finishedShutdown sync.WaitGroup

	// these will run in goroutines
	stopServer := func(server *Server) {
		defer finishedShutdown.Done()
		startedShutdown.Done()

		if err := server.server.Shutdown(ctx); err != nil {
			app.logger.Error("server shutdown",
				zap.Error(err),
				zap.Strings("addresses", server.Listen))
		}
	}
	stopH3Server := func(server *Server) {
		defer finishedShutdown.Done()
		startedShutdown.Done()

		if server.h3server == nil {
			return
		}

		if err := server.h3server.Shutdown(ctx); err != nil {
			app.logger.Error("HTTP/3 server shutdown",
				zap.Error(err),
				zap.Strings("addresses", server.Listen))
		}
	}
	stopH2Listener := func(server *Server) {
		defer finishedShutdown.Done()
		startedShutdown.Done()

		for i, s := range server.h2listeners {
			if err := s.Shutdown(ctx); err != nil {
				app.logger.Error("http2 listener shutdown",
					zap.Error(err),
					zap.Int("index", i))
			}
		}
	}

	for _, server := range app.Servers {
		startedShutdown.Add(3)
		finishedShutdown.Add(3)
		go stopServer(server)
		go stopH3Server(server)
		go stopH2Listener(server)
	}

	// block until all the goroutines have been run by the scheduler;
	// this means that they have likely called Shutdown() by now
	startedShutdown.Wait()

	// if the process is exiting, we need to block here and wait
	// for the grace periods to complete, otherwise the process will
	// terminate before the servers are finished shutting down; but
	// we don't really need to wait for the grace period to finish
	// if the process isn't exiting (but note that frequent config
	// reloads with long grace periods for a sustained length of time
	// may deplete resources)
	if caddy.Exiting() {
		finishedShutdown.Wait()
	}

	// run stop callbacks now that the server shutdowns are complete
	for name, s := range app.Servers {
		for _, stopHook := range s.onStopFuncs {
			if err := stopHook(ctx); err != nil {
				app.logger.Error("server stop hook", zap.String("server", name), zap.Error(err))
			}
		}
	}

	return nil
}

Cognitive complexity: 48, Cyclomatic complexity: 18

Uses: context.Background, context.CancelFunc, context.WithTimeout, sync.WaitGroup, time.Duration, time.Now, time.Sleep, zap.Duration, zap.Error, zap.Int, zap.String, zap.Strings, zap.Time.

func (*App) Validate

Validate ensures the app's configuration is valid.

func (app *App) Validate() error {
	lnAddrs := make(map[string]string)

	for srvName, srv := range app.Servers {
		// each server must use distinct listener addresses
		for _, addr := range srv.Listen {
			listenAddr, err := caddy.ParseNetworkAddress(addr)
			if err != nil {
				return fmt.Errorf("invalid listener address '%s': %v", addr, err)
			}
			// check that every address in the port range is unique to this server;
			// we do not use <= here because PortRangeSize() adds 1 to EndPort for us
			for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
				addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.FormatUint(uint64(listenAddr.StartPort+i), 10))
				if sn, ok := lnAddrs[addr]; ok {
					return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
				}
				lnAddrs[addr] = srvName
			}
		}

		// logger names must not have ports
		if srv.Logs != nil {
			for host := range srv.Logs.LoggerNames {
				if _, _, err := net.SplitHostPort(host); err == nil {
					return fmt.Errorf("server %s: logger name must not have a port: %s", srvName, host)
				}
			}
		}
	}
	return nil
}

Cognitive complexity: 19, Cyclomatic complexity: 9

Uses: fmt.Errorf, net.SplitHostPort, strconv.FormatUint.

func (*ExtraLogFields) Add

Add adds a field to the list of extra fields to log.

func (e *ExtraLogFields) Add(field zap.Field) {
	e.fields = append(e.fields, field)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*ExtraLogFields) Set

Set sets a field in the list of extra fields to log. If the field already exists, it is replaced.

func (e *ExtraLogFields) Set(field zap.Field) {
	for i := range e.fields {
		if e.fields[i].Key == field.Key {
			e.fields[i] = field
			return
		}
	}
	e.fields = append(e.fields, field)
}

Cognitive complexity: 5, Cyclomatic complexity: 3

func (*HTTPErrorConfig) WithError

WithError makes a shallow copy of r to add the error to its context, and sets placeholders on the request's replacer related to err. It returns the modified request which has the error information in its context and replacer. It overwrites any existing error values that are stored.

func (*HTTPErrorConfig) WithError(r *http.Request, err error) *http.Request {
	// add the raw error value to the request context
	// so it can be accessed by error handlers
	c := context.WithValue(r.Context(), ErrorCtxKey, err)
	r = r.WithContext(c)

	// add error values to the replacer
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
	repl.Set("http.error", err)
	if handlerErr, ok := err.(HandlerError); ok {
		repl.Set("http.error.status_code", handlerErr.StatusCode)
		repl.Set("http.error.status_text", http.StatusText(handlerErr.StatusCode))
		repl.Set("http.error.id", handlerErr.ID)
		repl.Set("http.error.trace", handlerErr.Trace)
		if handlerErr.Err != nil {
			repl.Set("http.error.message", handlerErr.Err.Error())
		} else {
			repl.Set("http.error.message", http.StatusText(handlerErr.StatusCode))
		}
	}

	return r
}

Cognitive complexity: 6, Cyclomatic complexity: 3

Uses: context.WithValue, http.StatusText.

func (*MatcherSets) FromInterface

FromInterface fills ms from an 'any' value obtained from LoadModule.

func (ms *MatcherSets) FromInterface(matcherSets any) error {
	for _, matcherSetIfaces := range matcherSets.([]map[string]any) {
		var matcherSet MatcherSet
		for _, matcher := range matcherSetIfaces {
			if m, ok := matcher.(RequestMatcherWithError); ok {
				matcherSet = append(matcherSet, m)
				continue
			}
			if m, ok := matcher.(RequestMatcher); ok {
				matcherSet = append(matcherSet, m)
				continue
			}
			return fmt.Errorf("decoded module is not a RequestMatcher or RequestMatcherWithError: %#v", matcher)
		}
		*ms = append(*ms, matcherSet)
	}
	return nil
}

Cognitive complexity: 10, Cyclomatic complexity: 5

Uses: fmt.Errorf.

func (*ResponseWriterWrapper) Push

Push implements http.Pusher. It simply calls the underlying ResponseWriter's Push method if there is one, or returns ErrNotImplemented otherwise.

func (rww *ResponseWriterWrapper) Push(target string, opts *http.PushOptions) error {
	if pusher, ok := rww.ResponseWriter.(http.Pusher); ok {
		return pusher.Push(target, opts)
	}
	return ErrNotImplemented
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: http.Pusher.

func (*ResponseWriterWrapper) ReadFrom

ReadFrom implements io.ReaderFrom. It retries to use io.ReaderFrom if available, then fallback to io.Copy. see: https://github.com/caddyserver/caddy/issues/6546

func (rww *ResponseWriterWrapper) ReadFrom(r io.Reader) (n int64, err error) {
	if rf, ok := rww.ResponseWriter.(io.ReaderFrom); ok {
		return rf.ReadFrom(r)
	}
	return io.Copy(rww.ResponseWriter, r)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: io.Copy, io.ReaderFrom.

func (*Route) ProvisionHandlers

ProvisionHandlers sets up all the handlers by loading the handler modules. Only call this method directly if you need to set up matchers and handlers separately without having to provision a second time; otherwise use Provision instead.

func (r *Route) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error {
	handlersIface, err := ctx.LoadModule(r, "HandlersRaw")
	if err != nil {
		return fmt.Errorf("loading handler modules: %v", err)
	}
	for _, handler := range handlersIface.([]any) {
		r.Handlers = append(r.Handlers, handler.(MiddlewareHandler))
	}

	// Make ProvisionHandlers idempotent by clearing the middleware field
	r.middleware = []Middleware{}

	// pre-compile the middleware handler chain
	for _, midhandler := range r.Handlers {
		r.middleware = append(r.middleware, wrapMiddleware(ctx, midhandler, metrics))
	}
	return nil
}

Cognitive complexity: 9, Cyclomatic complexity: 4

Uses: fmt.Errorf.

func (*Route) ProvisionMatchers

ProvisionMatchers sets up all the matchers by loading the matcher modules. Only call this method directly if you need to set up matchers and handlers separately without having to provision a second time; otherwise use Provision instead.

func (r *Route) ProvisionMatchers(ctx caddy.Context) error {
	// matchers
	matchersIface, err := ctx.LoadModule(r, "MatcherSetsRaw")
	if err != nil {
		return fmt.Errorf("loading matcher modules: %v", err)
	}
	err = r.MatcherSets.FromInterface(matchersIface)
	if err != nil {
		return err
	}
	return nil
}

Cognitive complexity: 4, Cyclomatic complexity: 3

Uses: fmt.Errorf.

func (*Server) Listeners

Listeners returns the server's listeners. These are active listeners, so calling Accept() or Close() on them will probably break things. They are made available here for read-only purposes (e.g. Addr()) and for type-asserting for purposes where you know what you're doing.

EXPERIMENTAL: Subject to change or removal.

func (s *Server) Listeners() []net.Listener	{ return s.listeners }

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*Server) Name

Name returns the server's name.

func (s *Server) Name() string	{ return s.name }

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*Server) RegisterConnContext

RegisterConnContext registers f to be invoked as part of s.ConnContext.

func (s *Server) RegisterConnContext(f func(ctx context.Context, c net.Conn) context.Context) {
	s.connContextFuncs = append(s.connContextFuncs, f)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*Server) RegisterConnState

RegisterConnState registers f to be invoked on s.ConnState.

func (s *Server) RegisterConnState(f func(net.Conn, http.ConnState)) {
	s.connStateFuncs = append(s.connStateFuncs, f)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*Server) RegisterOnShutdown

RegisterOnShutdown registers f to be invoked when the server begins to shut down.

func (s *Server) RegisterOnShutdown(f func()) {
	s.onShutdownFuncs = append(s.onShutdownFuncs, f)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*Server) RegisterOnStop

RegisterOnStop registers f to be invoked after the server has shut down completely.

EXPERIMENTAL: Subject to change or removal.

func (s *Server) RegisterOnStop(f func(context.Context) error) {
	s.onStopFuncs = append(s.onStopFuncs, f)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*StaticIPRange) GetIPRanges

func (s *StaticIPRange) GetIPRanges(_ *http.Request) []netip.Prefix {
	return s.ranges
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*WeakString) UnmarshalJSON

UnmarshalJSON satisfies json.Unmarshaler according to this type's documentation.

func (ws *WeakString) UnmarshalJSON(b []byte) error {
	if len(b) == 0 {
		return io.EOF
	}
	if b[0] == byte('"') && b[len(b)-1] == byte('"') {
		var s string
		err := json.Unmarshal(b, &s)
		if err != nil {
			return err
		}
		*ws = WeakString(s)
		return nil
	}
	if bytes.Equal(b, []byte("null")) {
		return nil
	}
	*ws = WeakString(b)
	return nil
}

Cognitive complexity: 8, Cyclomatic complexity: 5

Uses: bytes.Equal, io.EOF, json.Unmarshal.

func (*hijackedConn) WriteTo

func (hc *hijackedConn) WriteTo(w io.Writer) (int64, error) {
	n, err := io.Copy(w, hc.Conn)
	hc.updateReadSize(int(n))
	return n, err
}

Cognitive complexity: 0, Cyclomatic complexity: 1

Uses: io.Copy.

func (*http2Listener) Accept

func (h *http2Listener) Accept() (net.Conn, error) {
	for {
		conn, err := h.Listener.Accept()
		if err != nil {
			return nil, err
		}

		if csc, ok := conn.(connectionStateConn); ok {
			// *tls.Conn will return empty string because it's only populated after handshake is complete
			if csc.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
				go h.serveHttp2(csc)
				continue
			}
		}

		return conn, nil
	}
}

Cognitive complexity: 8, Cyclomatic complexity: 5

Uses: http2.NextProtoTLS.

func (*http2Listener) Shutdown

func (h *http2Listener) Shutdown(ctx context.Context) error {
	pollIntervalBase := time.Millisecond
	nextPollInterval := func() time.Duration {
		// Add 10% jitter.
		//nolint:gosec
		interval := pollIntervalBase + time.Duration(weakrand.Intn(int(pollIntervalBase/10)))
		// Double and clamp for next time.
		pollIntervalBase *= 2
		if pollIntervalBase > shutdownPollIntervalMax {
			pollIntervalBase = shutdownPollIntervalMax
		}
		return interval
	}

	timer := time.NewTimer(nextPollInterval())
	defer timer.Stop()
	for {
		if atomic.LoadUint64(&h.cnt) == 0 {
			return nil
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-timer.C:
			timer.Reset(nextPollInterval())
		}
	}
}

Cognitive complexity: 10, Cyclomatic complexity: 6

Uses: atomic.LoadUint64, time.Duration, time.Millisecond, time.NewTimer, weakrand.Intn.

func (*httpRedirectConn) Read

Read tries to peek at the first few bytes of the request, and if we get an error reading the headers, and that error was due to the bytes looking like an HTTP request, then we perform a HTTP->HTTPS redirect on the same port as the original connection.

func (c *httpRedirectConn) Read(p []byte) (int, error) {
	if c.once {
		return c.r.Read(p)
	}
	// no need to use sync.Once - net.Conn is not read from concurrently.
	c.once = true

	firstBytes, err := c.r.Peek(5)
	if err != nil {
		return 0, err
	}

	// If the request doesn't look like HTTP, then it's probably
	// TLS bytes, and we don't need to do anything.
	if !firstBytesLookLikeHTTP(firstBytes) {
		return c.r.Read(p)
	}

	// From now on, we can be almost certain the request is HTTP.
	// The returned error will be non nil and caller are expected to
	// close the connection.

	// Set the read limit, io.MultiReader is needed because
	// when resetting, *bufio.Reader discards buffered data.
	buffered, _ := c.r.Peek(c.r.Buffered())
	mr := io.MultiReader(bytes.NewReader(buffered), c.Conn)
	c.r.Reset(io.LimitReader(mr, c.limit))

	// Parse the HTTP request, so we can get the Host and URL to redirect to.
	req, err := http.ReadRequest(c.r)
	if err != nil {
		return 0, fmt.Errorf("couldn't read HTTP request")
	}

	// Build the redirect response, using the same Host and URL,
	// but replacing the scheme with https.
	headers := make(http.Header)
	headers.Add("Location", "https://"+req.Host+req.URL.String())
	resp := &http.Response{
		Proto:		"HTTP/1.0",
		Status:		"308 Permanent Redirect",
		StatusCode:	308,
		ProtoMajor:	1,
		ProtoMinor:	0,
		Header:		headers,
	}

	err = resp.Write(c.Conn)
	if err != nil {
		return 0, fmt.Errorf("couldn't write HTTP->HTTPS redirect")
	}

	return 0, fmt.Errorf("redirected HTTP request on HTTPS port")
}

Cognitive complexity: 8, Cyclomatic complexity: 5

Uses: bytes.NewReader, fmt.Errorf, http.Header, http.ReadRequest, http.Response, io.LimitReader, io.MultiReader.

func (*lengthReader) Close

func (r *lengthReader) Close() error {
	return r.Source.Close()
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*matcherCELLibrary) CompileOptions

func (lib *matcherCELLibrary) CompileOptions() []cel.EnvOption {
	return lib.envOptions
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*matcherCELLibrary) ProgramOptions

func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption {
	return lib.programOptions
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*responseRecorder) Buffer

Buffer returns the body buffer that rr was created with. You should still have your original pointer, though.

func (rr *responseRecorder) Buffer() *bytes.Buffer {
	return rr.buf
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*responseRecorder) Buffered

Buffered returns whether rr has decided to buffer the response.

func (rr *responseRecorder) Buffered() bool {
	return !rr.stream
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*responseRecorder) FlushError

FlushError will suppress actual flushing if the response is buffered. See: https://github.com/caddyserver/caddy/issues/6144

func (rr *responseRecorder) FlushError() error {
	if rr.stream {
		//nolint:bodyclose
		return http.NewResponseController(rr.ResponseWriterWrapper).Flush()
	}
	return nil
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: http.NewResponseController.

func (*responseRecorder) Hijack

func (rr *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	//nolint:bodyclose
	conn, brw, err := http.NewResponseController(rr.ResponseWriterWrapper).Hijack()
	if err != nil {
		return nil, nil, err
	}
	// Per http documentation, returned bufio.Writer is empty, but bufio.Read maybe not
	conn = &hijackedConn{conn, rr}
	brw.Writer.Reset(conn)

	buffered := brw.Reader.Buffered()
	if buffered != 0 {
		conn.(*hijackedConn).updateReadSize(buffered)
		data, _ := brw.Peek(buffered)
		brw.Reader.Reset(io.MultiReader(bytes.NewReader(data), conn))
		// peek to make buffered data appear, as Reset will make it 0
		_, _ = brw.Peek(buffered)
	} else {
		brw.Reader.Reset(conn)
	}
	return conn, brw, nil
}

Cognitive complexity: 7, Cyclomatic complexity: 3

Uses: bytes.NewReader, http.NewResponseController, io.MultiReader.

func (*responseRecorder) Size

Size returns the number of bytes written, not including the response headers.

func (rr *responseRecorder) Size() int {
	return rr.size
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*responseRecorder) Status

Status returns the status code that was written, if any.

func (rr *responseRecorder) Status() int {
	return rr.statusCode
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*responseRecorder) Write

func (rr *responseRecorder) Write(data []byte) (int, error) {
	rr.WriteHeader(http.StatusOK)
	var n int
	var err error
	if rr.stream {
		n, err = rr.ResponseWriterWrapper.Write(data)
	} else {
		n, err = rr.buf.Write(data)
	}

	rr.size += n
	return n, err
}

Cognitive complexity: 4, Cyclomatic complexity: 2

Uses: http.StatusOK.

func (*responseRecorder) WriteHeader

WriteHeader writes the headers with statusCode to the wrapped ResponseWriter unless the response is to be buffered instead. 1xx responses are never buffered.

func (rr *responseRecorder) WriteHeader(statusCode int) {
	if rr.wroteHeader {
		return
	}

	// save statusCode always, in case HTTP middleware upgrades websocket
	// connections by manually setting headers and writing status 101
	rr.statusCode = statusCode

	// 1xx responses aren't final; just informational
	if statusCode < 100 || statusCode > 199 {
		rr.wroteHeader = true

		// decide whether we should buffer the response
		if rr.shouldBuffer == nil {
			rr.stream = true
		} else {
			rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
		}
	}

	// if informational or not buffered, immediately write header
	if rr.stream || (100 <= statusCode && statusCode <= 199) {
		rr.ResponseWriterWrapper.WriteHeader(statusCode)
	}
}

Cognitive complexity: 10, Cyclomatic complexity: 8

func (*responseRecorder) WriteResponse

func (rr *responseRecorder) WriteResponse() error {
	if rr.statusCode == 0 {
		// could happen if no handlers actually wrote anything,
		// and this prevents a panic; status must be > 0
		rr.WriteHeader(http.StatusOK)
	}
	if rr.stream {
		return nil
	}
	rr.ResponseWriterWrapper.WriteHeader(rr.statusCode)
	_, err := io.Copy(rr.ResponseWriterWrapper, rr.buf)
	return err
}

Cognitive complexity: 4, Cyclomatic complexity: 3

Uses: http.StatusOK, io.Copy.

func (App) CaddyModule

CaddyModule returns the Caddy module information.

func (App) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:	"http",
		New:	func() caddy.Module { return new(App) },
	}
}

Cognitive complexity: 2, Cyclomatic complexity: 1

func (HandlerError) Unwrap

Unwrap returns the underlying error value. See the errors package for info.

func (e HandlerError) Unwrap() error	{ return e.Err }

Cognitive complexity: 0, Cyclomatic complexity: 1

func (HandlerFunc) ServeHTTP

ServeHTTP implements the Handler interface.

func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
	return f(w, r)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (LoggableHTTPRequest) MarshalLogObject

MarshalLogObject satisfies the zapcore.ObjectMarshaler interface.

func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	ip, port, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		ip = r.RemoteAddr
		port = ""
	}

	enc.AddString("remote_ip", ip)
	enc.AddString("remote_port", port)
	if ip, ok := GetVar(r.Context(), ClientIPVarKey).(string); ok {
		enc.AddString("client_ip", ip)
	}
	enc.AddString("proto", r.Proto)
	enc.AddString("method", r.Method)
	enc.AddString("host", r.Host)
	enc.AddString("uri", r.RequestURI)
	enc.AddObject("headers", LoggableHTTPHeader{
		Header:			r.Header,
		ShouldLogCredentials:	r.ShouldLogCredentials,
	})
	if r.TransferEncoding != nil {
		enc.AddArray("transfer_encoding", LoggableStringArray(r.TransferEncoding))
	}
	if r.TLS != nil {
		enc.AddObject("tls", LoggableTLSConnState(*r.TLS))
	}
	return nil
}

Cognitive complexity: 9, Cyclomatic complexity: 5

Uses: net.SplitHostPort.

func (LoggableStringArray) MarshalLogArray

MarshalLogArray satisfies the zapcore.ArrayMarshaler interface.

func (sa LoggableStringArray) MarshalLogArray(enc zapcore.ArrayEncoder) error {
	if sa == nil {
		return nil
	}
	for _, s := range sa {
		enc.AppendString(s)
	}
	return nil
}

Cognitive complexity: 5, Cyclomatic complexity: 3

func (MatchExpression) Match

Match returns true if r matches m.

func (m MatchExpression) Match(r *http.Request) bool {
	match, err := m.MatchWithError(r)
	if err != nil {
		SetVar(r.Context(), MatcherErrorVarKey, err)
	}
	return match
}

Cognitive complexity: 2, Cyclomatic complexity: 2

func (MatchExpression) MatchWithError

MatchWithError returns true if r matches m.

func (m MatchExpression) MatchWithError(r *http.Request) (bool, error) {
	celReq := celHTTPRequest{r}
	out, _, err := m.prg.Eval(celReq)
	if err != nil {
		m.log.Error("evaluating expression", zap.Error(err))
		return false, err
	}
	if outBool, ok := out.Value().(bool); ok {
		return outBool, nil
	}
	return false, nil
}

Cognitive complexity: 5, Cyclomatic complexity: 3

Uses: zap.Error.

func (MatchRemoteIP) CELLibrary

CELLibrary produces options that expose this matcher for use in CEL expression matchers.

Example:

expression remote_ip('192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8')

func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
	return CELMatcherImpl(
		// name of the macro, this is the function name that users see when writing expressions.
		"remote_ip",
		// name of the function that the macro will be rewritten to call.
		"remote_ip_match_request_list",
		// internal data type of the MatchPath value.
		[]*cel.Type{cel.ListType(cel.StringType)},
		// function to convert a constant list of strings to a MatchPath instance.
		func(data ref.Val) (RequestMatcherWithError, error) {
			refStringList := reflect.TypeOf([]string{})
			strList, err := data.ConvertToNative(refStringList)
			if err != nil {
				return nil, err
			}

			m := MatchRemoteIP{}

			for _, input := range strList.([]string) {
				if input == "forwarded" {
					return nil, errors.New("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
				}
				m.Ranges = append(m.Ranges, input)
			}

			err = m.Provision(ctx)
			return m, err
		},
	)
}

Cognitive complexity: 11, Cyclomatic complexity: 4

Uses: cel.ListType, cel.StringType, cel.Type, errors.New, ref.Val, reflect.TypeOf.

func (MatcherSets) AnyMatch

AnyMatch returns true if req matches any of the matcher sets in ms or if there are no matchers, in which case the request always matches.

Deprecated: Use AnyMatchWithError instead.

func (ms MatcherSets) AnyMatch(req *http.Request) bool {
	for _, m := range ms {
		match, err := m.MatchWithError(req)
		if err != nil {
			SetVar(req.Context(), MatcherErrorVarKey, err)
			return false
		}
		if match {
			return match
		}
	}
	return len(ms) == 0
}

Cognitive complexity: 7, Cyclomatic complexity: 4

func (MatcherSets) AnyMatchWithError

AnyMatchWithError returns true if req matches any of the matcher sets in ms or if there are no matchers, in which case the request always matches. If any matcher returns an error, we cut short and return the error.

func (ms MatcherSets) AnyMatchWithError(req *http.Request) (bool, error) {
	for _, m := range ms {
		match, err := m.MatchWithError(req)
		if err != nil || match {
			return match, err
		}
	}
	return len(ms) == 0, nil
}

Cognitive complexity: 5, Cyclomatic complexity: 4

func (Route) Compile

Compile prepares a middleware chain from the route list. This should only be done once during the request, just before the middleware chain is executed.

func (r Route) Compile(next Handler) Handler {
	return wrapRoute(r)(next)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (Route) Empty

Empty returns true if the route has all zero/default values.

func (r Route) Empty() bool {
	return len(r.MatcherSetsRaw) == 0 &&
		len(r.MatcherSets) == 0 &&
		len(r.HandlersRaw) == 0 &&
		len(r.Handlers) == 0 &&
		!r.Terminal &&
		r.Group == ""
}

Cognitive complexity: 0, Cyclomatic complexity: 6

func (WeakString) Bool

Bool returns ws as a boolean. If ws is not a boolean, false is returned.

func (ws WeakString) Bool() bool {
	return string(ws) == "true"
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (WeakString) Float64

Float64 returns ws as a float64. If ws is not a float value, the zero value is returned.

func (ws WeakString) Float64() float64 {
	num, _ := strconv.ParseFloat(string(ws), 64)
	return num
}

Cognitive complexity: 0, Cyclomatic complexity: 1

Uses: strconv.ParseFloat.

func (WeakString) Int

Int returns ws as an integer. If ws is not an integer, 0 is returned.

func (ws WeakString) Int() int {
	num, _ := strconv.Atoi(string(ws))
	return num
}

Cognitive complexity: 0, Cyclomatic complexity: 1

Uses: strconv.Atoi.

func (WeakString) MarshalJSON

MarshalJSON marshals was a boolean if true or false, a number if an integer, or a string otherwise.

func (ws WeakString) MarshalJSON() ([]byte, error) {
	if ws == "true" {
		return []byte("true"), nil
	}
	if ws == "false" {
		return []byte("false"), nil
	}
	if num, err := strconv.Atoi(string(ws)); err == nil {
		return json.Marshal(num)
	}
	return json.Marshal(string(ws))
}

Cognitive complexity: 6, Cyclomatic complexity: 4

Uses: json.Marshal, strconv.Atoi.

func (WeakString) String

String returns ws as a string.

func (ws WeakString) String() string {
	return string(ws)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (celHTTPRequest) ConvertToNative

func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (any, error) {
	return cr.Request, nil
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (celHTTPRequest) ConvertToType

func (celHTTPRequest) ConvertToType(typeVal ref.Type) ref.Val {
	panic("not implemented")
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (celHTTPRequest) Equal

func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
	if o, ok := other.Value().(celHTTPRequest); ok {
		return types.Bool(o.Request == cr.Request)
	}
	return types.ValOrErr(other, "%v is not comparable type", other)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: types.Bool, types.ValOrErr.

func (celHTTPRequest) Parent

func (cr celHTTPRequest) Parent() interpreter.Activation {
	return nil
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (celHTTPRequest) ResolveName

func (cr celHTTPRequest) ResolveName(name string) (any, bool) {
	if name == CELRequestVarName {
		return cr, true
	}
	return nil, false
}

Cognitive complexity: 2, Cyclomatic complexity: 2

func (celHTTPRequest) Type

func (celHTTPRequest) Type() ref.Type	{ return httpRequestCELType }

Cognitive complexity: 0, Cyclomatic complexity: 1

func (celHTTPRequest) Value

func (cr celHTTPRequest) Value() any	{ return cr }

Cognitive complexity: 0, Cyclomatic complexity: 1

func (celTypeAdapter) NativeToValue

func (celTypeAdapter) NativeToValue(value any) ref.Val {
	switch v := value.(type) {
	case celHTTPRequest:
		return v
	case pkix.Name:
		return celPkixName{&v}
	case time.Time:
		return types.Timestamp{Time: v}
	case error:
		return types.WrapErr(v)
	}
	return types.DefaultTypeAdapter.NativeToValue(value)
}

Cognitive complexity: 8, Cyclomatic complexity: 6

Uses: pkix.Name, time.Time, types.DefaultTypeAdapter, types.Timestamp, types.WrapErr.

func (tlsPlaceholderWrapper) UnmarshalCaddyfile

func (tlsPlaceholderWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error	{ return nil }

Cognitive complexity: 0, Cyclomatic complexity: 1

func (tlsPlaceholderWrapper) WrapListener

func (tlsPlaceholderWrapper) WrapListener(ln net.Listener) net.Listener	{ return ln }

Cognitive complexity: 0, Cyclomatic complexity: 1

Private functions

func addHTTPVarsToReplacer

addHTTPVarsToReplacer (repl *caddy.Replacer, req *http.Request, w http.ResponseWriter)
References: bytes.Buffer, http.LocalAddrContextKey, http.Request, io.Copy, io.NopCloser, net.Addr, net.SplitHostPort, netip.ParseAddr, path.Base, path.Ext, path.Split, strconv.Atoi, strings.Cut, strings.EqualFold, strings.HasPrefix, strings.Index, strings.Join, strings.Split, strings.TrimSuffix, textproto.CanonicalMIMEHeaderKey, time.Now, time.Since, time.Time, time.Until, zap.String.

func buildHTTPServer

buildHTTPServer (i int, port uint, addr string, statusCode int, hdr http.Header, body string, accessLog bool) (*Server, error)
References: bytes.Buffer, caddyconfig.JSONModuleObject, fmt.Sprintf, json.RawMessage, template.New, time.Second.

func celMatcherJSONMacroExpander

celMatcherJSONMacroExpander validates that the macro is called a single map literal argument.

The following function call is returned: <funcName>(request, arg)

celMatcherJSONMacroExpander (funcName string) parser.MacroExpander
References: ast.CallKind, ast.ComprehensionKind, ast.Expr, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind, ast.UnspecifiedExprKind, cel.MacroExprFactory, common.Error, fmt.Sprintf.

func celMatcherStringListMacroExpander

celMatcherStringListMacroExpander validates that the macro is called with a variable number of string arguments (at least one).

The arguments are collected into a single list argument the following function call returned: <funcName>(request, [args])

celMatcherStringListMacroExpander (funcName string) cel.MacroFactory
References: ast.Expr, cel.MacroExprFactory, common.Error.

func celMatcherStringMacroExpander

celMatcherStringMacroExpander validates that the macro is called a single string argument.

The following function call is returned: <funcName>(request, arg)

celMatcherStringMacroExpander (funcName string) parser.MacroExpander
References: ast.Expr, cel.MacroExprFactory, common.Error.

func cleanPath

cleanPath does path.Clean(p) but preserves any trailing slash.

cleanPath (p string) string
References: path.Clean, strings.HasSuffix.

func cloneURL

cloneURL makes a copy of r.URL and returns a new value that doesn't reference the original.

cloneURL (from,to *url.URL)
References: url.Userinfo.

func cmdRespond

cmdRespond (fl caddycmd.Flags) (int, error)
References: caddyconfig.JSON, fmt.Errorf, fmt.Printf, fmt.Sprintf, http.Header, io.ReadAll, os.Args, os.ModeNamedPipe, os.Stdin, strconv.Atoi, strings.Cut, strings.TrimSpace, zap.DebugLevel.

func computeApproximateRequestSize

taken from https://github.com/prometheus/client_golang/blob/6007b2b5cae01203111de55f753e76d8dac1f529/prometheus/promhttp/instrument_server.go#L298

computeApproximateRequestSize (r *http.Request) int

func determineTrustedProxy

determineTrustedProxy parses the remote IP address of the request, and determines (if the server configured it) if the client is a trusted proxy. If trusted, also returns the real client IP if possible.

determineTrustedProxy (r *http.Request, s *Server) (bool, string)
References: net.SplitHostPort, netip.ParseAddr, strings.Cut.

func errLogValues

errLogValues inspects err and returns the status code to use, the error log message, and any extra fields. If err is a HandlerError, the returned values will have richer information.

errLogValues (err error) (int, string, func() []zapcore.Field)
References: errors.As, http.StatusInternalServerError, zap.Int, zap.String, zapcore.Field.

func firstBytesLookLikeHTTP

firstBytesLookLikeHTTP reports whether a TLS record header looks like it might've been a misdirected plaintext HTTP request.

firstBytesLookLikeHTTP (hdr []byte) bool

func getHTTP3Network

getHTTP3Network (originalNetwork string) (string, error)
References: fmt.Errorf, strings.ToLower.

func getHeaderFieldVals

getHeaderFieldVals returns the field values for the given fieldName from input. The host parameter should be obtained from the http.Request.Host field, and the transferEncoding from http.Request.TransferEncoding, since net/http removes them from the header map.

getHeaderFieldVals (input http.Header, fieldName,host string, transferEncoding []string) []string
References: textproto.CanonicalMIMEHeaderKey.

func getReqTLSReplacement

getReqTLSReplacement (req *http.Request, key string) (any, bool)
References: base64.StdEncoding, caddytls.ProtocolName, fmt.Sprintf, net.IP, pem.Block, pem.EncodeToMemory, sha256.Sum256, strconv.Atoi, strings.HasPrefix, strings.HasSuffix, strings.ToLower, tls.CipherSuiteName, url.URL.

func getTLSPeerCert

getTLSPeerCert retrieves the first peer certificate from a TLS session. Returns nil if no peer cert is in use.

getTLSPeerCert (cs *tls.ConnectionState) *x509.Certificate

func init

init ()

func initHTTPMetrics

initHTTPMetrics (ctx caddy.Context, metrics *Metrics)
References: promauto.With, prometheus.CounterOpts, prometheus.DefBuckets, prometheus.ExponentialBuckets, prometheus.GaugeOpts, prometheus.HistogramOpts.

func isCELCaddyPlaceholderCall

isCELCaddyPlaceholderCall returns whether the expression is a caddy placeholder call.

isCELCaddyPlaceholderCall (e ast.Expr) bool
References: ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind, ast.UnspecifiedExprKind.

func isCELConcatCall

isCELConcatCall tests whether the expression is a concat function (+) with string, placeholder, or other concat call arguments.

isCELConcatCall (e ast.Expr) bool
References: ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind, ast.UnspecifiedExprKind, operators.Add.

func isCELStringExpr

isCELStringExpr indicates whether the expression is a supported string expression

isCELStringExpr (e ast.Expr) bool

func isCELStringListLiteral

isCELStringListLiteral returns whether the expression resolves to a list literal containing only string constants or a placeholder call.

isCELStringListLiteral (e ast.Expr) bool
References: ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind, ast.UnspecifiedExprKind.

func isCELStringLiteral

isCELStringLiteral returns whether the expression is a CEL string literal.

isCELStringLiteral (e ast.Expr) bool
References: ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind, ast.UnspecifiedExprKind, types.StringType.

func isTailscaleDomain

isTailscaleDomain (name string) bool
References: strings.HasSuffix, strings.ToLower.

func isTrustedClientIP

isTrustedClientIP returns true if the given IP address is in the list of trusted IP ranges.

isTrustedClientIP (ipAddr netip.Addr, trusted []netip.Prefix) bool
References: netip.Prefix, slices.ContainsFunc.

func marshalPublicKey

marshalPublicKey returns the byte encoding of pubKey.

marshalPublicKey (pubKey any) ([]byte, error)
References: asn1.Marshal, ecdsa.PublicKey, ed25519.PublicKey, fmt.Errorf, rsa.PublicKey.

func matchHeaders

matchHeaders returns true if input matches the criteria in against without regex. The host parameter should be obtained from the http.Request.Host field since net/http removes it from the header map.

matchHeaders (input,against http.Header, host string, transferEncoding []string, repl *caddy.Replacer) bool
References: strings.Contains, strings.HasPrefix, strings.HasSuffix.

func matchIPByCidrZones

matchIPByCidrZones (clientIP netip.Addr, zoneID string, cidrs []*netip.Prefix, zones []string) (bool, bool)

func newMetricsInstrumentedHandler

newMetricsInstrumentedHandler (ctx caddy.Context, handler string, mh MiddlewareHandler, metrics *Metrics) *metricsInstrumentedHandler

func originalRequest

originalRequest returns a partial, shallow copy of req, including: req.Method, deep copy of req.URL (into the urlCopy parameter, which should be on the stack), req.RequestURI, and req.RemoteAddr. Notably, headers are not copied. This function is designed to be very fast and efficient, and useful primarily for read-only/logging purposes.

originalRequest (req *http.Request, urlCopy *url.URL) http.Request
References: http.Request.

func parseIPZoneFromString

parseIPZoneFromString (address string) (netip.Addr, string, error)
References: net.SplitHostPort, netip.IPv4Unspecified, netip.ParseAddr, strings.Contains, strings.Split.

func provisionCidrsZonesFromRanges

provisionCidrsZonesFromRanges (ranges []string) ([]*netip.Prefix, []string, error)
References: fmt.Errorf, netip.ParseAddr, netip.ParsePrefix, netip.Prefix, netip.PrefixFrom, strings.Contains, strings.Split.

func randString

randString returns a string of n random characters. It is not even remotely secure OR a proper distribution. But it's good enough for some things. It excludes certain confusing characters like I, l, 1, 0, O, etc. If sameCase is true, then uppercase letters are excluded.

randString (n int, sameCase bool) string
References: weakrand.Int63.

func serverNameFromContext

serverNameFromContext extracts the current server name from the context. Returns "UNKNOWN" if none is available (should probably never happen).

serverNameFromContext (ctx context.Context) string

func strictUntrustedClientIp

strictUntrustedClientIp iterates through the list of client IP headers, parses them from right-to-left, and returns the first valid IP address that is untrusted. If no valid IP address is found, then the direct remote address is returned.

strictUntrustedClientIp (r *http.Request, headers []string, trusted []netip.Prefix, clientIP string) string
References: net.SplitHostPort, netip.ParseAddr, strings.Cut, strings.Join, strings.Split, strings.TrimSpace.

func trace

trace () string
References: fmt.Sprintf, path.Base, runtime.Caller, runtime.FuncForPC.

func trustedRealClientIP

trustedRealClientIP finds the client IP from the request assuming it is from a trusted client. If there is no client IP headers, then the direct remote address is returned. If there are client IP headers, then the first value from those headers is used.

trustedRealClientIP (r *http.Request, headers []string, clientIP string) string
References: net.SplitHostPort, netip.ParseAddr, strings.Cut, strings.Join, strings.Split, strings.TrimSpace.

func wrapMiddleware

wrapMiddleware wraps mh such that it can be correctly appended to a list of middleware in preparation for compiling into a handler chain. We can't do this inline inside a loop, because it relies on a reference to mh not changing until the execution of its handler (which is deferred by multiple func closures). In other words, we need to pull this particular MiddlewareHandler pointer into its own stack frame to preserve it so it won't be overwritten in future loop iterations.

wrapMiddleware (ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware
References: http.Request, http.ResponseWriter.

func wrapRoute

wrapRoute wraps route with a middleware and handler so that it can be chained in and defer evaluation of its matchers to request-time. Like wrapMiddleware, it is vital that this wrapping takes place in its own stack frame so as to not overwrite the reference to the intended route by looping and changing the reference each time.

wrapRoute (route Route) Middleware
References: http.Request, http.ResponseWriter.

func automaticHTTPSPhase1

automaticHTTPSPhase1 provisions all route matchers, determines which domain names found in the routes qualify for automatic HTTPS, and sets up HTTP->HTTPS redirects. This phase must occur at the beginning of provisioning, because it may add routes and even servers to the app, which still need to be set up with the rest of them during provisioning.

automaticHTTPSPhase1 (ctx caddy.Context, repl *caddy.Replacer) error
References: caddytls.ConnectionPolicies, caddytls.ConnectionPolicy, caddytls.InternalIssuer, certmagic.SubjectIsIP, certmagic.SubjectQualifiesForCert, certmagic.SubjectQualifiesForPublicCert, fmt.Errorf, slices.Contains, strings.Contains, strings.Count, strings.HasPrefix, strings.Index, strings.Trim, zap.Int, zap.Reflect, zap.String.

func automaticHTTPSPhase2

automaticHTTPSPhase2 begins certificate management for all names in the qualifying domain set for each server. This phase must occur after provisioning and at the end of app start, after all the servers have been started. Doing this last ensures that there won't be any race for listeners on the HTTP or HTTPS ports when management is async (if CertMagic's solvers bind to those ports first, then our servers would fail to bind to them, which would be bad, since CertMagic's bindings are temporary and don't serve the user's sites!).

automaticHTTPSPhase2 () error
References: fmt.Errorf, zap.Strings.

func createAutomationPolicies

createAutomationPolicies ensures that automated certificates for this app are managed properly. This adds up to two automation policies: one for the public names, and one for the internal names. If a catch-all automation policy exists, it will be shallow-copied and used as the base for the new ones (this is important for preserving behavior the user intends to be "defaults").

createAutomationPolicies (ctx caddy.Context, internalNames,tailscaleNames []string) error
References: caddytls.ACMEIssuer, caddytls.AutomationConfig, caddytls.AutomationPolicy, caddytls.DefaultIssuersProvisioned, caddytls.InternalIssuer, caddytls.Tailscale, certmagic.Issuer, certmagic.Manager, fmt.Errorf.

func fillInACMEIssuer

fillInACMEIssuer fills in default values into acmeIssuer that are defined in app; these values at time of writing are just app.HTTPPort and app.HTTPSPort, which are used by ACMEIssuer. Sure, we could just use the global/CertMagic defaults, but if a user has configured those ports in the HTTP app, it makes sense to use them in the TLS app too, even if they forgot (or were too lazy, like me) to set it in each automation policy that uses it -- this just makes things a little less tedious for the user, so they don't have to repeat those ports in potentially many places. This function never steps on existing config values. If any changes are made, acmeIssuer is reprovisioned. acmeIssuer must not be nil.

fillInACMEIssuer (acmeIssuer *caddytls.ACMEIssuer) error
References: caddytls.ChallengesConfig, caddytls.HTTPChallengeConfig, caddytls.TLSALPNChallengeConfig.

func httpPort

httpPort () int

func httpsPort

httpsPort () int

func makeRedirRoute

makeRedirRoute (redirToPort uint, matcherSet MatcherSet) Route
References: http.Header, http.StatusPermanentRedirect, strconv.Itoa.

func configureServer

configureServer applies/binds the registered callback functions to the server.

configureServer (server *http.Server)
References: context.Context, http.ConnState, net.Conn.

func enforcementHandler

enforcementHandler is an implicit middleware which performs standard checks before executing the HTTP middleware chain.

enforcementHandler (w http.ResponseWriter, r *http.Request, next Handler) error
References: fmt.Errorf, http.StatusMisdirectedRequest, net.SplitHostPort, strings.EqualFold.

func findLastRouteWithHostMatcher

findLastRouteWithHostMatcher returns the index of the last route in the server which has a host matcher. Used during Automatic HTTPS to determine where to insert the HTTP->HTTPS redirect route, such that it is after any other host matcher but before any "catch-all" route without a host matcher.

findLastRouteWithHostMatcher () int

func hasListenerAddress

hasListenerAddress returns true if s has a listener at the given address fullAddr. Currently, fullAddr must represent exactly one socket address (port ranges are not supported)

hasListenerAddress (fullAddr string) bool
References: runtime.GOOS.

func hasTLSClientAuth

hasTLSClientAuth () bool
References: caddytls.ConnectionPolicy, slices.ContainsFunc.

func listenersUseAnyPortOtherThan

listenersUseAnyPortOtherThan returns true if there are any listeners in s that use a port which is not otherPort.

listenersUseAnyPortOtherThan (otherPort int) bool

func logRequest

logRequest logs the request to access logs, unless skipped.

logRequest (accLog *zap.Logger, r *http.Request, wrec ResponseRecorder, duration *time.Duration, repl *caddy.Replacer, bodyReader *lengthReader, shouldLogCredentials bool)
References: zap.Duration, zap.Int, zap.Logger, zap.Object, zap.String, zapcore.ErrorLevel, zapcore.Field, zapcore.InfoLevel.

func logTrace

logTrace will log that this middleware handler is being invoked. It emits at DEBUG level.

logTrace (mh MiddlewareHandler)
References: zap.Any, zapcore.DebugLevel.

func protocol

protocol returns true if the protocol proto is configured/enabled.

protocol (proto string) bool
References: slices.Contains.

func serveHTTP3

serveHTTP3 creates a QUIC listener, configures an HTTP/3 server if not already done, and then uses that server to serve HTTP/3 over the listener, with Server s as the handler.

serveHTTP3 (addr caddy.NetworkAddress, tlsCfg *tls.Config) error
References: fmt.Errorf, http3.Server, net.ListenConfig, qlog.DefaultConnectionTracer, time.Duration.

func shouldLogRequest

shouldLogRequest returns true if this request should be logged.

shouldLogRequest (r *http.Request) bool
References: certmagic.MatchWildcard, net.SplitHostPort.

func wrapPrimaryRoute

wrapPrimaryRoute wraps stack (a compiled middleware handler chain) in s.enforcementHandler which performs crucial security checks, etc.

wrapPrimaryRoute (stack Handler) Handler
References: http.Request, http.ResponseWriter.

func clone

clone () *ServerLogConfig

func updateReadSize

updateReadSize (n int)

func runHook

runHook (conn net.Conn, state http.ConnState)

func serveHttp2

serveHttp2 (csc connectionStateConn)
References: atomic.AddUint64, context.Background, http.StateClosed, http.StateNew, http2.ServeConnOpts.

func setReadSize

Private interface so it can only be used in this package #TODO: maybe export it later

setReadSize (size *int)

func caddyPlaceholderFunc

caddyPlaceholderFunc implements the custom CEL function that accesses the Replacer on a request and gets values from it.

caddyPlaceholderFunc (lhs,rhs ref.Val) ref.Val
References: types.NewErr, types.String.

func fuzzy

fuzzy returns true if the given hostname h is not a specific hostname, e.g. has placeholders or wildcards.

fuzzy (h string) bool
References: strings.ContainsAny.

func large

large returns true if m is considered to be large. Optimizing the matcher for smaller lists has diminishing returns. See related benchmark function in test file to conduct experiments.

large () bool

func matchPatternWithEscapeSequence

matchPatternWithEscapeSequence (escapedPath,matchPath string) bool
References: path.Match, strings.Builder, strings.IndexByte, strings.ReplaceAll, strings.ToLower, url.PathUnescape.

func matchStatusCode

matchStatusCode (statusCode int) bool

func getLoggerHosts

getLoggerHosts (host string) []string
References: strings.Join, strings.Split.

func wrapLogger

wrapLogger wraps logger in one or more logger named according to user preferences for the given host.

wrapLogger (logger *zap.Logger, req *http.Request) []*zap.Logger
References: net.SplitHostPort, zap.Logger.


Tests

Files: 9. Third party imports: 4. Imports from organisation: 0. Tests: 44. Benchmarks: 8.

Vars

var (
	clientCert	= []byte(`-----BEGIN CERTIFICATE-----
MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk
eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG
A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF
z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+
fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ
BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A
AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+
eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH
9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g=
-----END CERTIFICATE-----`)

	matcherTests	= []struct {
		name			string
		expression		*MatchExpression
		urlTarget		string
		httpMethod		string
		httpHeader		*http.Header
		wantErr			bool
		wantResult		bool
		clientCertificate	[]byte
	}{
		{
			name:	"boolean matches succeed for placeholder http.request.tls.client.subject",
			expression: &MatchExpression{
				Expr: "{http.request.tls.client.subject} == 'CN=client.localdomain'",
			},
			clientCertificate:	clientCert,
			urlTarget:		"https://example.com/foo",
			wantResult:		true,
		},
		{
			name:	"header matches (MatchHeader)",
			expression: &MatchExpression{
				Expr: `header({'Field': 'foo'})`,
			},
			urlTarget:	"https://example.com/foo",
			httpHeader:	&http.Header{"Field": []string{"foo", "bar"}},
			wantResult:	true,
		},
		{
			name:	"header matches an escaped placeholder value (MatchHeader)",
			expression: &MatchExpression{
				Expr: `header({'Field': '\\\{foobar}'})`,
			},
			urlTarget:	"https://example.com/foo",
			httpHeader:	&http.Header{"Field": []string{"{foobar}"}},
			wantResult:	true,
		},
		{
			name:	"header matches an placeholder replaced during the header matcher (MatchHeader)",
			expression: &MatchExpression{
				Expr: `header({'Field': '\{http.request.uri.path}'})`,
			},
			urlTarget:	"https://example.com/foo",
			httpHeader:	&http.Header{"Field": []string{"/foo"}},
			wantResult:	true,
		},
		{
			name:	"header error, invalid escape sequence (MatchHeader)",
			expression: &MatchExpression{
				Expr: `header({'Field': '\\{foobar}'})`,
			},
			wantErr:	true,
		},
		{
			name:	"header error, needs to be JSON syntax with field as key (MatchHeader)",
			expression: &MatchExpression{
				Expr: `header('foo')`,
			},
			wantErr:	true,
		},
		{
			name:	"header_regexp matches (MatchHeaderRE)",
			expression: &MatchExpression{
				Expr: `header_regexp('Field', 'fo{2}')`,
			},
			urlTarget:	"https://example.com/foo",
			httpHeader:	&http.Header{"Field": []string{"foo", "bar"}},
			wantResult:	true,
		},
		{
			name:	"header_regexp matches with name (MatchHeaderRE)",
			expression: &MatchExpression{
				Expr: `header_regexp('foo', 'Field', 'fo{2}')`,
			},
			urlTarget:	"https://example.com/foo",
			httpHeader:	&http.Header{"Field": []string{"foo", "bar"}},
			wantResult:	true,
		},
		{
			name:	"header_regexp does not match (MatchHeaderRE)",
			expression: &MatchExpression{
				Expr: `header_regexp('foo', 'Nope', 'fo{2}')`,
			},
			urlTarget:	"https://example.com/foo",
			httpHeader:	&http.Header{"Field": []string{"foo", "bar"}},
			wantResult:	false,
		},
		{
			name:	"header_regexp error (MatchHeaderRE)",
			expression: &MatchExpression{
				Expr: `header_regexp('foo')`,
			},
			wantErr:	true,
		},
		{
			name:	"host matches localhost (MatchHost)",
			expression: &MatchExpression{
				Expr: `host('localhost')`,
			},
			urlTarget:	"http://localhost",
			wantResult:	true,
		},
		{
			name:	"host matches (MatchHost)",
			expression: &MatchExpression{
				Expr: `host('*.example.com')`,
			},
			urlTarget:	"https://foo.example.com",
			wantResult:	true,
		},
		{
			name:	"host does not match (MatchHost)",
			expression: &MatchExpression{
				Expr: `host('example.net', '*.example.com')`,
			},
			urlTarget:	"https://foo.example.org",
			wantResult:	false,
		},
		{
			name:	"host error (MatchHost)",
			expression: &MatchExpression{
				Expr: `host(80)`,
			},
			wantErr:	true,
		},
		{
			name:	"method does not match (MatchMethod)",
			expression: &MatchExpression{
				Expr: `method('PUT')`,
			},
			urlTarget:	"https://foo.example.com",
			httpMethod:	"GET",
			wantResult:	false,
		},
		{
			name:	"method matches (MatchMethod)",
			expression: &MatchExpression{
				Expr: `method('DELETE', 'PUT', 'POST')`,
			},
			urlTarget:	"https://foo.example.com",
			httpMethod:	"PUT",
			wantResult:	true,
		},
		{
			name:	"method error not enough arguments (MatchMethod)",
			expression: &MatchExpression{
				Expr: `method()`,
			},
			wantErr:	true,
		},
		{
			name:	"path matches substring (MatchPath)",
			expression: &MatchExpression{
				Expr: `path('*substring*')`,
			},
			urlTarget:	"https://example.com/foo/substring/bar.txt",
			wantResult:	true,
		},
		{
			name:	"path does not match (MatchPath)",
			expression: &MatchExpression{
				Expr: `path('/foo')`,
			},
			urlTarget:	"https://example.com/foo/bar",
			wantResult:	false,
		},
		{
			name:	"path matches end url fragment (MatchPath)",
			expression: &MatchExpression{
				Expr: `path('/foo')`,
			},
			urlTarget:	"https://example.com/FOO",
			wantResult:	true,
		},
		{
			name:	"path matches end fragment with substring prefix (MatchPath)",
			expression: &MatchExpression{
				Expr: `path('/foo*')`,
			},
			urlTarget:	"https://example.com/FOOOOO",
			wantResult:	true,
		},
		{
			name:	"path matches one of multiple (MatchPath)",
			expression: &MatchExpression{
				Expr: `path('/foo', '/foo/*', '/bar', '/bar/*', '/baz', '/baz*')`,
			},
			urlTarget:	"https://example.com/foo",
			wantResult:	true,
		},
		{
			name:	"path_regexp with empty regex matches empty path (MatchPathRE)",
			expression: &MatchExpression{
				Expr: `path_regexp('')`,
			},
			urlTarget:	"https://example.com/",
			wantResult:	true,
		},
		{
			name:	"path_regexp with slash regex matches empty path (MatchPathRE)",
			expression: &MatchExpression{
				Expr: `path_regexp('/')`,
			},
			urlTarget:	"https://example.com/",
			wantResult:	true,
		},
		{
			name:	"path_regexp matches end url fragment (MatchPathRE)",
			expression: &MatchExpression{
				Expr: `path_regexp('^/foo')`,
			},
			urlTarget:	"https://example.com/foo/",
			wantResult:	true,
		},
		{
			name:	"path_regexp does not match fragment at end (MatchPathRE)",
			expression: &MatchExpression{
				Expr: `path_regexp('bar_at_start', '^/bar')`,
			},
			urlTarget:	"https://example.com/foo/bar",
			wantResult:	false,
		},
		{
			name:	"protocol matches (MatchProtocol)",
			expression: &MatchExpression{
				Expr: `protocol('HTTPs')`,
			},
			urlTarget:	"https://example.com",
			wantResult:	true,
		},
		{
			name:	"protocol does not match (MatchProtocol)",
			expression: &MatchExpression{
				Expr: `protocol('grpc')`,
			},
			urlTarget:	"https://example.com",
			wantResult:	false,
		},
		{
			name:	"protocol invocation error no args (MatchProtocol)",
			expression: &MatchExpression{
				Expr: `protocol()`,
			},
			wantErr:	true,
		},
		{
			name:	"protocol invocation error too many args (MatchProtocol)",
			expression: &MatchExpression{
				Expr: `protocol('grpc', 'https')`,
			},
			wantErr:	true,
		},
		{
			name:	"protocol invocation error wrong arg type (MatchProtocol)",
			expression: &MatchExpression{
				Expr: `protocol(true)`,
			},
			wantErr:	true,
		},
		{
			name:	"query does not match against a specific value (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query({"debug": "1"})`,
			},
			urlTarget:	"https://example.com/foo",
			wantResult:	false,
		},
		{
			name:	"query matches against a specific value (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query({"debug": "1"})`,
			},
			urlTarget:	"https://example.com/foo/?debug=1",
			wantResult:	true,
		},
		{
			name:	"query matches against multiple values (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query({"debug": ["0", "1", {http.request.uri.query.debug}+"1"]})`,
			},
			urlTarget:	"https://example.com/foo/?debug=1",
			wantResult:	true,
		},
		{
			name:	"query matches against a wildcard (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query({"debug": ["*"]})`,
			},
			urlTarget:	"https://example.com/foo/?debug=something",
			wantResult:	true,
		},
		{
			name:	"query matches against a placeholder value (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query({"debug": {http.request.uri.query.debug}})`,
			},
			urlTarget:	"https://example.com/foo/?debug=1",
			wantResult:	true,
		},
		{
			name:	"query error bad map key type (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query({1: "1"})`,
			},
			wantErr:	true,
		},
		{
			name:	"query error typed struct instead of map (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query(Message{field: "1"})`,
			},
			wantErr:	true,
		},
		{
			name:	"query error bad map value type (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query({"debug": 1})`,
			},
			wantErr:	true,
		},
		{
			name:	"query error no args (MatchQuery)",
			expression: &MatchExpression{
				Expr: `query()`,
			},
			wantErr:	true,
		},
		{
			name:	"remote_ip error no args (MatchRemoteIP)",
			expression: &MatchExpression{
				Expr: `remote_ip()`,
			},
			wantErr:	true,
		},
		{
			name:	"remote_ip single IP match (MatchRemoteIP)",
			expression: &MatchExpression{
				Expr: `remote_ip('192.0.2.1')`,
			},
			urlTarget:	"https://example.com/foo",
			wantResult:	true,
		},
		{
			name:	"vars value (VarsMatcher)",
			expression: &MatchExpression{
				Expr: `vars({'foo': 'bar'})`,
			},
			urlTarget:	"https://example.com/foo",
			wantResult:	true,
		},
		{
			name:	"vars matches placeholder, needs escape (VarsMatcher)",
			expression: &MatchExpression{
				Expr: `vars({'\{http.request.uri.path}': '/foo'})`,
			},
			urlTarget:	"https://example.com/foo",
			wantResult:	true,
		},
		{
			name:	"vars error wrong syntax (VarsMatcher)",
			expression: &MatchExpression{
				Expr: `vars('foo', 'bar')`,
			},
			wantErr:	true,
		},
		{
			name:	"vars error no args (VarsMatcher)",
			expression: &MatchExpression{
				Expr: `vars()`,
			},
			wantErr:	true,
		},
		{
			name:	"vars_regexp value (MatchVarsRE)",
			expression: &MatchExpression{
				Expr: `vars_regexp('foo', 'ba?r')`,
			},
			urlTarget:	"https://example.com/foo",
			wantResult:	true,
		},
		{
			name:	"vars_regexp value with name (MatchVarsRE)",
			expression: &MatchExpression{
				Expr: `vars_regexp('name', 'foo', 'ba?r')`,
			},
			urlTarget:	"https://example.com/foo",
			wantResult:	true,
		},
		{
			name:	"vars_regexp matches placeholder, needs escape (MatchVarsRE)",
			expression: &MatchExpression{
				Expr: `vars_regexp('\{http.request.uri.path}', '/fo?o')`,
			},
			urlTarget:	"https://example.com/foo",
			wantResult:	true,
		},
		{
			name:	"vars_regexp error no args (MatchVarsRE)",
			expression: &MatchExpression{
				Expr: `vars_regexp()`,
			},
			wantErr:	true,
		},
	}
)

Types

baseRespWriter

a barebones http.ResponseWriter mock

type baseRespWriter []byte

middlewareHandlerFunc

This type doesn't have documentation.

type middlewareHandlerFunc func(http.ResponseWriter, *http.Request, Handler) error

nopSyncer

This type doesn't have documentation.

type nopSyncer writeFunc

readFromRespWriter

an http.ResponseWriter mock that supports ReadFrom

type readFromRespWriter struct {
	baseRespWriter
	called	bool
}

responseWriterSpy

This type doesn't have documentation.

type responseWriterSpy interface {
	http.ResponseWriter
	Written() string
	CalledReadFrom() bool
}

writeFunc

This type doesn't have documentation.

type writeFunc func(p []byte) (int, error)

Test functions

TestCleanPath

TestHTTPVarReplacement

References: context.WithValue, http.LocalAddrContextKey, http.MethodGet, http.NewRequest, httptest.NewRecorder, net.ResolveTCPAddr, pem.Decode, tls.ConnectionState, tls.TLS_AES_256_GCM_SHA384, tls.VersionTLS13, x509.Certificate, x509.ParseCertificate.

TestHeaderMatcher

References: context.WithValue, http.Header, http.Request.

TestHeaderREMatcher

References: context.WithValue, fmt.Sprintf, http.Header, http.Request, httptest.NewRecorder, url.URL.

TestHostMatcher

References: context.WithValue, http.Request, os.Setenv.

TestMatchExpressionMatch

References: context.Background, context.WithValue, httptest.NewRecorder, httptest.NewRequest, pem.Decode, testing.T, tls.ConnectionState, x509.Certificate, x509.ParseCertificate.

TestMatchExpressionProvision

References: context.Background, testing.T.

TestMetricsInstrumentedHandler

References: context.Background, errors.New, http.Request, http.ResponseWriter, http.StatusTooManyRequests, httptest.NewRecorder, httptest.NewRequest, strings.NewReader, sync.Once, testutil.GatherAndCompare, testutil.ToFloat64.

TestMetricsInstrumentedHandlerPerHost

References: context.Background, errors.New, http.Request, http.ResponseWriter, http.StatusTooManyRequests, httptest.NewRecorder, httptest.NewRequest, strings.NewReader, sync.Once, testutil.GatherAndCompare, testutil.ToFloat64.

TestNotMatcher

References: context.WithValue, http.Request, url.URL.

TestPathMatcher

References: context.WithValue, http.Request, url.ParseRequestURI.

TestPathMatcherWindows

References: context.WithValue, http.Request, runtime.GOOS, url.URL.

TestPathREMatcher

References: context.WithValue, fmt.Sprintf, http.Request, httptest.NewRecorder, url.ParseRequestURI.

TestQueryMatcher

References: context.WithValue, http.Request, url.Parse.

TestResponseMatcher

References: http.Header.

TestResponseRecorderReadFrom

References: bytes.Buffer, http.Header, io.Copy, io.Reader, strings.NewReader, testing.T.

TestResponseWriterWrapperReadFrom

References: io.Copy, io.Reader, strings.NewReader, testing.T.

TestResponseWriterWrapperUnwrap

TestSanitizedPathJoin

References: filepath.FromSlash, filepath.Join, runtime.GOOS, url.Parse.

TestServerNameFromContext

References: context.Background, context.WithValue.

TestServer_DetermineTrustedProxy_MatchLeftMostValidIp

References: assert.Equal, assert.True, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_DetermineTrustedProxy_MatchRightMostUntrusted

References: assert.Equal, assert.True, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_DetermineTrustedProxy_MatchRightMostUntrustedFirst

References: assert.Equal, assert.True, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_DetermineTrustedProxy_MatchRightMostUntrustedSkippingEmpty

References: assert.Equal, assert.True, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_DetermineTrustedProxy_MatchRightMostUntrustedSkippingTrusted

References: assert.Equal, assert.True, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_DetermineTrustedProxy_MultipleTrustedClientHeaders

References: assert.Equal, assert.True, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_DetermineTrustedProxy_MultipleTrustedPrefixes

References: assert.Equal, assert.True, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_DetermineTrustedProxy_NoConfig

References: assert.Equal, assert.False, httptest.NewRequest.

TestServer_DetermineTrustedProxy_NoConfigIpv6

References: assert.Equal, assert.False, httptest.NewRequest.

TestServer_DetermineTrustedProxy_NoConfigIpv6Zones

References: assert.Equal, assert.False, httptest.NewRequest.

TestServer_DetermineTrustedProxy_TrustedLoopback

References: assert.Equal, assert.True, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_DetermineTrustedProxy_UntrustedPrefix

References: assert.Equal, assert.False, httptest.NewRequest, netip.ParsePrefix, netip.Prefix.

TestServer_LogRequest

References: assert.JSONEq, bytes.Buffer, context.Background, context.WithValue, http.MethodGet, httptest.NewRecorder, httptest.NewRequest, time.Millisecond.

TestServer_LogRequest_WithTrace

References: assert.JSONEq, bytes.Buffer, context.Background, context.WithValue, http.MethodGet, httptest.NewRecorder, httptest.NewRequest, time.Millisecond, zap.String.

TestServer_TrustedRealClientIP_IncludesPort

References: assert.Equal, httptest.NewRequest.

TestServer_TrustedRealClientIP_MultipleTrustedHeaderValidArray

References: assert.Equal, httptest.NewRequest.

TestServer_TrustedRealClientIP_NoTrustedHeaders

References: assert.Equal, httptest.NewRequest.

TestServer_TrustedRealClientIP_OneTrustedHeaderEmpty

References: assert.Equal, httptest.NewRequest.

TestServer_TrustedRealClientIP_OneTrustedHeaderInvalid

References: assert.Equal, httptest.NewRequest.

TestServer_TrustedRealClientIP_OneTrustedHeaderValid

References: assert.Equal, httptest.NewRequest.

TestServer_TrustedRealClientIP_OneTrustedHeaderValidArray

References: assert.Equal, httptest.NewRequest.

TestServer_TrustedRealClientIP_SkipsInvalidIps

References: assert.Equal, httptest.NewRequest.

TestStaticResponseHandler

References: http.Header, http.StatusNotFound, httptest.NewRecorder, io.ReadAll, strconv.Itoa.

TestVarREMatcher

References: context.WithValue, fmt.Sprintf, http.MethodGet, http.Request, httptest.NewRecorder, testing.T, url.URL.

Benchmark functions

BenchmarkHeaderREMatcher

References: context.WithValue, http.Header, http.Request, httptest.NewRecorder, url.URL.

BenchmarkHostMatcherWithPlaceholder

References: context.WithValue, http.Request, os.Setenv.

BenchmarkHostMatcherWithoutPlaceholder

References: context.WithValue, http.Request.

BenchmarkLargeHostMatcher

References: context.WithValue, fmt.Sprintf, http.Request.

BenchmarkMatchExpressionMatch

References: context.WithValue, httptest.NewRecorder, httptest.NewRequest, pem.Decode, testing.B, tls.ConnectionState, x509.Certificate, x509.ParseCertificate.

BenchmarkServer_LogRequest

References: context.Background, context.WithValue, http.MethodGet, httptest.NewRecorder, httptest.NewRequest, io.Discard, time.Millisecond.

BenchmarkServer_LogRequest_NopLogger

References: context.Background, context.WithValue, http.MethodGet, httptest.NewRecorder, httptest.NewRequest, time.Millisecond, zap.NewNop.

BenchmarkServer_LogRequest_WithTrace

References: context.Background, context.WithValue, http.MethodGet, httptest.NewRecorder, httptest.NewRequest, io.Discard, time.Millisecond, zap.String.