mirror of
https://github.com/caddyserver/caddy.git
synced 2025-04-24 13:54:05 +08:00
Merge branch 'master' into prefer-wildcard
This commit is contained in:
commit
14bc38010e
2
.github/workflows/cross-build.yml
vendored
2
.github/workflows/cross-build.yml
vendored
@ -70,4 +70,4 @@ jobs:
|
||||
continue-on-error: true
|
||||
working-directory: ./cmd/caddy
|
||||
run: |
|
||||
GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
||||
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
||||
|
@ -83,6 +83,8 @@ builds:
|
||||
- -s -w
|
||||
tags:
|
||||
- nobadger
|
||||
- nomysql
|
||||
- nopgx
|
||||
|
||||
signs:
|
||||
- cmd: cosign
|
||||
|
@ -131,7 +131,7 @@ $ xcaddy build
|
||||
4. Initialize a Go module: `go mod init caddy`
|
||||
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name.
|
||||
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
|
||||
7. Compile: `go build`
|
||||
7. Compile: `go build -tags=nobadger,nomysql,nopgx`
|
||||
|
||||
|
||||
|
||||
|
@ -31,7 +31,7 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
// mapAddressToServerBlocks returns a map of listener address to list of server
|
||||
// mapAddressToProtocolToServerBlocks returns a map of listener address to list of server
|
||||
// blocks that will be served on that address. To do this, each server block is
|
||||
// expanded so that each one is considered individually, although keys of a
|
||||
// server block that share the same address stay grouped together so the config
|
||||
@ -329,8 +329,12 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad
|
||||
// use a map to prevent duplication
|
||||
listeners := map[string]map[string]struct{}{}
|
||||
for _, lnCfgVal := range lnCfgVals {
|
||||
for _, lnHost := range lnCfgVal.addresses {
|
||||
networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort)
|
||||
for _, lnAddr := range lnCfgVal.addresses {
|
||||
lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("splitting listener address: %v", err)
|
||||
}
|
||||
networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing network address: %v", err)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -186,12 +187,25 @@ func (st ServerType) Setup(
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
// hoist the metrics config from per-server to global
|
||||
metrics, _ := options["metrics"].(*caddyhttp.Metrics)
|
||||
for _, s := range servers {
|
||||
if s.Metrics != nil {
|
||||
metrics = cmp.Or[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{})
|
||||
metrics = &caddyhttp.Metrics{
|
||||
PerHost: metrics.PerHost || s.Metrics.PerHost,
|
||||
}
|
||||
s.Metrics = nil // we don't need it anymore
|
||||
}
|
||||
}
|
||||
|
||||
// now that each server is configured, make the HTTP app
|
||||
httpApp := caddyhttp.App{
|
||||
HTTPPort: tryInt(options["http_port"], &warnings),
|
||||
HTTPSPort: tryInt(options["https_port"], &warnings),
|
||||
GracePeriod: tryDuration(options["grace_period"], &warnings),
|
||||
ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings),
|
||||
Metrics: metrics,
|
||||
Servers: servers,
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
)
|
||||
|
||||
@ -53,6 +54,7 @@ func init() {
|
||||
RegisterGlobalOption("local_certs", parseOptTrue)
|
||||
RegisterGlobalOption("key_type", parseOptSingleString)
|
||||
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
|
||||
RegisterGlobalOption("metrics", parseMetricsOptions)
|
||||
RegisterGlobalOption("servers", parseServerOptions)
|
||||
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
||||
RegisterGlobalOption("cert_lifetime", parseOptDuration)
|
||||
@ -446,6 +448,24 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
d.Next() // consume option name
|
||||
metrics := new(caddyhttp.Metrics)
|
||||
for d.NextBlock(0) {
|
||||
switch d.Val() {
|
||||
case "per_host":
|
||||
metrics.PerHost = true
|
||||
default:
|
||||
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
|
||||
}
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
return unmarshalCaddyfileMetricsOptions(d)
|
||||
}
|
||||
|
||||
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
return unmarshalCaddyfileServerOptions(d)
|
||||
}
|
||||
|
@ -240,6 +240,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
}
|
||||
|
||||
case "metrics":
|
||||
caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.")
|
||||
serverOpts.Metrics = new(caddyhttp.Metrics)
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
|
@ -0,0 +1,39 @@
|
||||
{
|
||||
metrics
|
||||
servers :80 {
|
||||
metrics {
|
||||
per_host
|
||||
}
|
||||
}
|
||||
}
|
||||
:80 {
|
||||
respond "Hello"
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Hello",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"per_host": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -26,11 +26,11 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"per_host": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"per_host": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppendsRecords appends DNS records to the zone.
|
||||
// AppendRecords appends DNS records to the zone.
|
||||
func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -409,12 +409,13 @@ latest versions. EXPERIMENTAL: May be changed or removed.
|
||||
|
||||
RegisterCommand(Command{
|
||||
Name: "add-package",
|
||||
Usage: "<packages...>",
|
||||
Usage: "<package[@version]...>",
|
||||
Short: "Adds Caddy packages (EXPERIMENTAL)",
|
||||
Long: `
|
||||
Downloads an updated Caddy binary with the specified packages (module/plugin)
|
||||
added. Retains existing packages. Returns an error if the any of packages are
|
||||
already included. EXPERIMENTAL: May be changed or removed.
|
||||
added, with an optional version specified (e.g., "package@version"). Retains
|
||||
existing packages. Returns an error if any of the specified packages are already
|
||||
included. EXPERIMENTAL: May be changed or removed.
|
||||
`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
|
||||
|
@ -46,6 +46,25 @@ func cmdUpgrade(fl Flags) (int, error) {
|
||||
return upgradeBuild(pluginPkgs, fl)
|
||||
}
|
||||
|
||||
func splitModule(arg string) (module, version string, err error) {
|
||||
const versionSplit = "@"
|
||||
|
||||
// accommodate module paths that have @ in them, but we can only tolerate that if there's also
|
||||
// a version, otherwise we don't know if it's a version separator or part of the file path
|
||||
lastVersionSplit := strings.LastIndex(arg, versionSplit)
|
||||
if lastVersionSplit < 0 {
|
||||
module = arg
|
||||
} else {
|
||||
module, version = arg[:lastVersionSplit], arg[lastVersionSplit+1:]
|
||||
}
|
||||
|
||||
if module == "" {
|
||||
err = fmt.Errorf("module name is required")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func cmdAddPackage(fl Flags) (int, error) {
|
||||
if len(fl.Args()) == 0 {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified")
|
||||
@ -60,10 +79,15 @@ func cmdAddPackage(fl Flags) (int, error) {
|
||||
}
|
||||
|
||||
for _, arg := range fl.Args() {
|
||||
if _, ok := pluginPkgs[arg]; ok {
|
||||
module, version, err := splitModule(arg)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
|
||||
}
|
||||
// only allow a version to be specified if it's different from the existing version
|
||||
if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
|
||||
}
|
||||
pluginPkgs[arg] = struct{}{}
|
||||
pluginPkgs[module] = pluginPackage{Version: version, Path: module}
|
||||
}
|
||||
|
||||
return upgradeBuild(pluginPkgs, fl)
|
||||
@ -83,7 +107,11 @@ func cmdRemovePackage(fl Flags) (int, error) {
|
||||
}
|
||||
|
||||
for _, arg := range fl.Args() {
|
||||
if _, ok := pluginPkgs[arg]; !ok {
|
||||
module, _, err := splitModule(arg)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
|
||||
}
|
||||
if _, ok := pluginPkgs[module]; !ok {
|
||||
// package does not exist
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added")
|
||||
}
|
||||
@ -93,7 +121,7 @@ func cmdRemovePackage(fl Flags) (int, error) {
|
||||
return upgradeBuild(pluginPkgs, fl)
|
||||
}
|
||||
|
||||
func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
|
||||
func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) {
|
||||
l := caddy.Log()
|
||||
|
||||
thisExecPath, err := os.Executable()
|
||||
@ -120,8 +148,8 @@ func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
|
||||
"os": {runtime.GOOS},
|
||||
"arch": {runtime.GOARCH},
|
||||
}
|
||||
for pkg := range pluginPkgs {
|
||||
qs.Add("p", pkg)
|
||||
for _, pkgInfo := range pluginPkgs {
|
||||
qs.Add("p", pkgInfo.String())
|
||||
}
|
||||
|
||||
// initiate the build
|
||||
@ -276,14 +304,14 @@ func downloadBuild(qs url.Values) (*http.Response, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func getPluginPackages(modules []moduleInfo) (map[string]struct{}, error) {
|
||||
pluginPkgs := make(map[string]struct{})
|
||||
func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) {
|
||||
pluginPkgs := make(map[string]pluginPackage)
|
||||
for _, mod := range modules {
|
||||
if mod.goModule.Replace != nil {
|
||||
return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s",
|
||||
mod.goModule.Path, mod.goModule.Replace.Path)
|
||||
}
|
||||
pluginPkgs[mod.goModule.Path] = struct{}{}
|
||||
pluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path}
|
||||
}
|
||||
return pluginPkgs, nil
|
||||
}
|
||||
@ -312,3 +340,15 @@ func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) er
|
||||
}
|
||||
|
||||
const downloadPath = "https://caddyserver.com/api/download"
|
||||
|
||||
type pluginPackage struct {
|
||||
Version string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (p pluginPackage) String() string {
|
||||
if p.Version == "" {
|
||||
return p.Path
|
||||
}
|
||||
return p.Path + "@" + p.Version
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -19,7 +19,7 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.2.8
|
||||
github.com/mholt/acmez/v2 v2.0.3
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/quic-go/quic-go v0.48.0
|
||||
github.com/quic-go/quic-go v0.48.1
|
||||
github.com/smallstep/certificates v0.26.1
|
||||
github.com/smallstep/nosql v0.6.1
|
||||
github.com/smallstep/truststore v0.13.0
|
||||
|
4
go.sum
4
go.sum
@ -392,8 +392,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.48.0 h1:2TCyvBrMu1Z25rvIAlnp2dPT4lgh/uTqLqiXVpp5AeU=
|
||||
github.com/quic-go/quic-go v0.48.0/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA=
|
||||
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
|
52
listeners.go
52
listeners.go
@ -305,25 +305,6 @@ func IsFdNetwork(netw string) bool {
|
||||
return strings.HasPrefix(netw, "fd")
|
||||
}
|
||||
|
||||
// normally we would simply append the port,
|
||||
// but if host is IPv6, we need to ensure it
|
||||
// is enclosed in [ ]; net.JoinHostPort does
|
||||
// this for us, but host might also have a
|
||||
// network type in front (e.g. "tcp/") leading
|
||||
// to "[tcp/::1]" which causes parsing failures
|
||||
// later; what we need is "tcp/[::1]", so we have
|
||||
// to split the network and host, then re-combine
|
||||
func ParseNetworkAddressFromHostPort(host, port string) (NetworkAddress, error) {
|
||||
network, addr, ok := strings.Cut(host, "/")
|
||||
if !ok {
|
||||
addr = network
|
||||
network = ""
|
||||
}
|
||||
addr = strings.Trim(addr, "[]") // IPv6
|
||||
networkAddr := JoinNetworkAddress(network, addr, port)
|
||||
return ParseNetworkAddress(networkAddr)
|
||||
}
|
||||
|
||||
// ParseNetworkAddress parses addr into its individual
|
||||
// components. The input string is expected to be of
|
||||
// the form "network/host:port-range" where any part is
|
||||
@ -399,25 +380,28 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
|
||||
if slashFound {
|
||||
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
||||
a = afterSlash
|
||||
if IsUnixNetwork(network) || IsFdNetwork(network) {
|
||||
host = a
|
||||
return
|
||||
}
|
||||
}
|
||||
if IsUnixNetwork(network) || IsFdNetwork(network) {
|
||||
host = a
|
||||
return
|
||||
}
|
||||
|
||||
host, port, err = net.SplitHostPort(a)
|
||||
if err == nil || a == "" {
|
||||
return
|
||||
}
|
||||
// in general, if there was an error, it was likely "missing port",
|
||||
// so try adding a bogus port to take advantage of standard library's
|
||||
// robust parser, then strip the artificial port before returning
|
||||
// (don't overwrite original error though; might still be relevant)
|
||||
var err2 error
|
||||
host, port, err2 = net.SplitHostPort(a + ":0")
|
||||
if err2 == nil {
|
||||
err = nil
|
||||
firstErr := err
|
||||
|
||||
if err != nil {
|
||||
// in general, if there was an error, it was likely "missing port",
|
||||
// so try removing square brackets around an IPv6 host, adding a bogus
|
||||
// port to take advantage of standard library's robust parser, then
|
||||
// strip the artificial port.
|
||||
host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0"))
|
||||
port = ""
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = errors.Join(firstErr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ func TestSplitNetworkAddress(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expectErr: true,
|
||||
expectHost: "",
|
||||
},
|
||||
{
|
||||
input: "foo",
|
||||
@ -42,7 +42,7 @@ func TestSplitNetworkAddress(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: "::",
|
||||
expectErr: true,
|
||||
expectHost: "::",
|
||||
},
|
||||
{
|
||||
input: "[::]",
|
||||
@ -77,7 +77,7 @@ func TestSplitNetworkAddress(t *testing.T) {
|
||||
{
|
||||
input: "udp/",
|
||||
expectNetwork: "udp",
|
||||
expectErr: true,
|
||||
expectHost: "",
|
||||
},
|
||||
{
|
||||
input: "unix//foo/bar",
|
||||
@ -185,7 +185,8 @@ func TestParseNetworkAddress(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expectErr: true,
|
||||
expectAddr: NetworkAddress{
|
||||
},
|
||||
},
|
||||
{
|
||||
input: ":",
|
||||
@ -311,7 +312,8 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expectErr: true,
|
||||
expectAddr: NetworkAddress{
|
||||
},
|
||||
},
|
||||
{
|
||||
input: ":",
|
||||
|
@ -15,6 +15,7 @@
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
@ -142,6 +143,10 @@ type App struct {
|
||||
// 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
|
||||
@ -184,6 +189,10 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
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 {
|
||||
@ -196,6 +205,15 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
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")
|
||||
@ -342,16 +360,11 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
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 {
|
||||
if srv.Metrics != nil {
|
||||
srv.Metrics.init = sync.Once{}
|
||||
srv.Metrics.httpMetrics = &httpMetrics{}
|
||||
}
|
||||
err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics)
|
||||
err := srv.Routes.ProvisionHandlers(ctx, app.Metrics)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
|
||||
}
|
||||
@ -370,7 +383,7 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
|
||||
// provision the named routes (they get compiled at runtime)
|
||||
for name, route := range srv.NamedRoutes {
|
||||
err := route.Provision(ctx, srv.Metrics)
|
||||
err := route.Provision(ctx, app.Metrics)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
|
||||
var try_policy string
|
||||
if len(values["try_policy"]) > 0 {
|
||||
root = values["try_policy"][0]
|
||||
try_policy = values["try_policy"][0]
|
||||
}
|
||||
|
||||
m := MatchFile{
|
||||
|
@ -289,6 +289,7 @@ var expressionTests = []struct {
|
||||
wantErr bool
|
||||
wantResult bool
|
||||
clientCertificate []byte
|
||||
expectedPath string
|
||||
}{
|
||||
{
|
||||
name: "file error no args (MatchFile)",
|
||||
@ -354,6 +355,15 @@ var expressionTests = []struct {
|
||||
urlTarget: "https://example.com/nopenope.txt",
|
||||
wantResult: false,
|
||||
},
|
||||
{
|
||||
name: "file match long pattern foo.txt with try_policy (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file({"root": "./testdata", "try_policy": "largest_size", "try_files": ["foo.txt", "large.txt"]})`,
|
||||
},
|
||||
urlTarget: "https://example.com/",
|
||||
wantResult: true,
|
||||
expectedPath: "/large.txt",
|
||||
},
|
||||
}
|
||||
|
||||
func TestMatchExpressionMatch(t *testing.T) {
|
||||
@ -382,6 +392,16 @@ func TestMatchExpressionMatch(t *testing.T) {
|
||||
if tc.expression.Match(req) != tc.wantResult {
|
||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
||||
}
|
||||
|
||||
if tc.expectedPath != "" {
|
||||
path, ok := repl.Get("http.matchers.file.relative")
|
||||
if !ok {
|
||||
t.Errorf("MatchExpression.Match() expected to return path '%s', but got none", tc.expectedPath)
|
||||
}
|
||||
if path != tc.expectedPath {
|
||||
t.Errorf("MatchExpression.Match() expected to return path '%s', but got '%s'", tc.expectedPath, path)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
3
modules/caddyhttp/fileserver/testdata/large.txt
vendored
Normal file
3
modules/caddyhttp/fileserver/testdata/large.txt
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
This is a file with more content than the other files in this directory
|
||||
such that tests using the largest_size policy pick this file, or the
|
||||
smallest_size policy avoids this file.
|
@ -200,9 +200,7 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
|
||||
for _, fieldName := range ops.Delete {
|
||||
fieldName = repl.ReplaceKnown(fieldName, "")
|
||||
if fieldName == "*" {
|
||||
for existingField := range hdr {
|
||||
delete(hdr, existingField)
|
||||
}
|
||||
clear(hdr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -133,8 +134,8 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
|
||||
statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""}
|
||||
|
||||
if h.metrics.PerHost {
|
||||
labels["host"] = r.Host
|
||||
statusLabels["host"] = r.Host
|
||||
labels["host"] = strings.ToLower(r.Host)
|
||||
statusLabels["host"] = strings.ToLower(r.Host)
|
||||
}
|
||||
|
||||
inFlight := h.metrics.httpMetrics.requestInFlight.With(labels)
|
||||
|
@ -807,17 +807,26 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
|
||||
shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials
|
||||
|
||||
// Forward 1xx status codes, backported from https://github.com/golang/go/pull/53164
|
||||
var (
|
||||
roundTripMutex sync.Mutex
|
||||
roundTripDone bool
|
||||
)
|
||||
trace := &httptrace.ClientTrace{
|
||||
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
|
||||
roundTripMutex.Lock()
|
||||
defer roundTripMutex.Unlock()
|
||||
if roundTripDone {
|
||||
// If RoundTrip has returned, don't try to further modify
|
||||
// the ResponseWriter's header map.
|
||||
return nil
|
||||
}
|
||||
h := rw.Header()
|
||||
copyHeader(h, http.Header(header))
|
||||
rw.WriteHeader(code)
|
||||
|
||||
// Clear headers coming from the backend
|
||||
// (it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses)
|
||||
for k := range header {
|
||||
delete(h, k)
|
||||
}
|
||||
clear(h)
|
||||
|
||||
return nil
|
||||
},
|
||||
@ -833,11 +842,18 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
|
||||
req = req.WithContext(context.WithoutCancel(req.Context()))
|
||||
}
|
||||
|
||||
// do the round-trip; emit debug log with values we know are
|
||||
// safe, or if there is no error, emit fuller log entry
|
||||
// do the round-trip
|
||||
start := time.Now()
|
||||
res, err := h.Transport.RoundTrip(req)
|
||||
duration := time.Since(start)
|
||||
|
||||
// record that the round trip is done for the 1xx response handler
|
||||
roundTripMutex.Lock()
|
||||
roundTripDone = true
|
||||
roundTripMutex.Unlock()
|
||||
|
||||
// emit debug log with values we know are safe,
|
||||
// or if there is no error, emit fuller log entry
|
||||
logger := h.logger.With(
|
||||
zap.String("upstream", di.Upstream.String()),
|
||||
zap.Duration("duration", duration),
|
||||
|
@ -103,7 +103,7 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
conn.Close()
|
||||
if c := logger.Check(zapcore.DebugLevel, "hijack failed on protocol switch"); c != nil {
|
||||
if c := logger.Check(zapcore.DebugLevel, "connection closed"); c != nil {
|
||||
c.Write(zap.Duration("duration", time.Since(start)))
|
||||
}
|
||||
}()
|
||||
|
@ -226,6 +226,7 @@ type Server struct {
|
||||
|
||||
// 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
|
||||
|
@ -69,12 +69,13 @@ func TestServer_LogRequest(t *testing.T) {
|
||||
}`, buf.String())
|
||||
}
|
||||
|
||||
func TestServer_LogRequest_WithTraceID(t *testing.T) {
|
||||
func TestServer_LogRequest_WithTrace(t *testing.T) {
|
||||
s := &Server{}
|
||||
|
||||
extra := new(ExtraLogFields)
|
||||
ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra)
|
||||
extra.Add(zap.String("traceID", "1234567890abcdef"))
|
||||
extra.Add(zap.String("spanID", "12345678"))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
|
||||
rec := httptest.NewRecorder()
|
||||
@ -93,7 +94,8 @@ func TestServer_LogRequest_WithTraceID(t *testing.T) {
|
||||
"msg":"handled request", "level":"info", "bytes_read":0,
|
||||
"duration":"50ms", "resp_headers": {}, "size":0,
|
||||
"status":0, "user_id":"",
|
||||
"traceID":"1234567890abcdef"
|
||||
"traceID":"1234567890abcdef",
|
||||
"spanID":"12345678"
|
||||
}`, buf.String())
|
||||
}
|
||||
|
||||
@ -144,12 +146,13 @@ func BenchmarkServer_LogRequest_NopLogger(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkServer_LogRequest_WithTraceID(b *testing.B) {
|
||||
func BenchmarkServer_LogRequest_WithTrace(b *testing.B) {
|
||||
s := &Server{}
|
||||
|
||||
extra := new(ExtraLogFields)
|
||||
ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra)
|
||||
extra.Add(zap.String("traceID", "1234567890abcdef"))
|
||||
extra.Add(zap.String("spanID", "12345678"))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
|
||||
rec := httptest.NewRecorder()
|
||||
|
@ -88,11 +88,15 @@ func (ot *openTelemetryWrapper) serveHTTP(w http.ResponseWriter, r *http.Request
|
||||
spanCtx := trace.SpanContextFromContext(ctx)
|
||||
if spanCtx.IsValid() {
|
||||
traceID := spanCtx.TraceID().String()
|
||||
spanID := spanCtx.SpanID().String()
|
||||
// Add a trace_id placeholder, accessible via `{http.vars.trace_id}`.
|
||||
caddyhttp.SetVar(ctx, "trace_id", traceID)
|
||||
// Add the trace id to the log fields for the request.
|
||||
// Add a span_id placeholder, accessible via `{http.vars.span_id}`.
|
||||
caddyhttp.SetVar(ctx, "span_id", spanID)
|
||||
// Add the traceID and spanID to the log fields for the request.
|
||||
if extra, ok := ctx.Value(caddyhttp.ExtraLogFieldsCtxKey).(*caddyhttp.ExtraLogFields); ok {
|
||||
extra.Add(zap.String("traceID", traceID))
|
||||
extra.Add(zap.String("spanID", spanID))
|
||||
}
|
||||
}
|
||||
next := ctx.Value(nextCallCtxKey).(*nextCall)
|
||||
|
@ -28,6 +28,10 @@ import (
|
||||
|
||||
// trapSignalsPosix captures POSIX-only signals.
|
||||
func trapSignalsPosix() {
|
||||
// Ignore all SIGPIPE signals to prevent weird issues with systemd: https://github.com/dunglas/frankenphp/issues/1020
|
||||
// Docker/Moby has a similar hack: https://github.com/moby/moby/blob/d828b032a87606ae34267e349bf7f7ccb1f6495a/cmd/dockerd/docker.go#L87-L90
|
||||
signal.Ignore(syscall.SIGPIPE)
|
||||
|
||||
go func() {
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
|
||||
|
Loading…
x
Reference in New Issue
Block a user