summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--admin.go33
-rw-r--r--caddy.go74
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go3
-rw-r--r--caddyconfig/httpcaddyfile/directives.go10
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go36
-rw-r--r--caddyconfig/httpcaddyfile/options.go4
-rw-r--r--cmd/commandfuncs.go12
-rw-r--r--context.go257
-rw-r--r--logging.go128
-rw-r--r--modules.go197
-rw-r--r--modules/caddyhttp/caddyauth/basicauth.go11
-rw-r--r--modules/caddyhttp/caddyauth/caddyauth.go21
-rw-r--r--modules/caddyhttp/caddyauth/caddyfile.go4
-rw-r--r--modules/caddyhttp/caddyauth/hashes.go8
-rw-r--r--modules/caddyhttp/caddyhttp.go35
-rw-r--r--modules/caddyhttp/encode/brotli/brotli.go4
-rw-r--r--modules/caddyhttp/encode/caddyfile.go13
-rw-r--r--modules/caddyhttp/encode/encode.go27
-rw-r--r--modules/caddyhttp/encode/gzip/gzip.go4
-rw-r--r--modules/caddyhttp/encode/zstd/zstd.go4
-rw-r--r--modules/caddyhttp/fileserver/caddyfile.go5
-rw-r--r--modules/caddyhttp/fileserver/command.go6
-rw-r--r--modules/caddyhttp/fileserver/matcher.go4
-rw-r--r--modules/caddyhttp/fileserver/staticfiles.go4
-rw-r--r--modules/caddyhttp/headers/headers.go4
-rw-r--r--modules/caddyhttp/httpcache/httpcache.go4
-rw-r--r--modules/caddyhttp/markdown/markdown.go4
-rw-r--r--modules/caddyhttp/matchers.go61
-rw-r--r--modules/caddyhttp/requestbody/requestbody.go4
-rw-r--r--modules/caddyhttp/reverseproxy/caddyfile.go12
-rw-r--r--modules/caddyhttp/reverseproxy/circuitbreaker.go4
-rw-r--r--modules/caddyhttp/reverseproxy/command.go6
-rw-r--r--modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go17
-rw-r--r--modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go4
-rw-r--r--modules/caddyhttp/reverseproxy/httptransport.go5
-rw-r--r--modules/caddyhttp/reverseproxy/ntlm.go8
-rw-r--r--modules/caddyhttp/reverseproxy/reverseproxy.go50
-rw-r--r--modules/caddyhttp/reverseproxy/selectionpolicies.go32
-rw-r--r--modules/caddyhttp/rewrite/rewrite.go4
-rw-r--r--modules/caddyhttp/routes.go143
-rw-r--r--modules/caddyhttp/server.go112
-rw-r--r--modules/caddyhttp/starlarkmw/internal/lib/module.go4
-rw-r--r--modules/caddyhttp/starlarkmw/starlarkmw.go6
-rw-r--r--modules/caddyhttp/staticerror.go4
-rw-r--r--modules/caddyhttp/staticresp.go4
-rw-r--r--modules/caddyhttp/subroute.go4
-rw-r--r--modules/caddyhttp/templates/templates.go4
-rw-r--r--modules/caddyhttp/vars.go8
-rw-r--r--modules/caddytls/acmemanager.go68
-rw-r--r--modules/caddytls/certselection.go4
-rw-r--r--modules/caddytls/connpolicy.go51
-rw-r--r--modules/caddytls/distributedstek/distributedstek.go17
-rw-r--r--modules/caddytls/fileloader.go21
-rw-r--r--modules/caddytls/folderloader.go4
-rw-r--r--modules/caddytls/matchers.go4
-rw-r--r--modules/caddytls/pemloader.go17
-rw-r--r--modules/caddytls/sessiontickets.go24
-rw-r--r--modules/caddytls/standardstek/stek.go4
-rw-r--r--modules/caddytls/tls.go201
-rw-r--r--modules/filestorage/filestorage.go4
-rw-r--r--modules/logging/encoders.go21
-rw-r--r--modules/logging/filewriter.go40
-rw-r--r--modules/logging/filterencoder.go41
-rw-r--r--modules/logging/filters.go11
-rw-r--r--modules_test.go42
65 files changed, 1359 insertions, 627 deletions
diff --git a/admin.go b/admin.go
index 981f2dc..fcbb62e 100644
--- a/admin.go
+++ b/admin.go
@@ -39,12 +39,33 @@ import (
// TODO: is there a way to make the admin endpoint so that it can be plugged into the HTTP app? see issue #2833
-// AdminConfig configures the admin endpoint.
+// AdminConfig configures Caddy's API endpoint, which is used
+// to manage Caddy while it is running.
type AdminConfig struct {
- Disabled bool `json:"disabled,omitempty"`
- Listen string `json:"listen,omitempty"`
- EnforceOrigin bool `json:"enforce_origin,omitempty"`
- Origins []string `json:"origins,omitempty"`
+ // If true, the admin endpoint will be completely disabled.
+ // Note that this makes any runtime changes to the config
+ // impossible, since the interface to do so is through the
+ // admin endpoint.
+ Disabled bool `json:"disabled,omitempty"`
+
+ // The address to which the admin endpoint's listener should
+ // bind itself. Can be any single network address that can be
+ // parsed by Caddy.
+ Listen string `json:"listen,omitempty"`
+
+ // If true, CORS headers will be emitted, and requests to the
+ // API will be rejected if their `Host` and `Origin` headers
+ // do not match the expected value(s). Use `origins` to
+ // customize which origins/hosts are allowed.If `origins` is
+ // not set, the listen address is the only value allowed by
+ // default.
+ EnforceOrigin bool `json:"enforce_origin,omitempty"`
+
+ // The list of allowed origins for API requests. Only used if
+ // `enforce_origin` is true. If not set, the listener address
+ // will be the default value. If set but empty, no origins will
+ // be allowed.
+ Origins []string `json:"origins,omitempty"`
}
// listenAddr extracts a singular listen address from ac.Listen,
@@ -706,7 +727,7 @@ traverseLoop:
ptr = v[partInt]
default:
- return fmt.Errorf("invalid path: %s", parts[:i+1])
+ return fmt.Errorf("invalid traversal path at: %s", strings.Join(parts[:i+1], "/"))
}
}
diff --git a/caddy.go b/caddy.go
index d8609d2..9dcd883 100644
--- a/caddy.go
+++ b/caddy.go
@@ -32,23 +32,44 @@ import (
"github.com/mholt/certmagic"
)
-// Config represents a Caddy configuration. It is the
-// top of the module structure: all Caddy modules will
-// be loaded, starting with this struct. In order to
-// be loaded and run successfully, a Config and all its
-// modules must be JSON-encodable; i.e. when filling a
-// Config struct manually, its JSON-encodable fields
-// (the ones with JSON struct tags, usually ending with
-// "Raw" if they decode into a separate field) must be
-// set so that they can be unmarshaled and provisioned.
-// Setting the fields for the decoded values instead
-// will result in those values being overwritten at
-// unmarshaling/provisioning.
+// Config is the top (or beginning) of the Caddy configuration structure.
+// Caddy config is expressed natively as a JSON document. If you prefer
+// not to work with JSON directly, there are [many config adapters](/docs/config-adapters)
+// available that can convert various inputs into Caddy JSON.
+//
+// Many parts of this config are extensible through the use of Caddy modules.
+// Fields which have a json.RawMessage type and which appear as dots (•••) in
+// the online docs can be fulfilled by modules in a certain module
+// namespace. The docs show which modules can be used in a given place.
+//
+// Whenever a module is used, its name must be given either inline as part of
+// the module, or as the key to the module's value. The docs will make it clear
+// which to use.
+//
+// Generally, all config settings are optional, as it is Caddy convention to
+// have good, documented default values. If a parameter is required, the docs
+// should say so.
+//
+// Go programs which are directly building a Config struct value should take
+// care to populate the JSON-encodable fields of the struct (i.e. the fields
+// with `json` struct tags) if employing the module lifecycle (e.g. Provision
+// method calls).
type Config struct {
- Admin *AdminConfig `json:"admin,omitempty"`
- Logging *Logging `json:"logging,omitempty"`
- StorageRaw json.RawMessage `json:"storage,omitempty"`
- AppsRaw map[string]json.RawMessage `json:"apps,omitempty"`
+ Admin *AdminConfig `json:"admin,omitempty"`
+ Logging *Logging `json:"logging,omitempty"`
+
+ // StorageRaw is a storage module that defines how/where Caddy
+ // stores assets (such as TLS certificates). By default, this is
+ // the local file system (`caddy.storage.file_system` module).
+ // If the `XDG_DATA_HOME` environment variable is set, then
+ // `$XDG_DATA_HOME/caddy` is the default folder. Otherwise,
+ // `$HOME/.local/share/caddy` is the default folder.
+ StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
+
+ // AppsRaw are the apps that Caddy will load and run. The
+ // app module name is the key, and the app's config is the
+ // associated value.
+ AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
apps map[string]App
storage certmagic.Storage
@@ -322,7 +343,7 @@ func run(newCfg *Config, start bool) error {
// set up global storage and make it CertMagic's default storage, too
err = func() error {
if newCfg.StorageRaw != nil {
- val, err := ctx.LoadModuleInline("module", "caddy.storage", newCfg.StorageRaw)
+ val, err := ctx.LoadModule(newCfg, "StorageRaw")
if err != nil {
return fmt.Errorf("loading storage module: %v", err)
}
@@ -331,8 +352,8 @@ func run(newCfg *Config, start bool) error {
return fmt.Errorf("creating storage value: %v", err)
}
newCfg.storage = stor
- newCfg.StorageRaw = nil // allow GC to deallocate
}
+
if newCfg.storage == nil {
newCfg.storage = &certmagic.FileStorage{Path: dataDir()}
}
@@ -346,12 +367,12 @@ func run(newCfg *Config, start bool) error {
// Load, Provision, Validate each app and their submodules
err = func() error {
- for modName, rawMsg := range newCfg.AppsRaw {
- val, err := ctx.LoadModule(modName, rawMsg)
- if err != nil {
- return fmt.Errorf("loading app module '%s': %v", modName, err)
- }
- newCfg.apps[modName] = val.(App)
+ appsIface, err := ctx.LoadModule(newCfg, "AppsRaw")
+ if err != nil {
+ return fmt.Errorf("loading app modules: %v", err)
+ }
+ for appName, appIface := range appsIface.(map[string]interface{}) {
+ newCfg.apps[appName] = appIface.(App)
}
return nil
}()
@@ -447,7 +468,10 @@ func Validate(cfg *Config) error {
return err
}
-// Duration is a JSON-string-unmarshable duration type.
+// Duration can be an integer or a string. An integer is
+// interpreted as nanoseconds. If a string, it is a Go
+// time.Duration value such as `300ms`, `1.5h`, or `2h45m`;
+// valid units are `ns`, `us`/`µs`, `ms`, `s`, `m`, and `h`.
type Duration time.Duration
// UnmarshalJSON satisfies json.Unmarshaler.
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index ec3740c..e92aa9d 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -21,6 +21,7 @@ import (
"net/http"
"reflect"
+ "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
@@ -71,7 +72,7 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
},
}
if matcherSet != nil {
- route.MatcherSetsRaw = []map[string]json.RawMessage{matcherSet}
+ route.MatcherSetsRaw = []caddy.ModuleMap{matcherSet}
}
return h.NewVarsRoute(route), nil
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index acdca20..58aff98 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -88,7 +88,7 @@ type Helper struct {
*caddyfile.Dispenser
options map[string]interface{}
warnings *[]caddyconfig.Warning
- matcherDefs map[string]map[string]json.RawMessage
+ matcherDefs map[string]caddy.ModuleMap
parentBlock caddyfile.ServerBlock
}
@@ -125,7 +125,7 @@ func (h Helper) JSON(val interface{}, warnings *[]caddyconfig.Warning) json.RawM
// if so, returns the matcher set along with a true value. If the current
// token is not a matcher, nil and false is returned. Note that a true
// value may be returned with a nil matcher set if it is a catch-all.
-func (h Helper) MatcherToken() (map[string]json.RawMessage, bool, error) {
+func (h Helper) MatcherToken() (caddy.ModuleMap, bool, error) {
if !h.NextArg() {
return nil, false, nil
}
@@ -133,13 +133,13 @@ func (h Helper) MatcherToken() (map[string]json.RawMessage, bool, error) {
}
// NewRoute returns config values relevant to creating a new HTTP route.
-func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
+func (h Helper) NewRoute(matcherSet caddy.ModuleMap,
handler caddyhttp.MiddlewareHandler) []ConfigValue {
mod, err := caddy.GetModule(caddy.GetModuleName(handler))
if err != nil {
// TODO: append to warnings
}
- var matcherSetsRaw []map[string]json.RawMessage
+ var matcherSetsRaw []caddy.ModuleMap
if matcherSet != nil {
matcherSetsRaw = append(matcherSetsRaw, matcherSet)
}
@@ -148,7 +148,7 @@ func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
Class: "route",
Value: caddyhttp.Route{
MatcherSetsRaw: matcherSetsRaw,
- HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID(), h.warnings)},
+ HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID.Name(), h.warnings)},
},
},
}
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index 6ea32a2..2db3e7c 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -172,7 +172,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
}
// now for the TLS app! (TODO: refactor into own func)
- tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)}
+ tlsApp := caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
for _, p := range pairings {
for i, sblock := range p.serverBlocks {
// tls automation policies
@@ -189,7 +189,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
Hosts: sblockHosts,
- ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &warnings),
+ ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID.Name(), &warnings),
})
} else {
warnings = append(warnings, caddyconfig.Warning{
@@ -204,7 +204,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
for _, clVal := range clVals {
loader := clVal.Value.(caddytls.CertificateLoader)
loaderName := caddy.GetModuleID(loader)
- tlsApp.Certificates[loaderName] = caddyconfig.JSON(loader, &warnings)
+ tlsApp.CertificatesRaw[loaderName] = caddyconfig.JSON(loader, &warnings)
}
}
}
@@ -243,17 +243,17 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
}
// annnd the top-level config, then we're done!
- cfg := &caddy.Config{AppsRaw: make(map[string]json.RawMessage)}
+ cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
if !reflect.DeepEqual(httpApp, caddyhttp.App{}) {
cfg.AppsRaw["http"] = caddyconfig.JSON(httpApp, &warnings)
}
- if !reflect.DeepEqual(tlsApp, caddytls.TLS{Certificates: make(map[string]json.RawMessage)}) {
+ if !reflect.DeepEqual(tlsApp, caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) {
cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings)
}
if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok {
cfg.StorageRaw = caddyconfig.JSONModuleObject(storageCvtr,
"module",
- storageCvtr.(caddy.Module).CaddyModule().ID(),
+ storageCvtr.(caddy.Module).CaddyModule().ID.Name(),
&warnings)
}
if adminConfig, ok := options["admin"].(string); ok && adminConfig != "" {
@@ -337,7 +337,7 @@ func (st *ServerType) serversFromPairings(
}
// TODO: are matchers needed if every hostname of the config is matched?
- cp.Matchers = map[string]json.RawMessage{
+ cp.MatchersRaw = caddy.ModuleMap{
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
}
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
@@ -469,9 +469,9 @@ func consolidateAutomationPolicies(aps []caddytls.AutomationPolicy) []caddytls.A
func matcherSetFromMatcherToken(
tkn caddyfile.Token,
- matcherDefs map[string]map[string]json.RawMessage,
+ matcherDefs map[string]caddy.ModuleMap,
warnings *[]caddyconfig.Warning,
-) (map[string]json.RawMessage, bool, error) {
+) (caddy.ModuleMap, bool, error) {
// matcher tokens can be wildcards, simple path matchers,
// or refer to a pre-defined matcher by some name
if tkn.Text == "*" {
@@ -479,7 +479,7 @@ func matcherSetFromMatcherToken(
return nil, true, nil
} else if strings.HasPrefix(tkn.Text, "/") || strings.HasPrefix(tkn.Text, "=/") {
// convenient way to specify a single path match
- return map[string]json.RawMessage{
+ return caddy.ModuleMap{
"path": caddyconfig.JSON(caddyhttp.MatchPath{tkn.Text}, warnings),
}, true, nil
} else if strings.HasPrefix(tkn.Text, "match:") {
@@ -495,7 +495,7 @@ func matcherSetFromMatcherToken(
return nil, false, nil
}
-func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([]map[string]json.RawMessage, error) {
+func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([]caddy.ModuleMap, error) {
type hostPathPair struct {
hostm caddyhttp.MatchHost
pathm caddyhttp.MatchPath
@@ -562,7 +562,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
}
// finally, encode each of the matcher sets
- var matcherSetsEnc []map[string]json.RawMessage
+ var matcherSetsEnc []caddy.ModuleMap
for _, ms := range matcherSets {
msEncoded, err := encodeMatcherSet(ms)
if err != nil {
@@ -574,8 +574,8 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
return matcherSetsEnc, nil
}
-func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json.RawMessage, error) {
- matchers := make(map[string]map[string]json.RawMessage)
+func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]caddy.ModuleMap, error) {
+ matchers := make(map[string]caddy.ModuleMap)
for d.Next() {
definitionName := d.Val()
for nesting := d.Nesting(); d.NextBlock(nesting); {
@@ -597,7 +597,7 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json
return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
}
if _, ok := matchers[definitionName]; !ok {
- matchers[definitionName] = make(map[string]json.RawMessage)
+ matchers[definitionName] = make(caddy.ModuleMap)
}
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
}
@@ -605,8 +605,8 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json
return matchers, nil
}
-func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (map[string]json.RawMessage, error) {
- msEncoded := make(map[string]json.RawMessage)
+func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.ModuleMap, error) {
+ msEncoded := make(caddy.ModuleMap)
for matcherName, val := range matchers {
jsonBytes, err := json.Marshal(val)
if err != nil {
@@ -628,7 +628,7 @@ func tryInt(val interface{}, warnings *[]caddyconfig.Warning) int {
}
type matcherSetAndTokens struct {
- matcherSet map[string]json.RawMessage
+ matcherSet caddy.ModuleMap
tokens []caddyfile.Token
}
diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go
index 74ec507..e87b30f 100644
--- a/caddyconfig/httpcaddyfile/options.go
+++ b/caddyconfig/httpcaddyfile/options.go
@@ -96,7 +96,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
- return nil, fmt.Errorf("storage module '%s' is not a Caddyfile unmarshaler", mod.Name)
+ return nil, fmt.Errorf("storage module '%s' is not a Caddyfile unmarshaler", mod.ID)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
if err != nil {
@@ -104,7 +104,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
}
storage, ok := unm.(caddy.StorageConverter)
if !ok {
- return nil, fmt.Errorf("module %s is not a StorageConverter", mod.Name)
+ return nil, fmt.Errorf("module %s is not a StorageConverter", mod.ID)
}
return storage, nil
}
diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go
index d0886c5..8526372 100644
--- a/cmd/commandfuncs.go
+++ b/cmd/commandfuncs.go
@@ -321,11 +321,11 @@ func cmdListModules(fl Flags) (int, error) {
return caddy.ExitCodeSuccess, nil
}
- for _, modName := range caddy.Modules() {
- modInfo, err := caddy.GetModule(modName)
+ for _, modID := range caddy.Modules() {
+ modInfo, err := caddy.GetModule(modID)
if err != nil {
// that's weird
- fmt.Println(modName)
+ fmt.Println(modID)
continue
}
@@ -354,13 +354,13 @@ func cmdListModules(fl Flags) (int, error) {
}
// if we could find no matching module, just print out
- // the module name instead
+ // the module ID instead
if matched == nil {
- fmt.Println(modName)
+ fmt.Println(modID)
continue
}
- fmt.Printf("%s %s\n", modName, matched.Version)
+ fmt.Printf("%s %s\n", modID, matched.Version)
}
return caddy.ExitCodeSuccess, nil
diff --git a/context.go b/context.go
index 32368a9..c95b08f 100644
--- a/context.go
+++ b/context.go
@@ -80,24 +80,211 @@ func (ctx *Context) OnCancel(f func()) {
ctx.cleanupFuncs = append(ctx.cleanupFuncs, f)
}
-// LoadModule decodes rawMsg into a new instance of mod and
-// returns the value. If mod.New() does not return a pointer
-// value, it is converted to one so that it is unmarshaled
-// into the underlying concrete type. If mod.New is nil, an
-// error is returned. If the module implements Validator or
-// Provisioner interfaces, those methods are invoked to
-// ensure the module is fully configured and valid before
-// being used.
-func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
- modulesMu.Lock()
- mod, ok := modules[name]
- modulesMu.Unlock()
+// LoadModule loads the Caddy module(s) from the specified field of the parent struct
+// pointer and returns the loaded module(s). The struct pointer and its field name as
+// a string are necessary so that reflection can be used to read the struct tag on the
+// field to get the module namespace and inline module name key (if specified).
+//
+// The field can be any one of the supported raw module types: json.RawMessage,
+// []json.RawMessage, map[string]json.RawMessage, or []map[string]json.RawMessage.
+// ModuleMap may be used in place of map[string]json.RawMessage. The return value's
+// underlying type mirrors the input field's type:
+//
+// json.RawMessage => interface{}
+// []json.RawMessage => []interface{}
+// map[string]json.RawMessage => map[string]interface{}
+// []map[string]json.RawMessage => []map[string]interface{}
+//
+// The field must have a "caddy" struct tag in this format:
+//
+// caddy:"key1=val1 key2=val2"
+//
+// To load modules, a "namespace" key is required. For example, to load modules
+// in the "http.handlers" namespace, you'd put: `namespace=http.handlers` in the
+// Caddy struct tag.
+//
+// The module name must also be available. If the field type is a map or slice of maps,
+// then key is assumed to be the module name if an "inline_key" is NOT specified in the
+// caddy struct tag. In this case, the module name does NOT need to be specified in-line
+// with the module itself.
+//
+// If not a map, or if inline_key is non-empty, then the module name must be embedded
+// into the values, which must be objects; then there must be a key in those objects
+// where its associated value is the module name. This is called the "inline key",
+// meaning the key containing the module's name that is defined inline with the module
+// itself. You must specify the inline key in a struct tag, along with the namespace:
+//
+// caddy:"namespace=http.handlers inline_key=handler"
+//
+// This will look for a key/value pair like `"handler": "..."` in the json.RawMessage
+// in order to know the module name.
+//
+// To make use of the loaded module(s) (the return value), you will probably want
+// to type-assert each interface{} value(s) to the types that are useful to you
+// and store them on the same struct. Storing them on the same struct makes for
+// easy garbage collection when your host module is no longer needed.
+//
+// Loaded modules have already been provisioned and validated.
+func (ctx Context) LoadModule(structPointer interface{}, fieldName string) (interface{}, error) {
+ val := reflect.ValueOf(structPointer).Elem().FieldByName(fieldName)
+ typ := val.Type()
+
+ field, ok := reflect.TypeOf(structPointer).Elem().FieldByName(fieldName)
+ if !ok {
+ panic(fmt.Sprintf("field %s does not exist in %#v", fieldName, structPointer))
+ }
+
+ opts, err := ParseStructTag(field.Tag.Get("caddy"))
+ if err != nil {
+ panic(fmt.Sprintf("malformed tag on field %s: %v", fieldName, err))
+ }
+
+ moduleNamespace, ok := opts["namespace"]
+ if !ok {
+ panic(fmt.Sprintf("missing 'namespace' key in struct tag on field %s", fieldName))
+ }
+ inlineModuleKey := opts["inline_key"]
+
+ var result interface{}
+
+ switch val.Kind() {
+ case reflect.Slice:
+ if isJSONRawMessage(typ) {
+ // val is `json.RawMessage` ([]uint8 under the hood)
+
+ if inlineModuleKey == "" {
+ panic("unable to determine module name without inline_key when type is not a ModuleMap")
+ }
+ val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Interface().(json.RawMessage))
+ if err != nil {
+ return nil, err
+ }
+ result = val
+
+ } else if isJSONRawMessage(typ.Elem()) {
+ // val is `[]json.RawMessage`
+
+ if inlineModuleKey == "" {
+ panic("unable to determine module name without inline_key because type is not a ModuleMap")
+ }
+ var all []interface{}
+ for i := 0; i < val.Len(); i++ {
+ val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Index(i).Interface().(json.RawMessage))
+ if err != nil {
+ return nil, fmt.Errorf("position %d: %v", i, err)
+ }
+ all = append(all, val)
+ }
+ result = all
+
+ } else if isModuleMapType(typ.Elem()) {
+ // val is `[]map[string]json.RawMessage`
+
+ var all []map[string]interface{}
+ for i := 0; i < val.Len(); i++ {
+ thisSet, err := ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val.Index(i))
+ if err != nil {
+ return nil, err
+ }
+ all = append(all, thisSet)
+ }
+ result = all
+ }
+
+ case reflect.Map:
+ // val is a ModuleMap or some other kind of map
+ result, err = ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val)
+ if err != nil {
+ return nil, err
+ }
+
+ default:
+ return nil, fmt.Errorf("unrecognized type for module: %s", typ)
+ }
+
+ // we're done with the raw bytes; allow GC to deallocate
+ val.Set(reflect.Zero(typ))
+
+ return result, nil
+}
+
+// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]interface{}.
+// Depending on inlineModuleKey, it will be interpeted as either a ModuleMap (key is the module
+// name) or as a regular map (key is not the module name, and module name is defined inline).
+func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]interface{}, error) {
+ // if no inline_key is specified, then val must be a ModuleMap,
+ // where the key is the module name
+ if inlineModuleKey == "" {
+ if !isModuleMapType(val.Type()) {
+ panic(fmt.Sprintf("expected ModuleMap because inline_key is empty; but we do not recognize this type: %s", val.Type()))
+ }
+ return ctx.loadModuleMap(namespace, val)
+ }
+
+ // otherwise, val is a map with modules, but the module name is
+ // inline with each value (the key means something else)
+ return ctx.loadModulesFromRegularMap(namespace, inlineModuleKey, val)
+}
+
+// loadModulesFromRegularMap loads modules from val, where val is a map[string]json.RawMessage.
+// Map keys are NOT interpreted as module names, so module names are still expected to appear
+// inline with the objects.
+func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]interface{}, error) {
+ mods := make(map[string]interface{})
+ iter := val.MapRange()
+ for iter.Next() {
+ k := iter.Key()
+ v := iter.Value()
+ mod, err := ctx.loadModuleInline(inlineModuleKey, namespace, v.Interface().(json.RawMessage))
+ if err != nil {
+ return nil, fmt.Errorf("key %s: %v", k, err)
+ }
+ mods[k.String()] = mod
+ }
+ return mods, nil
+}
+
+// loadModuleMap loads modules from a ModuleMap, i.e. map[string]interface{}, where the key is the
+// module name. With a module map, module names do not need to be defined inline with their values.
+func (ctx Context) loadModuleMap(namespace string, val reflect.Value) (map[string]interface{}, error) {
+ all := make(map[string]interface{})
+ iter := val.MapRange()
+ for iter.Next() {
+ k := iter.Key().Interface().(string)
+ v := iter.Value().Interface().(json.RawMessage)
+ moduleName := namespace + "." + k
+ if namespace == "" {
+ moduleName = k
+ }
+ val, err := ctx.LoadModuleByID(moduleName, v)
+ if err != nil {
+ return nil, fmt.Errorf("module name '%s': %v", k, err)
+ }
+ all[k] = val
+ }
+ return all, nil
+}
+
+// LoadModuleByID decodes rawMsg into a new instance of mod and
+// returns the value. If mod.New is nil, an error is returned.
+// If the module implements Validator or Provisioner interfaces,
+// those methods are invoked to ensure the module is fully
+// configured and valid before being used.
+//
+// This is a lower-level method and will usually not be called
+// directly by most modules. However, this method is useful when
+// dynamically loading/unloading modules in their own context,
+// like from embedded scripts, etc.
+func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (interface{}, error) {
+ modulesMu.RLock()
+ mod, ok := modules[id]
+ modulesMu.RUnlock()
if !ok {
- return nil, fmt.Errorf("unknown module: %s", name)
+ return nil, fmt.Errorf("unknown module: %s", id)
}
if mod.New == nil {
- return nil, fmt.Errorf("module '%s' has no constructor", mod.Name)
+ return nil, fmt.Errorf("module '%s' has no constructor", mod.ID)
}
val := mod.New().(interface{})
@@ -108,7 +295,7 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+
" so we are using reflection to make a pointer instead; please fix this by"+
- " using new(Type) or &Type notation in your module's New() function.", name)
+ " using new(Type) or &Type notation in your module's New() function.", id)
val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)
}
@@ -116,7 +303,7 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
if len(rawMsg) > 0 {
err := strictUnmarshalJSON(rawMsg, &val)
if err != nil {
- return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
+ return nil, fmt.Errorf("decoding module config: %s: %v", mod, err)
}
}
@@ -124,8 +311,8 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
// returned module values are almost always type-asserted
// before being used, so a nil value would panic; and there
// is no good reason to explicitly declare null modules in
- // a config; it might be because the user is trying to
- // achieve a result they aren't expecting, which is a smell
+ // a config; it might be because the user is trying to achieve
+ // a result the developer isn't expecting, which is a smell
return nil, fmt.Errorf("module value cannot be null")
}
@@ -140,7 +327,7 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
}
}
- return nil, fmt.Errorf("provision %s: %v", mod.Name, err)
+ return nil, fmt.Errorf("provision %s: %v", mod, err)
}
}
@@ -154,33 +341,33 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
}
}
- return nil, fmt.Errorf("%s: invalid configuration: %v", mod.Name, err)
+ return nil, fmt.Errorf("%s: invalid configuration: %v", mod, err)
}
}
- ctx.moduleInstances[name] = append(ctx.moduleInstances[name], val)
+ ctx.moduleInstances[id] = append(ctx.moduleInstances[id], val)
return val, nil
}
-// LoadModuleInline loads a module from a JSON raw message which decodes
-// to a map[string]interface{}, where one of the keys is moduleNameKey
-// and the corresponding value is the module name as a string, which
-// can be found in the given scope.
-//
-// This allows modules to be decoded into their concrete types and
-// used when their names cannot be the unique key in a map, such as
-// when there are multiple instances in the map or it appears in an
-// array (where there are no custom keys). In other words, the key
-// containing the module name is treated special/separate from all
-// the other keys.
-func (ctx Context) LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
+// loadModuleInline loads a module from a JSON raw message which decodes to
+// a map[string]interface{}, where one of the object keys is moduleNameKey
+// and the corresponding value is the module name (as a string) which can
+// be found in the given scope. In other words, the module name is declared
+// in-line with the module itself.
+//
+// This allows modules to be decoded into their concrete types and used when
+// their names cannot be the unique key in a map, such as when there are
+// multiple instances in the map or it appears in an array (where there are
+// no custom keys). In other words, the key containing the module name is
+// treated special/separate from all the other keys in the object.
+func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
moduleName, raw, err := getModuleNameInline(moduleNameKey, raw)
if err != nil {
return nil, err
}
- val, err := ctx.LoadModule(moduleScope+"."+moduleName, raw)
+ val, err := ctx.LoadModuleByID(moduleScope+"."+moduleName, raw)
if err != nil {
return nil, fmt.Errorf("loading module '%s': %v", moduleName, err)
}
@@ -195,7 +382,7 @@ func (ctx Context) App(name string) (interface{}, error) {
if app, ok := ctx.cfg.apps[name]; ok {
return app, nil
}
- modVal, err := ctx.LoadModule(name, nil)
+ modVal, err := ctx.LoadModuleByID(name, nil)
if err != nil {
return nil, fmt.Errorf("instantiating new module %s: %v", name, err)
}
diff --git a/logging.go b/logging.go
index 97c0513..3fdd644 100644
--- a/logging.go
+++ b/logging.go
@@ -36,8 +36,36 @@ func init() {
}
// Logging facilitates logging within Caddy.
+//
+// By default, all logs at INFO level and higher are written to
+// standard error ("stderr" writer) in a human-readable format
+// ("console" encoder). The default log is called "default" and
+// you can customize it. You can also define additional logs.
+//
+// All defined logs accept all log entries by default, but you
+// can filter by level and module/logger names. A logger's name
+// is the same as the module's name, but a module may append to
+// logger names for more specificity. For example, you can
+// filter logs emitted only by HTTP handlers using the name
+// "http.handlers", because all HTTP handler module names have
+// that prefix.
+//
+// Caddy logs (except the sink) are mostly zero-allocation, so
+// they are very high-performing in terms of memory and CPU time.
+// Enabling sampling can further increase throughput on extremely
+// high-load servers.
type Logging struct {
- Sink *StandardLibLog `json:"sink,omitempty"`
+ // Sink is the destination for all unstructured logs emitted
+ // from Go's standard library logger. These logs are common
+ // in dependencies that are not designed specifically for use
+ // in Caddy. Because it is global and unstructured, the sink
+ // lacks most advanced features and customizations.
+ Sink *StandardLibLog `json:"sink,omitempty"`
+
+ // Logs are your logs, keyed by an arbitrary name of your
+ // choosing. The default log can be customized by defining
+ // a log called "default". You can further define other logs
+ // and filter what kinds of entries they accept.
Logs map[string]*CustomLog `json:"logs,omitempty"`
// a list of all keys for open writers; all writers
@@ -169,12 +197,12 @@ func (logging *Logging) closeLogs() error {
// Logger returns a logger that is ready for the module to use.
func (logging *Logging) Logger(mod Module) *zap.Logger {
- modName := mod.CaddyModule().Name
+ modID := string(mod.CaddyModule().ID)
var cores []zapcore.Core
if logging != nil {
for _, l := range logging.Logs {
- if l.matchesModule(modName) {
+ if l.matchesModule(modID) {
if len(l.Include) == 0 && len(l.Exclude) == 0 {
cores = append(cores, l.core)
continue
@@ -186,7 +214,7 @@ func (logging *Logging) Logger(mod Module) *zap.Logger {
multiCore := zapcore.NewTee(cores...)
- return zap.New(multiCore).Named(modName)
+ return zap.New(multiCore).Named(string(modID))
}
// openWriter opens a writer using opener, and returns true if
@@ -231,26 +259,27 @@ func (wdest writerDestructor) Destruct() error {
// StandardLibLog configures the default Go standard library
// global logger in the log package. This is necessary because
// module dependencies which are not built specifically for
-// Caddy will use the standard logger.
+// Caddy will use the standard logger. This is also known as
+// the "sink" logger.
type StandardLibLog struct {
- WriterRaw json.RawMessage `json:"writer,omitempty"`
+ // The module that writes out log entries for the sink.
+ WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
writer io.WriteCloser
}
func (sll *StandardLibLog) provision(ctx Context, logging *Logging) error {
if sll.WriterRaw != nil {
- val, err := ctx.LoadModuleInline("output", "caddy.logging.writers", sll.WriterRaw)
+ mod, err := ctx.LoadModule(sll, "WriterRaw")
if err != nil {
return fmt.Errorf("loading sink log writer module: %v", err)
}
- wo := val.(WriterOpener)
- sll.WriterRaw = nil // allow GC to deallocate
+ wo := mod.(WriterOpener)
var isNew bool
sll.writer, isNew, err = logging.openWriter(wo)
if err != nil {
- return fmt.Errorf("opening sink log writer %#v: %v", val, err)
+ return fmt.Errorf("opening sink log writer %#v: %v", mod, err)
}
if isNew {
@@ -264,13 +293,40 @@ func (sll *StandardLibLog) provision(ctx Context, logging *Logging) error {
}
// CustomLog represents a custom logger configuration.
+//
+// By default, a log will emit all log entries. Some entries
+// will be skipped if sampling is enabled. Further, the Include
+// and Exclude parameters define which loggers (by name) are
+// allowed or rejected from emitting in this log. If both Include
+// and Exclude are populated, their values must be mutually
+// exclusive, and longer namespaces have priority. If neither
+// are populated, all logs are emitted.
type CustomLog struct {
- WriterRaw json.RawMessage `json:"writer,omitempty"`
- EncoderRaw json.RawMessage `json:"encoder,omitempty"`
- Level string `json:"level,omitempty"`
- Sampling *LogSampling `json:"sampling,omitempty"`
- Include []string `json:"include,omitempty"`
- Exclude []string `json:"exclude,omitempty"`
+ // The writer defines where log entries are emitted.
+ WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
+
+ // The encoder is how the log entries are formatted or encoded.
+ EncoderRaw json.RawMessage `json:"encoder,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
+
+ // Level is the minimum level to emit, and is inclusive.
+ // Possible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL
+ Level string `json:"level,omitempty"`
+
+ // Sampling configures log entry sampling. If enabled,
+ // only some log entries will be emitted. This is useful
+ // for improving performance on extremely high-pressure
+ // servers.
+ Sampling *LogSampling `json:"sampling,omitempty"`
+
+ // Include defines the names of loggers to emit in this
+ // log. For example, to include only logs emitted by the
+ // admin API, you would include "admin.api".
+ Include []string `json:"include,omitempty"`
+
+ // Exclude defines the names of loggers that should be
+ // skipped by this log. For example, to exclude only
+ // HTTP access logs, you would exclude "http.log.access".
+ Exclude []string `json:"exclude,omitempty"`
writerOpener WriterOpener
writer io.WriteCloser
@@ -336,24 +392,22 @@ func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
}
if cl.EncoderRaw != nil {
- val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", cl.EncoderRaw)
+ mod, err := ctx.LoadModule(cl, "EncoderRaw")
if err != nil {
return fmt.Errorf("loading log encoder module: %v", err)
}
- cl.EncoderRaw = nil // allow GC to deallocate
- cl.encoder = val.(zapcore.Encoder)
+ cl.encoder = mod.(zapcore.Encoder)
}
if cl.encoder == nil {
cl.encoder = newDefaultProductionLogEncoder()
}
if cl.WriterRaw != nil {
- val, err := ctx.LoadModuleInline("output", "caddy.logging.writers", cl.WriterRaw)
+ mod, err := ctx.LoadModule(cl, "WriterRaw")
if err != nil {
return fmt.Errorf("loading log writer module: %v", err)
}
- cl.WriterRaw = nil // allow GC to deallocate
- cl.writerOpener = val.(WriterOpener)
+ cl.writerOpener = mod.(WriterOpener)
}
if cl.writerOpener == nil {
cl.writerOpener = StderrWriter{}
@@ -398,8 +452,8 @@ func (cl *CustomLog) buildCore() {
cl.core = c
}
-func (cl *CustomLog) matchesModule(moduleName string) bool {
- return cl.loggerAllowed(moduleName, true)
+func (cl *CustomLog) matchesModule(moduleID string) bool {
+ return cl.loggerAllowed(string(moduleID), true)
}
// loggerAllowed returns true if name is allowed to emit
@@ -493,9 +547,17 @@ func (fc *filteringCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapco
// LogSampling configures log entry sampling.
type LogSampling struct {
- Interval time.Duration `json:"interval,omitempty"`
- First int `json:"first,omitempty"`
- Thereafter int `json:"thereafter,omitempty"`
+ // The window over which to conduct sampling.
+ Interval time.Duration `json:"interval,omitempty"`
+
+ // Log this many entries within a given level and
+ // message for each interval.
+ First int `json:"first,omitempty"`
+
+ // If more entries with the same level and message
+ // are seen during the same interval, keep one in
+ // this many entries until the end of the interval.
+ Thereafter int `json:"thereafter,omitempty"`
}
type (
@@ -512,24 +574,24 @@ type (
// CaddyModule returns the Caddy module information.
func (StdoutWriter) CaddyModule() ModuleInfo {
return ModuleInfo{
- Name: "caddy.logging.writers.stdout",
- New: func() Module { return new(StdoutWriter) },
+ ID: "caddy.logging.writers.stdout",
+ New: func() Module { return new(StdoutWriter) },
}
}
// CaddyModule returns the Caddy module information.
func (StderrWriter) CaddyModule() ModuleInfo {
return ModuleInfo{
- Name: "caddy.logging.writers.stderr",
- New: func() Module { return new(StderrWriter) },
+ ID: "caddy.logging.writers.stderr",
+ New: func() Module { return new(StderrWriter) },
}
}
// CaddyModule returns the Caddy module information.
func (DiscardWriter) CaddyModule() ModuleInfo {
return ModuleInfo{
- Name: "caddy.logging.writers.discard",
- New: func() Module { return new(DiscardWriter) },
+ ID: "caddy.logging.writers.discard",
+ New: func() Module { return new(DiscardWriter) },
}
}
diff --git a/modules.go b/modules.go
index 2d01eb3..b036bea 100644
--- a/modules.go
+++ b/modules.go
@@ -18,58 +18,107 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "reflect"
"sort"
"strings"
"sync"
)
-// Module is a type that is used as a Caddy module.
+// Module is a type that is used as a Caddy module. In
+// addition to this interface, most modules will implement
+// some interface expected by their host module in order
+// to be useful. To learn which interface(s) to implement,
+// see the documentation for the host module. At a bare
+// minimum, this interface, when implemented, only provides
+// the module's ID and constructor function.
+//
+// Modules will often implement additional interfaces
+// including Provisioner, Validator, and CleanerUpper.
+// If a module implements these interfaces, their
+// methods are called during the module's lifespan.
+//
+// When a module is loaded by a host module, the following
+// happens: 1) ModuleInfo.New() is called to get a new
+// instance of the module. 2) The module's configuration is
+// unmarshaled into that instance. 3) If the module is a
+// Provisioner, the Provision() method is called. 4) If the
+// module is a Validator, the Validate() method is called.
+// 5) The module will probably be type-asserted from
+// interface{} to some other, more useful interface expected
+// by the host module. For example, HTTP handler modules are
+// type-asserted as caddyhttp.MiddlewareHandler values.
+// 6) When a module's containing Context is canceled, if it is
+// a CleanerUpper, its Cleanup() method is called.
type Module interface {
- // This method indicates the type is a Caddy
- // module. The returned ModuleInfo must have
- // both a name and a constructor function.
- // This method must not have any side-effects.
+ // This method indicates that the type is a Caddy
+ // module. The returned ModuleInfo must have both
+ // a name and a constructor function. This method
+ // must not have any side-effects.
CaddyModule() ModuleInfo
}
// ModuleInfo represents a registered Caddy module.
type ModuleInfo struct {
- // Name is the full name of the module. It
+ // ID is the "full name" of the module. It
// must be unique and properly namespaced.
- Name string
+ ID ModuleID
// New returns a pointer to a new, empty
- // instance of the module's type. The host
- // module which instantiates this module will
- // likely type-assert and invoke methods on
- // the returned value. This function must not
- // have any side-effects.
+ // instance of the module's type. This
+ // function must not have any side-effects.
New func() Module
}
-// Namespace returns the module's namespace (scope)
-// which is all but the last element of its name.
-// If there is no explicit namespace in the name,
-// the whole name is considered the namespace.
-func (mi ModuleInfo) Namespace() string {
- lastDot := strings.LastIndex(mi.Name, ".")
+// ModuleID is a string that uniquely identifies a Caddy module. A
+// module ID is lightly structured. It consists of dot-separated
+// labels which form a simple hierarchy from left to right. The last
+// label is the module name, and the labels before that constitute
+// the namespace (or scope).
+//
+// Thus, a module ID has the form: <namespace>.<id>
+//
+// An ID with no dot has the empty namespace, which is appropriate
+// for app modules (these are "top-level" modules that Caddy core
+// loads and runs).
+//
+// Module IDs should be lowercase and use underscore (_) instead of
+// spaces.
+//
+// Example valid names:
+// - http
+// - http.handlers.file_server
+// - caddy.logging.encoders.json
+type ModuleID string
+
+// Namespace returns the namespace (or scope) portion of a module ID,
+// which is all but the last label of the ID. If the ID has only one
+// label, then
+func (id ModuleID) Namespace() string {
+ lastDot := strings.LastIndex(string(id), ".")
if lastDot < 0 {
- return mi.Name
+ return string(id)
}
- return mi.Name[:lastDot]
+ return string(id)[:lastDot]
}
-// ID returns a module's ID, which is the
-// last element of its name.
-func (mi ModuleInfo) ID() string {
- if mi.Name == "" {
+// Name returns the Name (last element) of a module name.
+func (id ModuleID) Name() string {
+ if id == "" {
return ""
}
- parts := strings.Split(mi.Name, ".")
+ parts := strings.Split(string(id), ".")
return parts[len(parts)-1]
}
-func (mi ModuleInfo) String() string { return mi.Name }
+func (mi ModuleInfo) String() string { return string(mi.ID) }
+
+// ModuleMap is a map that can contain multiple modules,
+// where the map key is the module's name. (The namespace
+// is usually read from an associated field's struct tag.)
+// Because the module's name is given as the key in a
+// module map, the name does not have to be given in the
+// json.RawMessage.
+type ModuleMap map[string]json.RawMessage
// RegisterModule registers a module by receiving a
// plain/empty value of the module. For registration to
@@ -82,11 +131,11 @@ func (mi ModuleInfo) String() string { return mi.Name }
func RegisterModule(instance Module) error {
mod := instance.CaddyModule()
- if mod.Name == "" {
- return fmt.Errorf("missing ModuleInfo.Name")
+ if mod.ID == "" {
+ return fmt.Errorf("module ID missing")
}
- if mod.Name == "caddy" || mod.Name == "admin" {
- return fmt.Errorf("module name '%s' is reserved", mod.Name)
+ if mod.ID == "caddy" || mod.ID == "admin" {
+ return fmt.Errorf("module ID '%s' is reserved", mod.ID)
}
if mod.New == nil {
return fmt.Errorf("missing ModuleInfo.New")
@@ -98,17 +147,17 @@ func RegisterModule(instance Module) error {
modulesMu.Lock()
defer modulesMu.Unlock()
- if _, ok := modules[mod.Name]; ok {
- return fmt.Errorf("module already registered: %s", mod.Name)
+ if _, ok := modules[string(mod.ID)]; ok {
+ return fmt.Errorf("module already registered: %s", mod.ID)
}
- modules[mod.Name] = mod
+ modules[string(mod.ID)] = mod
return nil
}
-// GetModule returns module information from its full name.
+// GetModule returns module information from its ID (full name).
func GetModule(name string) (ModuleInfo, error) {
- modulesMu.Lock()
- defer modulesMu.Unlock()
+ modulesMu.RLock()
+ defer modulesMu.RUnlock()
m, ok := modules[name]
if !ok {
return ModuleInfo{}, fmt.Errorf("module not registered: %s", name)
@@ -116,25 +165,25 @@ func GetModule(name string) (ModuleInfo, error) {
return m, nil
}
-// GetModuleName returns a module's name from an instance of its value.
-// If the value is not a module, an empty name will be returned.
+// GetModuleName returns a module's name (the last label of its ID)
+// from an instance of its value. If the value is not a module, an
+// empty string will be returned.
func GetModuleName(instance interface{}) string {
var name string
if mod, ok := instance.(Module); ok {
- name = mod.CaddyModule().Name
+ name = mod.CaddyModule().ID.Name()
}
return name
}
-// GetModuleID returns a module's ID (the last element of its name)
-// from an instance of its value. If the value is not a module,
-// an empty string will be returned.
+// GetModuleID returns a module's ID from an instance of its value.
+// If the value is not a module, an empty string will be returned.
func GetModuleID(instance interface{}) string {
- var name string
+ var id string
if mod, ok := instance.(Module); ok {
- name = mod.CaddyModule().ID()
+ id = string(mod.CaddyModule().ID)
}
- return name
+ return id
}
// GetModules returns all modules in the given scope/namespace.
@@ -144,11 +193,11 @@ func GetModuleID(instance interface{}) string {
// scopes are not matched (i.e. scope "foo.ba" does not match
// name "foo.bar").
//
-// Because modules are registered to a map, the returned slice
-// will be sorted to keep it deterministic.
+// Because modules are registered to a map under the hood, the
+// returned slice will be sorted to keep it deterministic.
func GetModules(scope string) []ModuleInfo {
- modulesMu.Lock()
- defer modulesMu.Unlock()
+ modulesMu.RLock()
+ defer modulesMu.RUnlock()
scopeParts := strings.Split(scope, ".")
@@ -160,8 +209,8 @@ func GetModules(scope string) []ModuleInfo {
var mods []ModuleInfo
iterateModules:
- for name, m := range modules {
- modParts := strings.Split(name, ".")
+ for id, m := range modules {
+ modParts := strings.Split(string(id), ".")
// match only the next level of nesting
if len(modParts) != len(scopeParts)+1 {
@@ -180,7 +229,7 @@ iterateModules:
// make return value deterministic
sort.Slice(mods, func(i, j int) bool {
- return mods[i].Name < mods[j].Name
+ return mods[i].ID < mods[j].ID
})
return mods
@@ -189,12 +238,12 @@ iterateModules:
// Modules returns the names of all registered modules
// in ascending lexicographical order.
func Modules() []string {
- modulesMu.Lock()
- defer modulesMu.Unlock()
+ modulesMu.RLock()
+ defer modulesMu.RUnlock()
var names []string
for name := range modules {
- names = append(names, name)
+ names = append(names, string(name))
}
sort.Strings(names)
@@ -261,6 +310,25 @@ type CleanerUpper interface {
Cleanup() error
}
+// ParseStructTag parses a caddy struct tag into its keys and values.
+// It is very simple. The expected syntax is:
+// `caddy:"key1=val1 key2=val2 ..."`
+func ParseStructTag(tag string) (map[string]string, error) {
+ results := make(map[string]string)
+ pairs := strings.Split(tag, " ")
+ for i, pair := range pairs {
+ if pair == "" {
+ continue
+ }
+ parts := strings.SplitN(pair, "=", 2)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("missing key in '%s' (pair %d)", pair, i)
+ }
+ results[parts[0]] = parts[1]
+ }
+ return results, nil
+}
+
// strictUnmarshalJSON is like json.Unmarshal but returns an error
// if any of the fields are unrecognized. Useful when decoding
// module configurations, where you want to be more sure they're
@@ -271,7 +339,24 @@ func strictUnmarshalJSON(data []byte, v interface{}) error {
return dec.Decode(v)
}
+// isJSONRawMessage returns true if the type is encoding/json.RawMessage.
+func isJSONRawMessage(typ reflect.Type) bool {
+ return typ.PkgPath() == "encoding/json" && typ.Name() == "RawMessage"
+}
+
+// isModuleMapType returns true if the type is map[string]json.RawMessage.
+// It assumes that the string key is the module name, but this is not
+// always the case. To know for sure, this function must return true, but
+// also the struct tag where this type appears must NOT define an inline_key
+// attribute, which would mean that the module names appear inline with the
+// values, not in the key.
+func isModuleMapType(typ reflect.Type) bool {
+ return typ.Kind() == reflect.Map &&
+ typ.Key().Kind() == reflect.String &&
+ isJSONRawMessage(typ.Elem())
+}
+
var (
modules = make(map[string]ModuleInfo)
- modulesMu sync.Mutex
+ modulesMu sync.RWMutex
)
diff --git a/modules/caddyhttp/caddyauth/basicauth.go b/modules/caddyhttp/caddyauth/basicauth.go
index 6412d36..8aa44f1 100644
--- a/modules/caddyhttp/caddyauth/basicauth.go
+++ b/modules/caddyhttp/caddyauth/basicauth.go
@@ -28,7 +28,7 @@ func init() {
// HTTPBasicAuth facilitates HTTP basic authentication.
type HTTPBasicAuth struct {
- HashRaw json.RawMessage `json:"hash,omitempty"`
+ HashRaw json.RawMessage `json:"hash,omitempty" caddy:"namespace=http.authentication.hashes inline_key=algorithm"`
AccountList []Account `json:"accounts,omitempty"`
Realm string `json:"realm,omitempty"`
@@ -39,8 +39,8 @@ type HTTPBasicAuth struct {
// CaddyModule returns the Caddy module information.
func (HTTPBasicAuth) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.authentication.providers.http_basic",
- New: func() caddy.Module { return new(HTTPBasicAuth) },
+ ID: "http.authentication.providers.http_basic",
+ New: func() caddy.Module { return new(HTTPBasicAuth) },
}
}
@@ -51,12 +51,11 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
}
// load password hasher
- hashIface, err := ctx.LoadModuleInline("algorithm", "http.handlers.authentication.hashes", hba.HashRaw)
+ hasherIface, err := ctx.LoadModule(hba, "HashRaw")
if err != nil {
return fmt.Errorf("loading password hasher module: %v", err)
}
- hba.Hash = hashIface.(Comparer)
- hba.HashRaw = nil // allow GC to deallocate
+ hba.Hash = hasherIface.(Comparer)
if hba.Hash == nil {
return fmt.Errorf("hash is required")
diff --git a/modules/caddyhttp/caddyauth/caddyauth.go b/modules/caddyhttp/caddyauth/caddyauth.go
index 48d4fba..c79d080 100644
--- a/modules/caddyhttp/caddyauth/caddyauth.go
+++ b/modules/caddyhttp/caddyauth/caddyauth.go
@@ -15,7 +15,6 @@
package caddyauth
import (
- "encoding/json"
"fmt"
"log"
"net/http"
@@ -30,7 +29,7 @@ func init() {
// Authentication is a middleware which provides user authentication.
type Authentication struct {
- ProvidersRaw map[string]json.RawMessage `json:"providers,omitempty"`
+ ProvidersRaw caddy.ModuleMap `json:"providers,omitempty" caddy:"namespace=http.authentication.providers"`
Providers map[string]Authenticator `json:"-"`
}
@@ -38,23 +37,21 @@ type Authentication struct {
// CaddyModule returns the Caddy module information.
func (Authentication) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.authentication",
- New: func() caddy.Module { return new(Authentication) },
+ ID: "http.handlers.authentication",
+ New: func() caddy.Module { return new(Authentication) },
}
}
// Provision sets up a.
func (a *Authentication) Provision(ctx caddy.Context) error {
a.Providers = make(map[string]Authenticator)
- for modName, rawMsg := range a.ProvidersRaw {
- val, err := ctx.LoadModule("http.handlers.authentication.providers."+modName, rawMsg)
- if err != nil {
- return fmt.Errorf("loading authentication provider module '%s': %v", modName, err)
- }
- a.Providers[modName] = val.(Authenticator)
+ mods, err := ctx.LoadModule(a, "ProvidersRaw")
+ if err != nil {
+ return fmt.Errorf("loading authentication providers: %v", err)
+ }
+ for modName, modIface := range mods.(map[string]interface{}) {
+ a.Providers[modName] = modIface.(Authenticator)
}
- a.ProvidersRaw = nil // allow GC to deallocate
-
return nil
}
diff --git a/modules/caddyhttp/caddyauth/caddyfile.go b/modules/caddyhttp/caddyauth/caddyfile.go
index 3600324..8a33e6f 100644
--- a/modules/caddyhttp/caddyauth/caddyfile.go
+++ b/modules/caddyhttp/caddyauth/caddyfile.go
@@ -16,8 +16,8 @@ package caddyauth
import (
"encoding/base64"
- "encoding/json"
+ "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
@@ -97,7 +97,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
}
return Authentication{
- ProvidersRaw: map[string]json.RawMessage{
+ ProvidersRaw: caddy.ModuleMap{
"http_basic": caddyconfig.JSON(ba, nil),
},
}, nil
diff --git a/modules/caddyhttp/caddyauth/hashes.go b/modules/caddyhttp/caddyauth/hashes.go
index 13010db..3ca5116 100644
--- a/modules/caddyhttp/caddyauth/hashes.go
+++ b/modules/caddyhttp/caddyauth/hashes.go
@@ -33,8 +33,8 @@ type BcryptHash struct{}
// CaddyModule returns the Caddy module information.
func (BcryptHash) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.authentication.hashes.bcrypt",
- New: func() caddy.Module { return new(BcryptHash) },
+ ID: "http.authentication.hashes.bcrypt",
+ New: func() caddy.Module { return new(BcryptHash) },
}
}
@@ -61,8 +61,8 @@ type ScryptHash struct {
// CaddyModule returns the Caddy module information.
func (ScryptHash) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.authentication.hashes.scrypt",
- New: func() caddy.Module { return new(ScryptHash) },
+ ID: "http.authentication.hashes.scrypt",
+ New: func() caddy.Module { return new(ScryptHash) },
}
}
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go
index 064a963..756a6c3 100644
--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -44,12 +44,27 @@ func init() {
}
}
-// App is the HTTP app for Caddy.
+// App is a robust, flexible HTTP server for Caddy.
type App struct {
- HTTPPort int `json:"http_port,omitempty"`
- HTTPSPort int `json:"https_port,omitempty"`
- GracePeriod caddy.Duration `json:"grace_period,omitempty"`
- Servers map[string]*Server `json:"servers,omitempty"`
+ // 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 server. Once the grace period is over, connections will
+ // be forcefully closed.
+ GracePeriod caddy.Duration `json:"grace_period,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"`
servers []*http.Server
h3servers []*http3.Server
@@ -62,8 +77,8 @@ type App struct {
// CaddyModule returns the Caddy module information.
func (App) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http",
- New: func() caddy.Module { return new(App) },
+ ID: "http",
+ New: func() caddy.Module { return new(App) },
}
}
@@ -561,8 +576,10 @@ var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error {
// 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;
+//
+// 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.
diff --git a/modules/caddyhttp/encode/brotli/brotli.go b/modules/caddyhttp/encode/brotli/brotli.go
index cf055aa..52bb205 100644
--- a/modules/caddyhttp/encode/brotli/brotli.go
+++ b/modules/caddyhttp/encode/brotli/brotli.go
@@ -37,8 +37,8 @@ type Brotli struct {
// CaddyModule returns the Caddy module information.
func (Brotli) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.encoders.brotli",
- New: func() caddy.Module { return new(Brotli) },
+ ID: "http.encoders.brotli",
+ New: func() caddy.Module { return new(Brotli) },
}
}
diff --git a/modules/caddyhttp/encode/caddyfile.go b/modules/caddyhttp/encode/caddyfile.go
index 4764e9b..dd12de2 100644
--- a/modules/caddyhttp/encode/caddyfile.go
+++ b/modules/caddyhttp/encode/caddyfile.go
@@ -15,7 +15,6 @@
package encode
import (
- "encoding/json"
"fmt"
"github.com/caddyserver/caddy/v2"
@@ -52,14 +51,14 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for _, arg := range d.RemainingArgs() {
mod, err := caddy.GetModule("http.encoders." + arg)
if err != nil {
- return fmt.Errorf("finding encoder module '%s': %v", mod.Name, err)
+ return fmt.Errorf("finding encoder module '%s': %v", mod, err)
}
encoding, ok := mod.New().(Encoding)
if !ok {
- return fmt.Errorf("module %s is not an HTTP encoding", mod.Name)
+ return fmt.Errorf("module %s is not an HTTP encoding", mod)
}
if enc.EncodingsRaw == nil {
- enc.EncodingsRaw = make(map[string]json.RawMessage)
+ enc.EncodingsRaw = make(caddy.ModuleMap)
}
enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
}
@@ -72,7 +71,7 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
- return fmt.Errorf("encoder module '%s' is not a Caddyfile unmarshaler", mod.Name)
+ return fmt.Errorf("encoder module '%s' is not a Caddyfile unmarshaler", mod)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
if err != nil {
@@ -80,10 +79,10 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
encoding, ok := unm.(Encoding)
if !ok {
- return fmt.Errorf("module %s is not an HTTP encoding", mod.Name)
+ return fmt.Errorf("module %s is not an HTTP encoding", mod)
}
if enc.EncodingsRaw == nil {
- enc.EncodingsRaw = make(map[string]json.RawMessage)
+ enc.EncodingsRaw = make(caddy.ModuleMap)
}
enc.EncodingsRaw[name] = caddyconfig.JSON(encoding, nil)
}
diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go
index 3716fc6..c68f507 100644
--- a/modules/caddyhttp/encode/encode.go
+++ b/modules/caddyhttp/encode/encode.go
@@ -21,7 +21,6 @@ package encode
import (
"bytes"
- "encoding/json"
"fmt"
"io"
"net/http"
@@ -40,9 +39,9 @@ func init() {
// Encode is a middleware which can encode responses.
type Encode struct {
- EncodingsRaw map[string]json.RawMessage `json:"encodings,omitempty"`
- Prefer []string `json:"prefer,omitempty"`
- MinLength int `json:"minimum_length,omitempty"`
+ EncodingsRaw caddy.ModuleMap `json:"encodings,omitempty" caddy:"namespace=http.encoders"`
+ Prefer []string `json:"prefer,omitempty"`
+ MinLength int `json:"minimum_length,omitempty"`
writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads...
}
@@ -50,25 +49,23 @@ type Encode struct {
// CaddyModule returns the Caddy module information.
func (Encode) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.encode",
- New: func() caddy.Module { return new(Encode) },
+ ID: "http.handlers.encode",
+ New: func() caddy.Module { return new(Encode) },
}
}
// Provision provisions enc.
func (enc *Encode) Provision(ctx caddy.Context) error {
- for modName, rawMsg := range enc.EncodingsRaw {
- val, err := ctx.LoadModule("http.encoders."+modName, rawMsg)
- if err != nil {
- return fmt.Errorf("loading encoder module '%s': %v", modName, err)
- }
- encoding := val.(Encoding)
- err = enc.addEncoding(encoding)
+ mods, err := ctx.LoadModule(enc, "EncodingsRaw")
+ if err != nil {
+ return fmt.Errorf("loading encoder modules: %v", err)
+ }
+ for modName, modIface := range mods.(map[string]interface{}) {
+ err = enc.addEncoding(modIface.(Encoding))
if err != nil {
- return err
+ return fmt.Errorf("adding encoding %s: %v", modName, err)
}
}
- enc.EncodingsRaw = nil // allow GC to deallocate
if enc.MinLength == 0 {
enc.MinLength = defaultMinLength
diff --git a/modules/caddyhttp/encode/gzip/gzip.go b/modules/caddyhttp/encode/gzip/gzip.go
index d6d67f7..590f708 100644
--- a/modules/caddyhttp/encode/gzip/gzip.go
+++ b/modules/caddyhttp/encode/gzip/gzip.go
@@ -37,8 +37,8 @@ type Gzip struct {
// CaddyModule returns the Caddy module information.
func (Gzip) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.encoders.gzip",
- New: func() caddy.Module { return new(Gzip) },
+ ID: "http.encoders.gzip",
+ New: func() caddy.Module { return new(Gzip) },
}
}
diff --git a/modules/caddyhttp/encode/zstd/zstd.go b/modules/caddyhttp/encode/zstd/zstd.go
index f2b4e85..5182fc4 100644
--- a/modules/caddyhttp/encode/zstd/zstd.go
+++ b/modules/caddyhttp/encode/zstd/zstd.go
@@ -31,8 +31,8 @@ type Zstd struct{}
// CaddyModule returns the Caddy module information.
func (Zstd) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.encoders.zstd",
- New: func() caddy.Module { return new(Zstd) },
+ ID: "http.encoders.zstd",
+ New: func() caddy.Module { return new(Zstd) },
}
}
diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go
index 46bc5b7..fb931a1 100644
--- a/modules/caddyhttp/fileserver/caddyfile.go
+++ b/modules/caddyhttp/fileserver/caddyfile.go
@@ -15,8 +15,7 @@
package fileserver
import (
- "encoding/json"
-
+ "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
@@ -122,7 +121,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
URI: "{http.matchers.file.relative}{http.request.uri.query_string}",
}
- matcherSet := map[string]json.RawMessage{
+ matcherSet := caddy.ModuleMap{
"file": h.JSON(MatchFile{
TryFiles: try,
}, nil),
diff --git a/modules/caddyhttp/fileserver/command.go b/modules/caddyhttp/fileserver/command.go
index b861a99..e7a0ee3 100644
--- a/modules/caddyhttp/fileserver/command.go
+++ b/modules/caddyhttp/fileserver/command.go
@@ -75,8 +75,8 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
},
}
if domain != "" {
- route.MatcherSetsRaw = []map[string]json.RawMessage{
- map[string]json.RawMessage{
+ route.MatcherSetsRaw = []caddy.ModuleMap{
+ caddy.ModuleMap{
"host": caddyconfig.JSON(caddyhttp.MatchHost{domain}, nil),
},
}
@@ -100,7 +100,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
cfg := &caddy.Config{
Admin: &caddy.AdminConfig{Disabled: true},
- AppsRaw: map[string]json.RawMessage{
+ AppsRaw: caddy.ModuleMap{
"http": caddyconfig.JSON(httpApp, nil),
},
}
diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go
index 4a7f657..13cb60a 100644
--- a/modules/caddyhttp/fileserver/matcher.go
+++ b/modules/caddyhttp/fileserver/matcher.go
@@ -54,8 +54,8 @@ type MatchFile struct {
// CaddyModule returns the Caddy module information.
func (MatchFile) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.file",
- New: func() caddy.Module { return new(MatchFile) },
+ ID: "http.matchers.file",
+ New: func() caddy.Module { return new(MatchFile) },
}
}
diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go
index 732894d..a9e6e1c 100644
--- a/modules/caddyhttp/fileserver/staticfiles.go
+++ b/modules/caddyhttp/fileserver/staticfiles.go
@@ -53,8 +53,8 @@ type FileServer struct {
// CaddyModule returns the Caddy module information.
func (FileServer) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.file_server",
- New: func() caddy.Module { return new(FileServer) },
+ ID: "http.handlers.file_server",
+ New: func() caddy.Module { return new(FileServer) },
}
}
diff --git a/modules/caddyhttp/headers/headers.go b/modules/caddyhttp/headers/headers.go
index 813b9fe..f53e859 100644
--- a/modules/caddyhttp/headers/headers.go
+++ b/modules/caddyhttp/headers/headers.go
@@ -37,8 +37,8 @@ type Handler struct {
// CaddyModule returns the Caddy module information.
func (Handler) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.headers",
- New: func() caddy.Module { return new(Handler) },
+ ID: "http.handlers.headers",
+ New: func() caddy.Module { return new(Handler) },
}
}
diff --git a/modules/caddyhttp/httpcache/httpcache.go b/modules/caddyhttp/httpcache/httpcache.go
index b5bc044..81f5816 100644
--- a/modules/caddyhttp/httpcache/httpcache.go
+++ b/modules/caddyhttp/httpcache/httpcache.go
@@ -43,8 +43,8 @@ type Cache struct {
// CaddyModule returns the Caddy module information.
func (Cache) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.cache",
- New: func() caddy.Module { return new(Cache) },
+ ID: "http.handlers.cache",
+ New: func() caddy.Module { return new(Cache) },
}
}
diff --git a/modules/caddyhttp/markdown/markdown.go b/modules/caddyhttp/markdown/markdown.go
index 5ff18b8..acaa0c3 100644
--- a/modules/caddyhttp/markdown/markdown.go
+++ b/modules/caddyhttp/markdown/markdown.go
@@ -38,8 +38,8 @@ type Markdown struct {
// CaddyModule returns the Caddy module information.
func (Markdown) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.markdown",
- New: func() caddy.Module { return new(Markdown) },
+ ID: "http.handlers.markdown",
+ New: func() caddy.Module { return new(Markdown) },
}
}
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index 82fb04a..ea715c5 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -66,7 +66,7 @@ type (
// MatchNegate matches requests by negating its matchers' results.
MatchNegate struct {
- MatchersRaw map[string]json.RawMessage `json:"-"`
+ MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
Matchers MatcherSet `json:"-"`
}
@@ -95,8 +95,8 @@ func init() {
// CaddyModule returns the Caddy module information.
func (MatchHost) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.host",
- New: func() caddy.Module { return new(MatchHost) },
+ ID: "http.matchers.host",
+ New: func() caddy.Module { return new(MatchHost) },
}
}
@@ -149,8 +149,8 @@ outer:
// CaddyModule returns the Caddy module information.
func (MatchPath) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.path",
- New: func() caddy.Module { return new(MatchPath) },
+ ID: "http.matchers.path",
+ New: func() caddy.Module { return new(MatchPath) },
}
}
@@ -208,8 +208,8 @@ func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// CaddyModule returns the Caddy module information.
func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.path_regexp",
- New: func() caddy.Module { return new(MatchPathRE) },
+ ID: "http.matchers.path_regexp",
+ New: func() caddy.Module { return new(MatchPathRE) },
}
}
@@ -222,8 +222,8 @@ func (m MatchPathRE) Match(r *http.Request) bool {
// CaddyModule returns the Caddy module information.
func (MatchMethod) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.method",
- New: func() caddy.Module { return new(MatchMethod) },
+ ID: "http.matchers.method",
+ New: func() caddy.Module { return new(MatchMethod) },
}
}
@@ -248,8 +248,8 @@ func (m MatchMethod) Match(r *http.Request) bool {
// CaddyModule returns the Caddy module information.
func (MatchQuery) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.query",
- New: func() caddy.Module { return new(MatchQuery) },
+ ID: "http.matchers.query",
+ New: func() caddy.Module { return new(MatchQuery) },
}
}
@@ -291,8 +291,8 @@ func (m MatchQuery) Match(r *http.Request) bool {
// CaddyModule returns the Caddy module information.
func (MatchHeader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.header",
- New: func() caddy.Module { return new(MatchHeader) },
+ ID: "http.matchers.header",
+ New: func() caddy.Module { return new(MatchHeader) },
}
}
@@ -349,8 +349,8 @@ func (m MatchHeader) Match(r *http.Request) bool {
// CaddyModule returns the Caddy module information.
func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.header_regexp",
- New: func() caddy.Module { return new(MatchHeaderRE) },
+ ID: "http.matchers.header_regexp",
+ New: func() caddy.Module { return new(MatchHeaderRE) },
}
}
@@ -406,8 +406,8 @@ func (m MatchHeaderRE) Validate() error {
// CaddyModule returns the Caddy module information.
func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.protocol",
- New: func() caddy.Module { return new(MatchProtocol) },
+ ID: "http.matchers.protocol",
+ New: func() caddy.Module { return new(MatchProtocol) },
}
}
@@ -439,8 +439,8 @@ func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// CaddyModule returns the Caddy module information.
func (MatchNegate) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.not",
- New: func() caddy.Module { return new(MatchNegate) },
+ ID: "http.matchers.not",
+ New: func() caddy.Module { return new(MatchNegate) },
}
}
@@ -486,7 +486,7 @@ func (m *MatchNegate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// we should now be functional, but we also need
// to be able to marshal as JSON, otherwise config
// adaptation won't work properly
- m.MatchersRaw = make(map[string]json.RawMessage)
+ m.MatchersRaw = make(caddy.ModuleMap)
for name, matchers := range matcherMap {
jsonBytes, err := json.Marshal(matchers)
if err != nil {
@@ -500,14 +500,13 @@ func (m *MatchNegate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Provision loads the matcher modules to be negated.
func (m *MatchNegate) Provision(ctx caddy.Context) error {
- for modName, rawMsg := range m.MatchersRaw {
- val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
- if err != nil {
- return fmt.Errorf("loading matcher module '%s': %v", modName, err)
- }
- m.Matchers = append(m.Matchers, val.(RequestMatcher))
+ mods, err := ctx.LoadModule(m, "MatchersRaw")
+ if err != nil {
+ return fmt.Errorf("loading matchers: %v", err)
+ }
+ for _, modIface := range mods.(map[string]interface{}) {
+ m.Matchers = append(m.Matchers, modIface.(RequestMatcher))
}
- m.MatchersRaw = nil // allow GC to deallocate
return nil
}
@@ -520,8 +519,8 @@ func (m MatchNegate) Match(r *http.Request) bool {
// CaddyModule returns the Caddy module information.
func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.remote_ip",
- New: func() caddy.Module { return new(MatchRemoteIP) },
+ ID: "http.matchers.remote_ip",
+ New: func() caddy.Module { return new(MatchRemoteIP) },
}
}
@@ -597,8 +596,8 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
// CaddyModule returns the Caddy module information.
func (MatchStarlarkExpr) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'?
- New: func() caddy.Module { return new(MatchStarlarkExpr) },
+ ID: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'?
+ New: func() caddy.Module { return new(MatchStarlarkExpr) },
}
}
diff --git a/modules/caddyhttp/requestbody/requestbody.go b/modules/caddyhttp/requestbody/requestbody.go
index 9b16250..dd3f256 100644
--- a/modules/caddyhttp/requestbody/requestbody.go
+++ b/modules/caddyhttp/requestbody/requestbody.go
@@ -33,8 +33,8 @@ type RequestBody struct {
// CaddyModule returns the Caddy module information.
func (RequestBody) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.request_body", // TODO: better name for this?
- New: func() caddy.Module { return new(RequestBody) },
+ ID: "http.handlers.request_body", // TODO: better name for this?
+ New: func() caddy.Module { return new(RequestBody) },
}
}
diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go
index c8cf26e..9dba769 100644
--- a/modules/caddyhttp/reverseproxy/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/caddyfile.go
@@ -108,11 +108,11 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
name := d.Val()
mod, err := caddy.GetModule("http.handlers.reverse_proxy.selection_policies." + name)
if err != nil {
- return d.Errf("getting load balancing policy module '%s': %v", mod.Name, err)
+ return d.Errf("getting load balancing policy module '%s': %v", mod, err)
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
- return d.Errf("load balancing policy module '%s' is not a Caddyfile unmarshaler", mod.Name)
+ return d.Errf("load balancing policy module '%s' is not a Caddyfile unmarshaler", mod)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
if err != nil {
@@ -120,7 +120,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
sel, ok := unm.(Selector)
if !ok {
- return d.Errf("module %s is not a Selector", mod.Name)
+ return d.Errf("module %s is not a Selector", mod)
}
if h.LoadBalancing == nil {
h.LoadBalancing = new(LoadBalancing)
@@ -391,11 +391,11 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
name := d.Val()
mod, err := caddy.GetModule("http.handlers.reverse_proxy.transport." + name)
if err != nil {
- return d.Errf("getting transport module '%s': %v", mod.Name, err)
+ return d.Errf("getting transport module '%s': %v", mod, err)
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
- return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod.Name)
+ return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
if err != nil {
@@ -403,7 +403,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
rt, ok := unm.(http.RoundTripper)
if !ok {
- return d.Errf("module %s is not a RoundTripper", mod.Name)
+ return d.Errf("module %s is not a RoundTripper", mod)
}
h.TransportRaw = caddyconfig.JSONModuleObject(rt, "protocol", name, nil)
diff --git a/modules/caddyhttp/reverseproxy/circuitbreaker.go b/modules/caddyhttp/reverseproxy/circuitbreaker.go
index de2a6f9..474f1c6 100644
--- a/modules/caddyhttp/reverseproxy/circuitbreaker.go
+++ b/modules/caddyhttp/reverseproxy/circuitbreaker.go
@@ -41,8 +41,8 @@ type localCircuitBreaker struct {
// CaddyModule returns the Caddy module information.
func (localCircuitBreaker) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.circuit_breakers.local",
- New: func() caddy.Module { return new(localCircuitBreaker) },
+ ID: "http.reverse_proxy.circuit_breakers.local",
+ New: func() caddy.Module { return new(localCircuitBreaker) },
}
}
diff --git a/modules/caddyhttp/reverseproxy/command.go b/modules/caddyhttp/reverseproxy/command.go
index 0ddb8f2..e16c4f5 100644
--- a/modules/caddyhttp/reverseproxy/command.go
+++ b/modules/caddyhttp/reverseproxy/command.go
@@ -113,8 +113,8 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
}
urlHost := fromURL.Hostname()
if urlHost != "" {
- route.MatcherSetsRaw = []map[string]json.RawMessage{
- map[string]json.RawMessage{
+ route.MatcherSetsRaw = []caddy.ModuleMap{
+ caddy.ModuleMap{
"host": caddyconfig.JSON(caddyhttp.MatchHost{urlHost}, nil),
},
}
@@ -138,7 +138,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
cfg := &caddy.Config{
Admin: &caddy.AdminConfig{Disabled: true},
- AppsRaw: map[string]json.RawMessage{
+ AppsRaw: caddy.ModuleMap{
"http": caddyconfig.JSON(httpApp, nil),
},
}
diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
index ed97342..8e723b2 100644
--- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
@@ -18,6 +18,7 @@ import (
"encoding/json"
"net/http"
+ "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
@@ -121,12 +122,12 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
}
// route to redirect to canonical path if index PHP file
- redirMatcherSet := map[string]json.RawMessage{
+ redirMatcherSet := caddy.ModuleMap{
"file": h.JSON(fileserver.MatchFile{
TryFiles: []string{"{http.request.uri.path}/index.php"},
}, nil),
"not": h.JSON(caddyhttp.MatchNegate{
- MatchersRaw: map[string]json.RawMessage{
+ MatchersRaw: caddy.ModuleMap{
"path": h.JSON(caddyhttp.MatchPath{"*/"}, nil),
},
}, nil),
@@ -136,12 +137,12 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
Headers: http.Header{"Location": []string{"{http.request.uri.path}/"}},
}
redirRoute := caddyhttp.Route{
- MatcherSetsRaw: []map[string]json.RawMessage{redirMatcherSet},
+ MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
}
// route to rewrite to PHP index file
- rewriteMatcherSet := map[string]json.RawMessage{
+ rewriteMatcherSet := caddy.ModuleMap{
"file": h.JSON(fileserver.MatchFile{
TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php", "index.php"},
}, nil),
@@ -151,13 +152,13 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
Rehandle: true,
}
rewriteRoute := caddyhttp.Route{
- MatcherSetsRaw: []map[string]json.RawMessage{rewriteMatcherSet},
+ MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)},
}
// route to actually reverse proxy requests to PHP files;
// match only requests that are for PHP files
- rpMatcherSet := map[string]json.RawMessage{
+ rpMatcherSet := caddy.ModuleMap{
"path": h.JSON([]string{"*.php"}, nil),
}
@@ -193,7 +194,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// create the final reverse proxy route which is
// conditional on matching PHP files
rpRoute := caddyhttp.Route{
- MatcherSetsRaw: []map[string]json.RawMessage{rpMatcherSet},
+ MatcherSetsRaw: []caddy.ModuleMap{rpMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rpHandler, "handler", "reverse_proxy", nil)},
}
@@ -207,7 +208,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
{
Class: "route",
Value: caddyhttp.Route{
- MatcherSetsRaw: []map[string]json.RawMessage{userMatcherSet},
+ MatcherSetsRaw: []caddy.ModuleMap{userMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)},
},
},
diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
index 21aeb17..aff9a6e 100644
--- a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
+++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
@@ -73,8 +73,8 @@ type Transport struct {
// CaddyModule returns the Caddy module information.
func (Transport) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.transport.fastcgi",
- New: func() caddy.Module { return new(Transport) },
+ ID: "http.reverse_proxy.transport.fastcgi",
+ New: func() caddy.Module { return new(Transport) },
}
}
diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go
index 38a904e..1dd1d14 100644
--- a/modules/caddyhttp/reverseproxy/httptransport.go
+++ b/modules/caddyhttp/reverseproxy/httptransport.go
@@ -40,6 +40,7 @@ type HTTPTransport struct {
// TODO: It's possible that other transports (like fastcgi) might be
// able to borrow/use at least some of these config fields; if so,
// maybe move them into a type called CommonTransport and embed it?
+
TLS *TLSConfig `json:"tls,omitempty"`
KeepAlive *KeepAlive `json:"keep_alive,omitempty"`
Compression *bool `json:"compression,omitempty"`
@@ -59,8 +60,8 @@ type HTTPTransport struct {
// CaddyModule returns the Caddy module information.
func (HTTPTransport) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.transport.http",
- New: func() caddy.Module { return new(HTTPTransport) },
+ ID: "http.reverse_proxy.transport.http",
+ New: func() caddy.Module { return new(HTTPTransport) },
}
}
diff --git a/modules/caddyhttp/reverseproxy/ntlm.go b/modules/caddyhttp/reverseproxy/ntlm.go
index e2d46b4..ea2bb85 100644
--- a/modules/caddyhttp/reverseproxy/ntlm.go
+++ b/modules/caddyhttp/reverseproxy/ntlm.go
@@ -30,12 +30,12 @@ func init() {
caddy.RegisterModule(NTLMTransport{})
}
-// NTLMTransport proxies HTTP+NTLM authentication is being used.
+// NTLMTransport proxies HTTP with NTLM authentication.
// It basically wraps HTTPTransport so that it is compatible with
// NTLM's HTTP-hostile requirements. Specifically, it will use
// HTTPTransport's single, default *http.Transport for all requests
// (unless the client's connection is already mapped to a different
-// transport) until a request comes in with Authorization header
+// transport) until a request comes in with an Authorization header
// that has "NTLM" or "Negotiate"; when that happens, NTLMTransport
// maps the client's connection (by its address, req.RemoteAddr)
// to a new transport that is used only by that downstream conn.
@@ -56,8 +56,8 @@ type NTLMTransport struct {
// CaddyModule returns the Caddy module information.
func (NTLMTransport) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.transport.http_ntlm",
- New: func() caddy.Module { return new(NTLMTransport) },
+ ID: "http.reverse_proxy.transport.http_ntlm",
+ New: func() caddy.Module { return new(NTLMTransport) },
}
}
diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go
index 87895e2..132f222 100644
--- a/modules/caddyhttp/reverseproxy/reverseproxy.go
+++ b/modules/caddyhttp/reverseproxy/reverseproxy.go
@@ -42,8 +42,8 @@ func init() {
// Handler implements a highly configurable and production-ready reverse proxy.
type Handler struct {
- TransportRaw json.RawMessage `json:"transport,omitempty"`
- CBRaw json.RawMessage `json:"circuit_breaker,omitempty"`
+ TransportRaw json.RawMessage `json:"transport,omitempty" caddy:"namespace=http.reverse_proxy.transport inline_key=protocol"`
+ CBRaw json.RawMessage `json:"circuit_breaker,omitempty" caddy:"namespace=http.reverse_proxy.circuit_breakers inline_key=type"`
LoadBalancing *LoadBalancing `json:"load_balancing,omitempty"`
HealthChecks *HealthChecks `json:"health_checks,omitempty"`
Upstreams UpstreamPool `json:"upstreams,omitempty"`
@@ -60,8 +60,8 @@ type Handler struct {
// CaddyModule returns the Caddy module information.
func (Handler) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy",
- New: func() caddy.Module { return new(Handler) },
+ ID: "http.handlers.reverse_proxy",
+ New: func() caddy.Module { return new(Handler) },
}
}
@@ -71,30 +71,25 @@ func (h *Handler) Provision(ctx caddy.Context) error {
// start by loading modules
if h.TransportRaw != nil {
- val, err := ctx.LoadModuleInline("protocol", "http.handlers.reverse_proxy.transport", h.TransportRaw)
+ mod, err := ctx.LoadModule(h, "TransportRaw")
if err != nil {
- return fmt.Errorf("loading transport module: %s", err)
+ return fmt.Errorf("loading transport: %v", err)
}
- h.Transport = val.(http.RoundTripper)
- h.TransportRaw = nil // allow GC to deallocate
+ h.Transport = mod.(http.RoundTripper)
}
if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil {
- val, err := ctx.LoadModuleInline("policy",
- "http.handlers.reverse_proxy.selection_policies",
- h.LoadBalancing.SelectionPolicyRaw)
+ mod, err := ctx.LoadModule(h.LoadBalancing, "SelectionPolicyRaw")
if err != nil {
- return fmt.Errorf("loading load balancing selection module: %s", err)
+ return fmt.Errorf("loading load balancing selection policy: %s", err)
}
- h.LoadBalancing.SelectionPolicy = val.(Selector)
- h.LoadBalancing.SelectionPolicyRaw = nil // allow GC to deallocate
+ h.LoadBalancing.SelectionPolicy = mod.(Selector)
}
if h.CBRaw != nil {
- val, err := ctx.LoadModuleInline("type", "http.handlers.reverse_proxy.circuit_breakers", h.CBRaw)
+ mod, err := ctx.LoadModule(h, "CBRaw")
if err != nil {
- return fmt.Errorf("loading circuit breaker module: %s", err)
+ return fmt.Errorf("loading circuit breaker: %s", err)
}
- h.CB = val.(CircuitBreaker)
- h.CBRaw = nil // allow GC to deallocate
+ h.CB = mod.(CircuitBreaker)
}
// set up transport
@@ -128,12 +123,14 @@ func (h *Handler) Provision(ctx caddy.Context) error {
// defaulting to a sane wait period between attempts
h.LoadBalancing.TryInterval = caddy.Duration(250 * time.Millisecond)
}
- lbMatcherSets, err := h.LoadBalancing.RetryMatchRaw.Setup(ctx)
+ lbMatcherSets, err := ctx.LoadModule(h.LoadBalancing, "RetryMatchRaw")
+ if err != nil {
+ return err
+ }
+ err = h.LoadBalancing.RetryMatch.FromInterface(lbMatcherSets)
if err != nil {
return err
}
- h.LoadBalancing.RetryMatch = lbMatcherSets
- h.LoadBalancing.RetryMatchRaw = nil // allow GC to deallocate
// if active health checks are enabled, configure them and start a worker
if h.HealthChecks != nil &&
@@ -407,7 +404,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
// do the round-trip
start := time.Now()
res, err := h.Transport.RoundTrip(req)
- latency := time.Since(start)
+ duration := time.Since(start)
if err != nil {
return err
}
@@ -415,12 +412,13 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
h.logger.Debug("upstream roundtrip",
zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}),
zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)),
+ zap.Duration("duration", duration),
zap.Int("status", res.StatusCode),
)
// update circuit breaker on current conditions
if di.Upstream.cb != nil {
- di.Upstream.cb.RecordMetric(res.StatusCode, latency)
+ di.Upstream.cb.RecordMetric(res.StatusCode, duration)
}
// perform passive health checks (if enabled)
@@ -434,7 +432,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
// strike if the roundtrip took too long
if h.HealthChecks.Passive.UnhealthyLatency > 0 &&
- latency >= time.Duration(h.HealthChecks.Passive.UnhealthyLatency) {
+ duration >= time.Duration(h.HealthChecks.Passive.UnhealthyLatency) {
h.countFailure(di.Upstream)
}
}
@@ -651,10 +649,10 @@ func removeConnectionHeaders(h http.Header) {
// LoadBalancing has parameters related to load balancing.
type LoadBalancing struct {
- SelectionPolicyRaw json.RawMessage `json:"selection_policy,omitempty"`
+ SelectionPolicyRaw json.RawMessage `json:"selection_policy,omitempty" caddy:"namespace=http.reverse_proxy.selection_policies inline_key=policy"`
TryDuration caddy.Duration `json:"try_duration,omitempty"`
TryInterval caddy.Duration `json:"try_interval,omitempty"`
- RetryMatchRaw caddyhttp.RawMatcherSets `json:"retry_match,omitempty"`
+ RetryMatchRaw caddyhttp.RawMatcherSets `json:"retry_match,omitempty" caddy:"namespace=http.matchers"`
SelectionPolicy Selector `json:"-"`
RetryMatch caddyhttp.MatcherSets `json:"-"`
diff --git a/modules/caddyhttp/reverseproxy/selectionpolicies.go b/modules/caddyhttp/reverseproxy/selectionpolicies.go
index a21e44c..937ae37 100644
--- a/modules/caddyhttp/reverseproxy/selectionpolicies.go
+++ b/modules/caddyhttp/reverseproxy/selectionpolicies.go
@@ -48,8 +48,8 @@ type RandomSelection struct{}
// CaddyModule returns the Caddy module information.
func (RandomSelection) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.selection_policies.random",
- New: func() caddy.Module { return new(RandomSelection) },
+ ID: "http.reverse_proxy.selection_policies.random",
+ New: func() caddy.Module { return new(RandomSelection) },
}
}
@@ -84,8 +84,8 @@ type RandomChoiceSelection struct {
// CaddyModule returns the Caddy module information.
func (RandomChoiceSelection) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.selection_policies.random_choose",
- New: func() caddy.Module { return new(RandomChoiceSelection) },
+ ID: "http.reverse_proxy.selection_policies.random_choose",
+ New: func() caddy.Module { return new(RandomChoiceSelection) },
}
}
@@ -154,8 +154,8 @@ type LeastConnSelection struct{}
// CaddyModule returns the Caddy module information.
func (LeastConnSelection) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.selection_policies.least_conn",
- New: func() caddy.Module { return new(LeastConnSelection) },
+ ID: "http.reverse_proxy.selection_policies.least_conn",
+ New: func() caddy.Module { return new(LeastConnSelection) },
}
}
@@ -199,8 +199,8 @@ type RoundRobinSelection struct {
// CaddyModule returns the Caddy module information.
func (RoundRobinSelection) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.selection_policies.round_robin",
- New: func() caddy.Module { return new(RoundRobinSelection) },
+ ID: "http.reverse_proxy.selection_policies.round_robin",
+ New: func() caddy.Module { return new(RoundRobinSelection) },
}
}
@@ -227,8 +227,8 @@ type FirstSelection struct{}
// CaddyModule returns the Caddy module information.
func (FirstSelection) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.selection_policies.first",
- New: func() caddy.Module { return new(FirstSelection) },
+ ID: "http.reverse_proxy.selection_policies.first",
+ New: func() caddy.Module { return new(FirstSelection) },
}
}
@@ -249,8 +249,8 @@ type IPHashSelection struct{}
// CaddyModule returns the Caddy module information.
func (IPHashSelection) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.selection_policies.ip_hash",
- New: func() caddy.Module { return new(IPHashSelection) },
+ ID: "http.reverse_proxy.selection_policies.ip_hash",
+ New: func() caddy.Module { return new(IPHashSelection) },
}
}
@@ -270,8 +270,8 @@ type URIHashSelection struct{}
// CaddyModule returns the Caddy module information.
func (URIHashSelection) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.selection_policies.uri_hash",
- New: func() caddy.Module { return new(URIHashSelection) },
+ ID: "http.reverse_proxy.selection_policies.uri_hash",
+ New: func() caddy.Module { return new(URIHashSelection) },
}
}
@@ -289,8 +289,8 @@ type HeaderHashSelection struct {
// CaddyModule returns the Caddy module information.
func (HeaderHashSelection) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.reverse_proxy.selection_policies.header",
- New: func() caddy.Module { return new(HeaderHashSelection) },
+ ID: "http.reverse_proxy.selection_policies.header",
+ New: func() caddy.Module { return new(HeaderHashSelection) },
}
}
diff --git a/modules/caddyhttp/rewrite/rewrite.go b/modules/caddyhttp/rewrite/rewrite.go
index 5a84a33..f610658 100644
--- a/modules/caddyhttp/rewrite/rewrite.go
+++ b/modules/caddyhttp/rewrite/rewrite.go
@@ -47,8 +47,8 @@ type Rewrite struct {
// CaddyModule returns the Caddy module information.
func (Rewrite) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.rewrite",
- New: func() caddy.Module { return new(Rewrite) },
+ ID: "http.handlers.rewrite",
+ New: func() caddy.Module { return new(Rewrite) },
}
}
diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go
index 550b14e..0dce990 100644
--- a/modules/caddyhttp/routes.go
+++ b/modules/caddyhttp/routes.go
@@ -22,14 +22,73 @@ import (
"github.com/caddyserver/caddy/v2"
)
-// Route represents a set of matching rules,
-// middlewares, and a responder for handling HTTP
-// requests.
+// 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 string `json:"group,omitempty"`
- MatcherSetsRaw RawMatcherSets `json:"match,omitempty"`
- HandlersRaw []json.RawMessage `json:"handle,omitempty"`
- Terminal bool `json:"terminal,omitempty"`
+ // Group is an optional name for a group to which this
+ // route belongs. If a route belongs to a group, only
+ // the first matching route in the group will be used.
+ 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, even if they matched.
+ Terminal bool `json:"terminal,omitempty"`
// decoded values
MatcherSets MatcherSets `json:"-"`
@@ -54,22 +113,23 @@ type RouteList []Route
func (routes RouteList) Provision(ctx caddy.Context) error {
for i, route := range routes {
// matchers
- matcherSets, err := route.MatcherSetsRaw.Setup(ctx)
+ matchersIface, err := ctx.LoadModule(&route, "MatcherSetsRaw")
+ if err != nil {
+ return fmt.Errorf("loadng matchers in route %d: %v", i, err)
+ }
+ err = routes[i].MatcherSets.FromInterface(matchersIface)
if err != nil {
- return err
+ return fmt.Errorf("route %d: %v", i, err)
}
- routes[i].MatcherSets = matcherSets
- routes[i].MatcherSetsRaw = nil // allow GC to deallocate
// handlers
- for j, rawMsg := range route.HandlersRaw {
- mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
- if err != nil {
- return fmt.Errorf("loading handler module in position %d: %v", j, err)
- }
- routes[i].Handlers = append(routes[i].Handlers, mh.(MiddlewareHandler))
+ handlersIface, err := ctx.LoadModule(&route, "HandlersRaw")
+ if err != nil {
+ return fmt.Errorf("loading handler modules in route %d: %v", i, err)
+ }
+ for _, handler := range handlersIface.([]interface{}) {
+ routes[i].Handlers = append(routes[i].Handlers, handler.(MiddlewareHandler))
}
- routes[i].HandlersRaw = nil // allow GC to deallocate
}
return nil
}
@@ -171,28 +231,7 @@ func (mset MatcherSet) Match(r *http.Request) bool {
// RawMatcherSets is a group of matcher sets
// in their raw, JSON form.
-type RawMatcherSets []map[string]json.RawMessage
-
-// Setup sets up all matcher sets by loading each matcher module
-// and returning the group of provisioned matcher sets.
-func (rm RawMatcherSets) Setup(ctx caddy.Context) (MatcherSets, error) {
- if rm == nil {
- return nil, nil
- }
- var ms MatcherSets
- for _, matcherSet := range rm {
- var matchers MatcherSet
- for modName, rawMsg := range matcherSet {
- val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
- if err != nil {
- return nil, fmt.Errorf("loading matcher module '%s': %v", modName, err)
- }
- matchers = append(matchers, val.(RequestMatcher))
- }
- ms = append(ms, matchers)
- }
- return ms, nil
-}
+type RawMatcherSets []caddy.ModuleMap
// MatcherSets is a group of matcher sets capable
// of checking whether a request matches any of
@@ -202,11 +241,27 @@ type MatcherSets []MatcherSet
// AnyMatch returns true if req matches any of the
// matcher sets in mss or if there are no matchers,
// in which case the request always matches.
-func (mss MatcherSets) AnyMatch(req *http.Request) bool {
- for _, ms := range mss {
- if ms.Match(req) {
+func (ms MatcherSets) AnyMatch(req *http.Request) bool {
+ for _, m := range ms {
+ if m.Match(req) {
return true
}
}
- return len(mss) == 0
+ return len(ms) == 0
+}
+
+// FromInterface fills ms from an interface{} value obtained from LoadModule.
+func (ms *MatcherSets) FromInterface(matcherSets interface{}) error {
+ for _, matcherSetIfaces := range matcherSets.([]map[string]interface{}) {
+ var matcherSet MatcherSet
+ for _, matcher := range matcherSetIfaces {
+ reqMatcher, ok := matcher.(RequestMatcher)
+ if !ok {
+ return fmt.Errorf("decoded module is not a RequestMatcher: %#v", matcher)
+ }
+ matcherSet = append(matcherSet, reqMatcher)
+ }
+ *ms = append(*ms, matcherSet)
+ }
+ return nil
}
diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go
index fef887d..c34444e 100644
--- a/modules/caddyhttp/server.go
+++ b/modules/caddyhttp/server.go
@@ -31,23 +31,101 @@ import (
"go.uber.org/zap/zapcore"
)
-// Server is an HTTP server.
+// Server describes an HTTP server.
type Server struct {
- Listen []string `json:"listen,omitempty"`
- ReadTimeout caddy.Duration `json:"read_timeout,omitempty"`
- ReadHeaderTimeout caddy.Duration `json:"read_header_timeout,omitempty"`
- WriteTimeout caddy.Duration `json:"write_timeout,omitempty"`
- IdleTimeout caddy.Duration `json:"idle_timeout,omitempty"`
- MaxHeaderBytes int `json:"max_header_bytes,omitempty"`
- Routes RouteList `json:"routes,omitempty"`
- Errors *HTTPErrorConfig `json:"errors,omitempty"`
- TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"`
- AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`
- MaxRehandles *int `json:"max_rehandles,omitempty"`
- StrictSNIHost *bool `json:"strict_sni_host,omitempty"`
- Logs *ServerLogConfig `json:"logs,omitempty"`
-
- // This field is not subject to compatibility promises
+ // Socket interfaces to which to bind listeners. Caddy network
+ // addresses have the following form:
+ //
+ // network/address
+ //
+ // The network part is anything that [Go's `net` package](https://golang.org/pkg/net/)
+ // recognizes, and is optional. The default network is `tcp`. If
+ // a network is specified, a single forward slash `/` is used to
+ // separate the network and address portions.
+ //
+ // The address part may be any of these forms:
+ //
+ // - `host`
+ // - `host:port`
+ // - `:port`
+ // - `/path/to/unix/socket`
+ //
+ // The host may be any hostname, resolvable domain name, or IP address.
+ // The port may be a single value (`:8080`) or a range (`:8080-8085`).
+ // A port range will be multiplied into singular addresses. Not all
+ // config parameters accept port ranges, but Listen does.
+ //
+ // Valid examples:
+ //
+ // :8080
+ // 127.0.0.1:8080
+ // localhost:8080
+ // localhost:8080-8085
+ // tcp/localhost:8080
+ // tcp/localhost:8080-8085
+ // udp/localhost:9005
+ // unix//path/to/socket
+ //
+ Listen []string `json:"listen,omitempty"`
+
+ // 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.
+ 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, ReadTimeout is used.
+ // If both are zero, there is no timeout.
+ IdleTimeout caddy.Duration `json:"idle_timeout,omitempty"`
+
+ // MaxHeaderBytes is the maximum size to parse from a client's
+ // HTTP request headers.
+ MaxHeaderBytes int `json:"max_header_bytes,omitempty"`
+
+ // Routes describes how this server will handle requests.
+ // When a request comes in, each route's matchers will
+ // be evaluated against the request, and matching routes
+ // will be compiled into a middleware chain in the order
+ // in which they appear in the list.
+ Routes RouteList `json:"routes,omitempty"`
+
+ // Errors is how this server will handle errors returned from
+ // any of the handlers in the primary routes.
+ Errors *HTTPErrorConfig `json:"errors,omitempty"`
+
+ // How to handle TLS connections.
+ 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.
+ AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`
+
+ // MaxRehandles is the maximum number of times to allow a
+ // request to be rehandled, to prevent accidental infinite
+ // loops. Default: 1.
+ MaxRehandles *int `json:"max_rehandles,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"`
+
+ // Logs customizes how access logs are handled in this server.
+ Logs *ServerLogConfig `json:"logs,omitempty"`
+
+ // Enable experimental HTTP/3 support. Note that HTTP/3 is not a
+ // finished standard and has extremely limited client support.
+ // This field is not subject to compatibility promises.
ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"`
tlsApp *caddytls.TLS
@@ -296,6 +374,8 @@ func (s *Server) hasTLSClientAuth() bool {
// 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.
Disabled bool `json:"disable,omitempty"`
diff --git a/modules/caddyhttp/starlarkmw/internal/lib/module.go b/modules/caddyhttp/starlarkmw/internal/lib/module.go
index a7164cd..a75aedf 100644
--- a/modules/caddyhttp/starlarkmw/internal/lib/module.go
+++ b/modules/caddyhttp/starlarkmw/internal/lib/module.go
@@ -64,7 +64,7 @@ func (r *LoadMiddleware) Run(thread *starlark.Thread, fn *starlark.Builtin, args
name = fmt.Sprintf("http.handlers.%s", name)
}
- inst, err := r.Ctx.LoadModule(name, js)
+ inst, err := r.Ctx.LoadModuleByID(name, js)
if err != nil {
return starlark.None, err
}
@@ -112,7 +112,7 @@ func (r *LoadResponder) Run(thread *starlark.Thread, fn *starlark.Builtin, args
name = fmt.Sprintf("http.handlers.%s", name)
}
- inst, err := r.Ctx.LoadModule(name, js)
+ inst, err := r.Ctx.LoadModuleByID(name, js)
if err != nil {
return starlark.None, err
}
diff --git a/modules/caddyhttp/starlarkmw/starlarkmw.go b/modules/caddyhttp/starlarkmw/starlarkmw.go
index 007ddb4..47e335d 100644
--- a/modules/caddyhttp/starlarkmw/starlarkmw.go
+++ b/modules/caddyhttp/starlarkmw/starlarkmw.go
@@ -7,8 +7,8 @@ import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
- caddyscript "github.com/caddyserver/caddy/v2/pkg/caddyscript/lib"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/starlarkmw/internal/lib"
+ caddyscript "github.com/caddyserver/caddy/v2/pkg/caddyscript/lib"
"github.com/starlight-go/starlight/convert"
"go.starlark.net/starlark"
)
@@ -34,8 +34,8 @@ type StarlarkMW struct {
// CaddyModule returns the Caddy module information.
func (StarlarkMW) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.starlark",
- New: func() caddy.Module { return new(StarlarkMW) },
+ ID: "http.handlers.starlark",
+ New: func() caddy.Module { return new(StarlarkMW) },
}
}
diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go
index 3a45366..fd1490d 100644
--- a/modules/caddyhttp/staticerror.go
+++ b/modules/caddyhttp/staticerror.go
@@ -35,8 +35,8 @@ type StaticError struct {
// CaddyModule returns the Caddy module information.
func (StaticError) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.error",
- New: func() caddy.Module { return new(StaticError) },
+ ID: "http.handlers.error",
+ New: func() caddy.Module { return new(StaticError) },
}
}
diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go
index 732a3fb..44b045e 100644
--- a/modules/caddyhttp/staticresp.go
+++ b/modules/caddyhttp/staticresp.go
@@ -38,8 +38,8 @@ type StaticResponse struct {
// CaddyModule returns the Caddy module information.
func (StaticResponse) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.static_response",
- New: func() caddy.Module { return new(StaticResponse) },
+ ID: "http.handlers.static_response",
+ New: func() caddy.Module { return new(StaticResponse) },
}
}
diff --git a/modules/caddyhttp/subroute.go b/modules/caddyhttp/subroute.go
index 57fb80a..a60eaf7 100644
--- a/modules/caddyhttp/subroute.go
+++ b/modules/caddyhttp/subroute.go
@@ -44,8 +44,8 @@ type Subroute struct {
// CaddyModule returns the Caddy module information.
func (Subroute) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.subroute",
- New: func() caddy.Module { return new(Subroute) },
+ ID: "http.handlers.subroute",
+ New: func() caddy.Module { return new(Subroute) },
}
}
diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go
index e9c1da8..ac37e9d 100644
--- a/modules/caddyhttp/templates/templates.go
+++ b/modules/caddyhttp/templates/templates.go
@@ -39,8 +39,8 @@ type Templates struct {
// CaddyModule returns the Caddy module information.
func (Templates) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.templates",
- New: func() caddy.Module { return new(Templates) },
+ ID: "http.handlers.templates",
+ New: func() caddy.Module { return new(Templates) },
}
}
diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go
index 3fb8fa3..791203b 100644
--- a/modules/caddyhttp/vars.go
+++ b/modules/caddyhttp/vars.go
@@ -32,8 +32,8 @@ type VarsMiddleware map[string]string
// CaddyModule returns the Caddy module information.
func (VarsMiddleware) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.handlers.vars",
- New: func() caddy.Module { return new(VarsMiddleware) },
+ ID: "http.handlers.vars",
+ New: func() caddy.Module { return new(VarsMiddleware) },
}
}
@@ -55,8 +55,8 @@ type VarsMatcher map[string]string
// CaddyModule returns the Caddy module information.
func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "http.matchers.vars",
- New: func() caddy.Module { return new(VarsMatcher) },
+ ID: "http.matchers.vars",
+ New: func() caddy.Module { return new(VarsMatcher) },
}
}
diff --git a/modules/caddytls/acmemanager.go b/modules/caddytls/acmemanager.go
index 9f31215..31c954f 100644
--- a/modules/caddytls/acmemanager.go
+++ b/modules/caddytls/acmemanager.go
@@ -40,16 +40,50 @@ func init() {
// after you have configured this struct
// to your liking.
type ACMEManagerMaker struct {
- CA string `json:"ca,omitempty"`
- Email string `json:"email,omitempty"`
- RenewAhead caddy.Duration `json:"renew_ahead,omitempty"`
- KeyType string `json:"key_type,omitempty"`
- ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
- MustStaple bool `json:"must_staple,omitempty"`
- Challenges *ChallengesConfig `json:"challenges,omitempty"`
- OnDemand bool `json:"on_demand,omitempty"`
- Storage json.RawMessage `json:"storage,omitempty"`
- TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"`
+ // The URL to the CA's ACME directory endpoint.
+ CA string `json:"ca,omitempty"`
+
+ // Your email address, so the CA can contact you if necessary.
+ // Not required, but strongly recommended to provide one so
+ // you can be reached if there is a problem. Your email is
+ // not sent to any Caddy mothership or used for any purpose
+ // other than ACME transactions.
+ Email string `json:"email,omitempty"`
+
+ // How long before a certificate's expiration to try renewing it.
+ // Should usually be about 1/3 of certificate lifetime, but long
+ // enough to give yourself time to troubleshoot problems before
+ // expiration. Default: 30d
+ RenewAhead caddy.Duration `json:"renew_ahead,omitempty"`
+
+ // The type of key to generate for the certificate.
+ // Supported values: `rsa2048`, `rsa4096`, `p256`, `p384`.
+ KeyType string `json:"key_type,omitempty"`
+
+ // Time to wait before timing out an ACME operation.
+ ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
+
+ // If true, certificates will be requested with MustStaple. Not all
+ // CAs support this, and there are potentially serious consequences
+ // of enabling this feature without proper threat modeling.
+ MustStaple bool `json:"must_staple,omitempty"`
+
+ // Configures the various ACME challenge types.
+ Challenges *ChallengesConfig `json:"challenges,omitempty"`
+
+ // If true, certificates will be managed "on demand", that is, during
+ // TLS handshakes or when needed, as opposed to at startup or config
+ // load.
+ OnDemand bool `json:"on_demand,omitempty"`
+
+ // Optionally configure a separate storage module associated with this
+ // manager, instead of using Caddy's global/default-configured storage.
+ Storage json.RawMessage `json:"storage,omitempty"`
+
+ // An array of files of CA certificates to accept when connecting to the
+ // ACME CA. Generally, you should only use this if the ACME CA endpoint
+ // is internal or for development/testing purposes.
+ TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"`
storage certmagic.Storage
rootPool *x509.CertPool
@@ -58,8 +92,8 @@ type ACMEManagerMaker struct {
// CaddyModule returns the Caddy module information.
func (ACMEManagerMaker) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls.management.acme",
- New: func() caddy.Module { return new(ACMEManagerMaker) },
+ ID: "tls.management.acme",
+ New: func() caddy.Module { return new(ACMEManagerMaker) },
}
}
@@ -73,26 +107,24 @@ func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error
func (m *ACMEManagerMaker) Provision(ctx caddy.Context) error {
// DNS providers
if m.Challenges != nil && m.Challenges.DNSRaw != nil {
- val, err := ctx.LoadModuleInline("provider", "tls.dns", m.Challenges.DNSRaw)
+ val, err := ctx.LoadModule(m.Challenges, "DNSRaw")
if err != nil {
- return fmt.Errorf("loading DNS provider module: %s", err)
+ return fmt.Errorf("loading DNS provider module: %v", err)
}
m.Challenges.DNS = val.(challenge.Provider)
- m.Challenges.DNSRaw = nil // allow GC to deallocate
}
// policy-specific storage implementation
if m.Storage != nil {
- val, err := ctx.LoadModuleInline("module", "caddy.storage", m.Storage)
+ val, err := ctx.LoadModule(m, "Storage")
if err != nil {
- return fmt.Errorf("loading TLS storage module: %s", err)
+ return fmt.Errorf("loading TLS storage module: %v", err)
}
cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
if err != nil {
return fmt.Errorf("creating TLS storage configuration: %v", err)
}
m.storage = cmStorage
- m.Storage = nil // allow GC to deallocate
}
// add any custom CAs to trust store
diff --git a/modules/caddytls/certselection.go b/modules/caddytls/certselection.go
index b56185a..eb01605 100644
--- a/modules/caddytls/certselection.go
+++ b/modules/caddytls/certselection.go
@@ -28,8 +28,8 @@ type Policy struct {
// CaddyModule returns the Caddy module information.
func (Policy) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls.certificate_selection.custom",
- New: func() caddy.Module { return new(Policy) },
+ ID: "tls.certificate_selection.custom",
+ New: func() caddy.Module { return new(Policy) },
}
}
diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go
index c82337d..6ce6b9e 100644
--- a/modules/caddytls/connpolicy.go
+++ b/modules/caddytls/connpolicy.go
@@ -39,23 +39,21 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) {
// set up each of the connection policies
for i, pol := range cp {
// matchers
- for modName, rawMsg := range pol.Matchers {
- val, err := ctx.LoadModule("tls.handshake_match."+modName, rawMsg)
- if err != nil {
- return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err)
- }
- cp[i].matchers = append(cp[i].matchers, val.(ConnectionMatcher))
+ mods, err := ctx.LoadModule(pol, "MatchersRaw")
+ if err != nil {
+ return nil, fmt.Errorf("loading handshake matchers: %v", err)
+ }
+ for _, modIface := range mods.(map[string]interface{}) {
+ cp[i].matchers = append(cp[i].matchers, modIface.(ConnectionMatcher))
}
- cp[i].Matchers = nil // allow GC to deallocate
// certificate selector
if pol.CertSelection != nil {
- val, err := ctx.LoadModuleInline("policy", "tls.certificate_selection", pol.CertSelection)
+ val, err := ctx.LoadModule(pol, "CertSelection")
if err != nil {
return nil, fmt.Errorf("loading certificate selection module: %s", err)
}
cp[i].certSelector = val.(certmagic.CertificateSelector)
- cp[i].CertSelection = nil // allow GC to deallocate
}
}
@@ -109,14 +107,33 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) {
// ConnectionPolicy specifies the logic for handling a TLS handshake.
type ConnectionPolicy struct {
- Matchers map[string]json.RawMessage `json:"match,omitempty"`
- CertSelection json.RawMessage `json:"certificate_selection,omitempty"`
-
- CipherSuites []string `json:"cipher_suites,omitempty"`
- Curves []string `json:"curves,omitempty"`
- ALPN []string `json:"alpn,omitempty"`
- ProtocolMin string `json:"protocol_min,omitempty"`
- ProtocolMax string `json:"protocol_max,omitempty"`
+ // How to match this policy with a TLS ClientHello. If
+ // this policy is the first to match, it will be used.
+ MatchersRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=tls.handshake_match"`
+
+ // How to choose a certificate if more than one matched
+ // the given ServerName (SNI) value.
+ CertSelection json.RawMessage `json:"certificate_selection,omitempty" caddy:"namespace=tls.certificate_selection inline_key=policy"`
+
+ // The list of cipher suites to support. Caddy's
+ // defaults are modern and secure.
+ CipherSuites []string `json:"cipher_suites,omitempty"`
+
+ // The list of elliptic curves to support. Caddy's
+ // defaults are modern and secure.
+ Curves []string `json:"curves,omitempty"`
+
+ // Protocols to use for Application-Layer Protocol
+ // Negotiation (ALPN) during the handshake.
+ ALPN []string `json:"alpn,omitempty"`
+
+ // Minimum TLS protocol version to allow. Default: `tls1.2`
+ ProtocolMin string `json:"protocol_min,omitempty"`
+
+ // Maximum TLS protocol version to allow. Default: `tls1.3`
+ ProtocolMax string `json:"protocol_max,omitempty"`
+
+ // Enables and configures TLS client authentication.
ClientAuthentication *ClientAuthentication `json:"client_authentication,omitempty"`
matchers []ConnectionMatcher
diff --git a/modules/caddytls/distributedstek/distributedstek.go b/modules/caddytls/distributedstek/distributedstek.go
index a0c4cd2..cef3733 100644
--- a/modules/caddytls/distributedstek/distributedstek.go
+++ b/modules/caddytls/distributedstek/distributedstek.go
@@ -39,9 +39,15 @@ func init() {
caddy.RegisterModule(Provider{})
}
-// Provider implements a distributed STEK provider.
+// Provider implements a distributed STEK provider. This
+// module will obtain STEKs from a storage module instead
+// of generating STEKs internally. This allows STEKs to be
+// coordinated, improving TLS session resumption in a cluster.
type Provider struct {
- Storage json.RawMessage `json:"storage,omitempty"`
+ // The storage module wherein to store and obtain session
+ // ticket keys. If unset, Caddy's default/global-configured
+ // storage module will be used.
+ Storage json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
storage certmagic.Storage
stekConfig *caddytls.SessionTicketService
@@ -51,8 +57,8 @@ type Provider struct {
// CaddyModule returns the Caddy module information.
func (Provider) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls.stek.distributed",
- New: func() caddy.Module { return new(Provider) },
+ ID: "tls.stek.distributed",
+ New: func() caddy.Module { return new(Provider) },
}
}
@@ -60,7 +66,7 @@ func (Provider) CaddyModule() caddy.ModuleInfo {
func (s *Provider) Provision(ctx caddy.Context) error {
// unpack the storage module to use, if different from the default
if s.Storage != nil {
- val, err := ctx.LoadModuleInline("module", "caddy.storage", s.Storage)
+ val, err := ctx.LoadModule(s, "Storage")
if err != nil {
return fmt.Errorf("loading TLS storage module: %s", err)
}
@@ -69,7 +75,6 @@ func (s *Provider) Provision(ctx caddy.Context) error {
return fmt.Errorf("creating TLS storage configuration: %v", err)
}
s.storage = cmStorage
- s.Storage = nil // allow GC to deallocate
}
// otherwise, use default storage
diff --git a/modules/caddytls/fileloader.go b/modules/caddytls/fileloader.go
index b2cc132..6d6ff99 100644
--- a/modules/caddytls/fileloader.go
+++ b/modules/caddytls/fileloader.go
@@ -32,18 +32,27 @@ type FileLoader []CertKeyFilePair
// CaddyModule returns the Caddy module information.
func (FileLoader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls.certificates.load_files",
- New: func() caddy.Module { return new(FileLoader) },
+ ID: "tls.certificates.load_files",
+ New: func() caddy.Module { return new(FileLoader) },
}
}
// CertKeyFilePair pairs certificate and key file names along with their
// encoding format so that they can be loaded from disk.
type CertKeyFilePair struct {
- Certificate string `json:"certificate"`
- Key string `json:"key"`
- Format string `json:"format,omitempty"` // "pem" is default
- Tags []string `json:"tags,omitempty"`
+ // Path to the certificate (public key) file.
+ Certificate string `json:"certificate"`
+
+ // Path to the private key file.
+ Key string `json:"key"`
+
+ // The format of the cert and key. Can be "pem". Default: "pem"
+ Format string `json:"format,omitempty"`
+
+ // Arbitrary values to associate with this certificate.
+ // Can be useful when you want to select a particular
+ // certificate when there may be multiple valid candidates.
+ Tags []string `json:"tags,omitempty"`
}
// LoadCertificates returns the certificates to be loaded by fl.
diff --git a/modules/caddytls/folderloader.go b/modules/caddytls/folderloader.go
index da1dff0..f1a742d 100644
--- a/modules/caddytls/folderloader.go
+++ b/modules/caddytls/folderloader.go
@@ -39,8 +39,8 @@ type FolderLoader []string
// CaddyModule returns the Caddy module information.
func (FolderLoader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls.certificates.load_folders",
- New: func() caddy.Module { return new(FolderLoader) },
+ ID: "tls.certificates.load_folders",
+ New: func() caddy.Module { return new(FolderLoader) },
}
}
diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go
index 47fb296..9e2dfc5 100644
--- a/modules/caddytls/matchers.go
+++ b/modules/caddytls/matchers.go
@@ -30,8 +30,8 @@ type MatchServerName []string
// CaddyModule returns the Caddy module information.
func (MatchServerName) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls.handshake_match.sni",
- New: func() caddy.Module { return new(MatchServerName) },
+ ID: "tls.handshake_match.sni",
+ New: func() caddy.Module { return new(MatchServerName) },
}
}
diff --git a/modules/caddytls/pemloader.go b/modules/caddytls/pemloader.go
index 30a491c..46d06a8 100644
--- a/modules/caddytls/pemloader.go
+++ b/modules/caddytls/pemloader.go
@@ -33,16 +33,23 @@ type PEMLoader []CertKeyPEMPair
// CaddyModule returns the Caddy module information.
func (PEMLoader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls.certificates.load_pem",
- New: func() caddy.Module { return PEMLoader{} },
+ ID: "tls.certificates.load_pem",
+ New: func() caddy.Module { return PEMLoader{} },
}
}
// CertKeyPEMPair pairs certificate and key PEM blocks.
type CertKeyPEMPair struct {
- CertificatePEM string `json:"certificate"`
- KeyPEM string `json:"key"`
- Tags []string `json:"tags,omitempty"`
+ // The certificate (public key) in PEM format.
+ CertificatePEM string `json:"certificate"`
+
+ // The private key in PEM format.
+ KeyPEM string `json:"key"`
+
+ // Arbitrary values to associate with this certificate.
+ // Can be useful when you want to select a particular
+ // certificate when there may be multiple valid candidates.
+ Tags []string `json:"tags,omitempty"`
}
// LoadCertificates returns the certificates contained in pl.
diff --git a/modules/caddytls/sessiontickets.go b/modules/caddytls/sessiontickets.go
index 6ca921d..258c135 100644
--- a/modules/caddytls/sessiontickets.go
+++ b/modules/caddytls/sessiontickets.go
@@ -28,11 +28,22 @@ import (
// SessionTicketService configures and manages TLS session tickets.
type SessionTicketService struct {
- KeySource json.RawMessage `json:"key_source,omitempty"`
- RotationInterval caddy.Duration `json:"rotation_interval,omitempty"`
- MaxKeys int `json:"max_keys,omitempty"`
- DisableRotation bool `json:"disable_rotation,omitempty"`
- Disabled bool `json:"disabled,omitempty"`
+ // KeySource is the method by which Caddy produces or obtains
+ // TLS session ticket keys (STEKs). By default, Caddy generates
+ // them internally using a secure pseudorandom source.
+ KeySource json.RawMessage `json:"key_source,omitempty" caddy:"namespace=tls.stek inline_key=provider"`
+
+ // How often Caddy rotates STEKs. Default: 12h.
+ RotationInterval caddy.Duration `json:"rotation_interval,omitempty"`
+
+ // The maximum number of keys to keep in rotation. Default: 4.
+ MaxKeys int `json:"max_keys,omitempty"`
+
+ // Disables STEK rotation.
+ DisableRotation bool `json:"disable_rotation,omitempty"`
+
+ // Disables TLS session resumption by tickets.
+ Disabled bool `json:"disabled,omitempty"`
keySource STEKProvider
configs map[*tls.Config]struct{}
@@ -57,12 +68,11 @@ func (s *SessionTicketService) provision(ctx caddy.Context) error {
}
// load the STEK module, which will provide keys
- val, err := ctx.LoadModuleInline("provider", "tls.stek", s.KeySource)
+ val, err := ctx.LoadModule(s, "KeySource")
if err != nil {
return fmt.Errorf("loading TLS session ticket ephemeral keys provider module: %s", err)
}
s.keySource = val.(STEKProvider)
- s.KeySource = nil // allow GC to deallocate
// if session tickets or just rotation are
// disabled, no need to start service
diff --git a/modules/caddytls/standardstek/stek.go b/modules/caddytls/standardstek/stek.go
index 6d10c76..eb609ca 100644
--- a/modules/caddytls/standardstek/stek.go
+++ b/modules/caddytls/standardstek/stek.go
@@ -35,8 +35,8 @@ type standardSTEKProvider struct {
// CaddyModule returns the Caddy module information.
func (standardSTEKProvider) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls.stek.standard",
- New: func() caddy.Module { return new(standardSTEKProvider) },
+ ID: "tls.stek.standard",
+ New: func() caddy.Module { return new(standardSTEKProvider) },
}
}
diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go
index 5dfe063..1b155b0 100644
--- a/modules/caddytls/tls.go
+++ b/modules/caddytls/tls.go
@@ -30,15 +30,30 @@ import (
func init() {
caddy.RegisterModule(TLS{})
+ caddy.RegisterModule(AutomateLoader{})
}
-// TLS represents a process-wide TLS configuration.
+// TLS provides TLS facilities including certificate
+// loading and management, client auth, and more.
type TLS struct {
- Certificates map[string]json.RawMessage `json:"certificates,omitempty"`
- Automation *AutomationConfig `json:"automation,omitempty"`
- SessionTickets *SessionTicketService `json:"session_tickets,omitempty"`
+ // Caches certificates in memory for quick use during
+ // TLS handshakes. Each key is the name of a certificate
+ // loader module. All loaded certificates get pooled
+ // into the same cache and may be used to complete TLS
+ // handshakes for the relevant server names (SNI).
+ // Certificates loaded manually (anything other than
+ // "automate") are not automatically managed and will
+ // have to be refreshed manually before they expire.
+ CertificatesRaw caddy.ModuleMap `json:"certificates,omitempty" caddy:"namespace=tls.certificates"`
+
+ // Configures the automation of certificate management.
+ Automation *AutomationConfig `json:"automation,omitempty"`
+
+ // Configures session ticket ephemeral keys (STEKs).
+ SessionTickets *SessionTicketService `json:"session_tickets,omitempty"`
certificateLoaders []CertificateLoader
+ automateNames []string
certCache *certmagic.Cache
ctx caddy.Context
storageCleanTicker *time.Ticker
@@ -49,8 +64,8 @@ type TLS struct {
// CaddyModule returns the Caddy module information.
func (TLS) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "tls",
- New: func() caddy.Module { return new(TLS) },
+ ID: "tls",
+ New: func() caddy.Module { return new(TLS) },
}
}
@@ -74,25 +89,32 @@ func (t *TLS) Provision(ctx caddy.Context) error {
// automation/management policies
if t.Automation != nil {
for i, ap := range t.Automation.Policies {
- val, err := ctx.LoadModuleInline("module", "tls.management", ap.ManagementRaw)
+ val, err := ctx.LoadModule(&ap, "ManagementRaw")
if err != nil {
return fmt.Errorf("loading TLS automation management module: %s", err)
}
t.Automation.Policies[i].Management = val.(ManagerMaker)
- t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate
}
}
// certificate loaders
- for modName, rawMsg := range t.Certificates {
- if modName == automateKey {
- continue // special case; these will be loaded in later
- }
- val, err := ctx.LoadModule("tls.certificates."+modName, rawMsg)
- if err != nil {
- return fmt.Errorf("loading certificate module '%s': %s", modName, err)
+ val, err := ctx.LoadModule(t, "CertificatesRaw")
+ if err != nil {
+ return fmt.Errorf("loading TLS automation management module: %s", err)
+ }
+ for modName, modIface := range val.(map[string]interface{}) {
+ if modName == "automate" {
+ // special case; these will be loaded in later
+ // using our automation facilities, which we
+ // want to avoid during provisioning
+ var ok bool
+ t.automateNames, ok = modIface.([]string)
+ if !ok {
+ return fmt.Errorf("loading certificates with 'automate' requires []string, got: %#v", modIface)
+ }
+ continue
}
- t.certificateLoaders = append(t.certificateLoaders, val.(CertificateLoader))
+ t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader))
}
// session ticket ephemeral keys (STEK) service and provider
@@ -115,7 +137,8 @@ func (t *TLS) Provision(ctx caddy.Context) error {
// load manual/static (unmanaged) certificates - we do this in
// provision so that other apps (such as http) can know which
- // certificates have been manually loaded
+ // certificates have been manually loaded, and also so that
+ // commands like validate can be a better test
magic := certmagic.New(t.certCache, certmagic.Config{
Storage: ctx.Storage(),
})
@@ -137,19 +160,12 @@ func (t *TLS) Provision(ctx caddy.Context) error {
// Start activates the TLS module.
func (t *TLS) Start() error {
- // load automated (managed) certificates
- if automatedRawMsg, ok := t.Certificates[automateKey]; ok {
- var names []string
- err := json.Unmarshal(automatedRawMsg, &names)
- if err != nil {
- return fmt.Errorf("automate: decoding names: %v", err)
- }
- err = t.Manage(names)
- if err != nil {
- return fmt.Errorf("automate: managing %v: %v", names, err)
- }
+ // now that we are running, and all manual certificates have
+ // been loaded, time to load the automated/managed certificates
+ err := t.Manage(t.automateNames)
+ if err != nil {
+ return fmt.Errorf("automate: managing %v: %v", t.automateNames, err)
}
- t.Certificates = nil // allow GC to deallocate
t.keepStorageClean()
@@ -311,18 +327,48 @@ type Certificate struct {
// AutomationConfig designates configuration for the
// construction and use of ACME clients.
type AutomationConfig struct {
- Policies []AutomationPolicy `json:"policies,omitempty"`
- OnDemand *OnDemandConfig `json:"on_demand,omitempty"`
- OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"`
- RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"`
+ // The list of automation policies. The first matching
+ // policy will be applied for a given certificate/name.
+ Policies []AutomationPolicy `json:"policies,omitempty"`
+
+ // On-Demand TLS defers certificate operations to the
+ // moment they are needed, e.g. during a TLS handshake.
+ // Useful when you don't know all the hostnames up front.
+ // Caddy was the first web server to deploy this technology.
+ OnDemand *OnDemandConfig `json:"on_demand,omitempty"`
+
+ // Caddy staples OCSP (and caches the response) for all
+ // qualifying certificates by default. This setting
+ // changes how often it scans responses for freshness,
+ // and updates them if they are getting stale.
+ OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"`
+
+ // Every so often, Caddy will scan all loaded, managed
+ // certificates for expiration. Certificates which are
+ // about 2/3 into their valid lifetime are due for
+ // renewal. This setting changes how frequently the scan
+ // is performed. If your certificate lifetimes are very
+ // short (less than ~1 week), you should customize this.
+ RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"`
}
// AutomationPolicy designates the policy for automating the
-// management of managed TLS certificates.
+// management (obtaining, renewal, and revocation) of managed
+// TLS certificates.
type AutomationPolicy struct {
- Hosts []string `json:"hosts,omitempty"`
- ManagementRaw json.RawMessage `json:"management,omitempty"`
- ManageSync bool `json:"manage_sync,omitempty"`
+ // Which hostnames this policy applies to.
+ Hosts []string `json:"hosts,omitempty"`
+
+ // How to manage certificates.
+ ManagementRaw json.RawMessage `json:"management,omitempty" caddy:"namespace=tls.management inline_key=module"`
+
+ // If true, certificate management will be conducted
+ // in the foreground; this will block config reloads
+ // and return errors if there were problems with
+ // obtaining or renewing certificates. This is often
+ // not desirable, especially when serving sites out
+ // of your control. Default: false
+ ManageSync bool `json:"manage_sync,omitempty"`
Management ManagerMaker `json:"-"`
}
@@ -345,36 +391,84 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf
// ChallengesConfig configures the ACME challenges.
type ChallengesConfig struct {
- HTTP *HTTPChallengeConfig `json:"http,omitempty"`
+ // HTTP configures the ACME HTTP challenge. This
+ // challenge is enabled and used automatically
+ // and by default.
+ HTTP *HTTPChallengeConfig `json:"http,omitempty"`
+
+ // TLSALPN configures the ACME TLS-ALPN challenge.
+ // This challenge is enabled and used automatically
+ // and by default.
TLSALPN *TLSALPNChallengeConfig `json:"tls-alpn,omitempty"`
- DNSRaw json.RawMessage `json:"dns,omitempty"`
+
+ // Configures the ACME DNS challenge. Because this
+ // challenge typically requires credentials for
+ // interfacing with a DNS provider, this challenge is
+ // not enabled by default. This is the only challenge
+ // type which does not require a direct connection
+ // to Caddy from an external server.
+ DNSRaw json.RawMessage `json:"dns,omitempty" caddy:"namespace=tls.dns inline_key=provider"`
DNS challenge.Provider `json:"-"`
}
// HTTPChallengeConfig configures the ACME HTTP challenge.
type HTTPChallengeConfig struct {
- Disabled bool `json:"disabled,omitempty"`
- AlternatePort int `json:"alternate_port,omitempty"`
+ // If true, the HTTP challenge will be disabled.
+ Disabled bool `json:"disabled,omitempty"`
+
+ // An alternate port on which to service this
+ // challenge. Note that the HTTP challenge port is
+ // hard-coded into the spec and cannot be changed,
+ // so you would have to forward packets from the
+ // standard HTTP challenge port to this one.
+ AlternatePort int `json:"alternate_port,omitempty"`
}
// TLSALPNChallengeConfig configures the ACME TLS-ALPN challenge.
type TLSALPNChallengeConfig struct {
- Disabled bool `json:"disabled,omitempty"`
- AlternatePort int `json:"alternate_port,omitempty"`
+ // If true, the TLS-ALPN challenge will be disabled.
+ Disabled bool `json:"disabled,omitempty"`
+
+ // An alternate port on which to service this
+ // challenge. Note that the TLS-ALPN challenge port
+ // is hard-coded into the spec and cannot be changed,
+ // so you would have to forward packets from the
+ // standard TLS-ALPN challenge port to this one.
+ AlternatePort int `json:"alternate_port,omitempty"`
}
// OnDemandConfig configures on-demand TLS, for obtaining
-// needed certificates at handshake-time.
+// needed certificates at handshake-time. Because this
+// feature can easily be abused, you should set up rate
+// limits and/or an internal endpoint that Caddy can
+// "ask" if it should be allowed to manage certificates
+// for a given hostname.
type OnDemandConfig struct {
+ // An optional rate limit to throttle the
+ // issuance of certificates from handshakes.
RateLimit *RateLimit `json:"rate_limit,omitempty"`
- Ask string `json:"ask,omitempty"`
+
+ // If Caddy needs to obtain or renew a certificate
+ // during a TLS handshake, it will perform a quick
+ // HTTP request to this URL to check if it should be
+ // allowed to try to get a certificate for the name
+ // in the "domain" query string parameter, like so:
+ // `?domain=example.com`. The endpoint must return a
+ // 200 OK status if a certificate is allowed;
+ // anything else will cause it to be denied.
+ // Redirects are not followed.
+ Ask string `json:"ask,omitempty"`
}
// RateLimit specifies an interval with optional burst size.
type RateLimit struct {
+ // A duration value. A certificate may be obtained 'burst'
+ // times during this interval.
Interval caddy.Duration `json:"interval,omitempty"`
- Burst int `json:"burst,omitempty"`
+
+ // How many times during an interval a certificate can be obtained.
+ Burst int `json:"burst,omitempty"`
}
// ManagerMaker makes a certificate manager.
@@ -382,6 +476,21 @@ type ManagerMaker interface {
NewManager(interactive bool) (certmagic.Manager, error)
}
+// AutomateLoader is a no-op certificate loader module
+// that is treated as a special case: it uses this app's
+// automation features to load certificates for the
+// list of hostnames, rather than loading certificates
+// manually.
+type AutomateLoader []string
+
+// CaddyModule returns the Caddy module information.
+func (AutomateLoader) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "tls.certificates.automate",
+ New: func() caddy.Module { return new(AutomateLoader) },
+ }
+}
+
// These perpetual values are used for on-demand TLS.
var (
onDemandRateLimiter = certmagic.NewRateLimiter(0, 0)
diff --git a/modules/filestorage/filestorage.go b/modules/filestorage/filestorage.go
index 6217a07..55607ba 100644
--- a/modules/filestorage/filestorage.go
+++ b/modules/filestorage/filestorage.go
@@ -32,8 +32,8 @@ type FileStorage struct {
// CaddyModule returns the Caddy module information.
func (FileStorage) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.storage.file_system",
- New: func() caddy.Module { return new(FileStorage) },
+ ID: "caddy.storage.file_system",
+ New: func() caddy.Module { return new(FileStorage) },
}
}
diff --git a/modules/logging/encoders.go b/modules/logging/encoders.go
index c3c8aba..28cda55 100644
--- a/modules/logging/encoders.go
+++ b/modules/logging/encoders.go
@@ -43,8 +43,8 @@ type ConsoleEncoder struct {
// CaddyModule returns the Caddy module information.
func (ConsoleEncoder) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.logging.encoders.console",
- New: func() caddy.Module { return new(ConsoleEncoder) },
+ ID: "caddy.logging.encoders.console",
+ New: func() caddy.Module { return new(ConsoleEncoder) },
}
}
@@ -63,8 +63,8 @@ type JSONEncoder struct {
// CaddyModule returns the Caddy module information.
func (JSONEncoder) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.logging.encoders.json",
- New: func() caddy.Module { return new(JSONEncoder) },
+ ID: "caddy.logging.encoders.json",
+ New: func() caddy.Module { return new(JSONEncoder) },
}
}
@@ -84,8 +84,8 @@ type LogfmtEncoder struct {
// CaddyModule returns the Caddy module information.
func (LogfmtEncoder) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.logging.encoders.logfmt",
- New: func() caddy.Module { return new(LogfmtEncoder) },
+ ID: "caddy.logging.encoders.logfmt",
+ New: func() caddy.Module { return new(LogfmtEncoder) },
}
}
@@ -102,25 +102,24 @@ func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error {
type StringEncoder struct {
zapcore.Encoder
FieldName string `json:"field,omitempty"`
- FallbackRaw json.RawMessage `json:"fallback,omitempty"`
+ FallbackRaw json.RawMessage `json:"fallback,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
}
// CaddyModule returns the Caddy module information.
func (StringEncoder) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.logging.encoders.string",
- New: func() caddy.Module { return new(StringEncoder) },
+ ID: "caddy.logging.encoders.string",
+ New: func() caddy.Module { return new(StringEncoder) },
}
}
// Provision sets up the encoder.
func (se *StringEncoder) Provision(ctx caddy.Context) error {
if se.FallbackRaw != nil {
- val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", se.FallbackRaw)
+ val, err := ctx.LoadModule(se, "FallbackRaw")
if err != nil {
return fmt.Errorf("loading fallback encoder module: %v", err)
}
- se.FallbackRaw = nil // allow GC to deallocate
se.Encoder = val.(zapcore.Encoder)
}
if se.Encoder == nil {
diff --git a/modules/logging/filewriter.go b/modules/logging/filewriter.go
index 6957b6a..cc60c64 100644
--- a/modules/logging/filewriter.go
+++ b/modules/logging/filewriter.go
@@ -28,22 +28,42 @@ func init() {
caddy.RegisterModule(FileWriter{})
}
-// FileWriter can write logs to files.
+// FileWriter can write logs to files. By default, log files
+// are rotated ("rolled") when they get large, and old log
+// files get deleted, to ensure that the process does not
+// exhaust disk space.
type FileWriter struct {
- Filename string `json:"filename,omitempty"`
- Roll *bool `json:"roll,omitempty"`
- RollSizeMB int `json:"roll_size_mb,omitempty"`
- RollCompress *bool `json:"roll_gzip,omitempty"`
- RollLocalTime bool `json:"roll_local_time,omitempty"`
- RollKeep int `json:"roll_keep,omitempty"`
- RollKeepDays int `json:"roll_keep_days,omitempty"`
+ // Filename is the name of the file to write.
+ Filename string `json:"filename,omitempty"`
+
+ // Roll toggles log rolling or rotation, which is
+ // enabled by default.
+ Roll *bool `json:"roll,omitempty"`
+
+ // When a log file reaches approximately this size,
+ // it will be rotated.
+ RollSizeMB int `json:"roll_size_mb,omitempty"`
+
+ // Whether to compress rolled files. Default: true
+ RollCompress *bool `json:"roll_gzip,omitempty"`
+
+ // Whether to use local timestamps in rolled filenames.
+ // Default: false
+ RollLocalTime bool `json:"roll_local_time,omitempty"`
+
+ // The maximum number of rolled log files to keep.
+ // Default: 10
+ RollKeep int `json:"roll_keep,omitempty"`
+
+ // How many days to keep rolled log files. Default: 90
+ RollKeepDays int `json:"roll_keep_days,omitempty"`
}
// CaddyModule returns the Caddy module information.
func (FileWriter) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.logging.writers.file",
- New: func() caddy.Module { return new(FileWriter) },
+ ID: "caddy.logging.writers.file",
+ New: func() caddy.Module { return new(FileWriter) },
}
}
diff --git a/modules/logging/filterencoder.go b/modules/logging/filterencoder.go
index eff0279..6680019 100644
--- a/modules/logging/filterencoder.go
+++ b/modules/logging/filterencoder.go
@@ -29,13 +29,17 @@ func init() {
caddy.RegisterModule(FilterEncoder{})
}
-// FilterEncoder wraps an underlying encoder. It does
-// not do any encoding itself, but it can manipulate
-// (filter) fields before they are actually encoded.
-// A wrapped encoder is required.
+// FilterEncoder can filter (manipulate) fields on
+// log entries before they are actually encoded by
+// an underlying encoder.
type FilterEncoder struct {
- WrappedRaw json.RawMessage `json:"wrap,omitempty"`
- FieldsRaw map[string]json.RawMessage `json:"fields,omitempty"`
+ // The underlying encoder that actually
+ // encodes the log entries. Required.
+ WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
+
+ // A map of field names to their filters. Note that this
+ // is not a module map; the keys are field names.
+ FieldsRaw map[string]json.RawMessage `json:"fields,omitempty" caddy:"namespace=caddy.logging.encoders.filter inline_key=filter"`
wrapped zapcore.Encoder
Fields map[string]LogFieldFilter `json:"-"`
@@ -47,8 +51,8 @@ type FilterEncoder struct {
// CaddyModule returns the Caddy module information.
func (FilterEncoder) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.logging.encoders.filter",
- New: func() caddy.Module { return new(FilterEncoder) },
+ ID: "caddy.logging.encoders.filter",
+ New: func() caddy.Module { return new(FilterEncoder) },
}
}
@@ -59,28 +63,23 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
}
// set up wrapped encoder (required)
- val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", fe.WrappedRaw)
+ val, err := ctx.LoadModule(fe, "WrappedRaw")
if err != nil {
return fmt.Errorf("loading fallback encoder module: %v", err)
}
- fe.WrappedRaw = nil // allow GC to deallocate
fe.wrapped = val.(zapcore.Encoder)
// set up each field filter
if fe.Fields == nil {
fe.Fields = make(map[string]LogFieldFilter)
}
- for field, filterRaw := range fe.FieldsRaw {
- if filterRaw == nil {
- continue
- }
- val, err := ctx.LoadModuleInline("filter", "caddy.logging.encoders.filter", filterRaw)
- if err != nil {
- return fmt.Errorf("loading log filter module: %v", err)
- }
- fe.Fields[field] = val.(LogFieldFilter)
- }
- fe.FieldsRaw = nil // allow GC to deallocate
+ vals, err := ctx.LoadModule(fe, "FieldsRaw")
+ if err != nil {
+ return fmt.Errorf("loading log filter modules: %v", err)
+ }
+ for fieldName, modIface := range vals.(map[string]interface{}) {
+ fe.Fields[fieldName] = modIface.(LogFieldFilter)
+ }
return nil
}
diff --git a/modules/logging/filters.go b/modules/logging/filters.go
index b44e084..52db2fe 100644
--- a/modules/logging/filters.go
+++ b/modules/logging/filters.go
@@ -41,8 +41,8 @@ type DeleteFilter struct{}
// CaddyModule returns the Caddy module information.
func (DeleteFilter) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.logging.encoders.filter.delete",
- New: func() caddy.Module { return new(DeleteFilter) },
+ ID: "caddy.logging.encoders.filter.delete",
+ New: func() caddy.Module { return new(DeleteFilter) },
}
}
@@ -55,15 +55,18 @@ func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
// IPMaskFilter is a Caddy log field filter that
// masks IP addresses.
type IPMaskFilter struct {
+ // The IPv4 range in CIDR notation.
IPv4CIDR int `json:"ipv4_cidr,omitempty"`
+
+ // The IPv6 range in CIDR notation.
IPv6CIDR int `json:"ipv6_cidr,omitempty"`
}
// CaddyModule returns the Caddy module information.
func (IPMaskFilter) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- Name: "caddy.logging.encoders.filter.ip_mask",
- New: func() caddy.Module { return new(IPMaskFilter) },
+ ID: "caddy.logging.encoders.filter.ip_mask",
+ New: func() caddy.Module { return new(IPMaskFilter) },
}
}
diff --git a/modules_test.go b/modules_test.go
index ef7edf7..c561c6f 100644
--- a/modules_test.go
+++ b/modules_test.go
@@ -22,17 +22,17 @@ import (
func TestGetModules(t *testing.T) {
modulesMu.Lock()
modules = map[string]ModuleInfo{
- "a": {Name: "a"},
- "a.b": {Name: "a.b"},
- "a.b.c": {Name: "a.b.c"},
- "a.b.cd": {Name: "a.b.cd"},
- "a.c": {Name: "a.c"},
- "a.d": {Name: "a.d"},
- "b": {Name: "b"},
- "b.a": {Name: "b.a"},
- "b.b": {Name: "b.b"},
- "b.a.c": {Name: "b.a.c"},
- "c": {Name: "c"},
+ "a": {ID: "a"},
+ "a.b": {ID: "a.b"},
+ "a.b.c": {ID: "a.b.c"},
+ "a.b.cd": {ID: "a.b.cd"},
+ "a.c": {ID: "a.c"},
+ "a.d": {ID: "a.d"},
+ "b": {ID: "b"},
+ "b.a": {ID: "b.a"},
+ "b.b": {ID: "b.b"},
+ "b.a.c": {ID: "b.a.c"},
+ "c": {ID: "c"},
}
modulesMu.Unlock()
@@ -43,24 +43,24 @@ func TestGetModules(t *testing.T) {
{
input: "",
expect: []ModuleInfo{
- {Name: "a"},
- {Name: "b"},
- {Name: "c"},
+ {ID: "a"},
+ {ID: "b"},
+ {ID: "c"},
},
},
{
input: "a",
expect: []ModuleInfo{
- {Name: "a.b"},
- {Name: "a.c"},
- {Name: "a.d"},
+ {ID: "a.b"},
+ {ID: "a.c"},
+ {ID: "a.d"},
},
},
{
input: "a.b",
expect: []ModuleInfo{
- {Name: "a.b.c"},
- {Name: "a.b.cd"},
+ {ID: "a.b.c"},
+ {ID: "a.b.cd"},
},
},
{
@@ -69,8 +69,8 @@ func TestGetModules(t *testing.T) {
{
input: "b",
expect: []ModuleInfo{
- {Name: "b.a"},
- {Name: "b.b"},
+ {ID: "b.a"},
+ {ID: "b.b"},
},
},
{