summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Holt <mholt@users.noreply.github.com>2019-12-10 13:36:46 -0700
committerGitHub <noreply@github.com>2019-12-10 13:36:46 -0700
commit3c90e370a49cafe7f58c7195187822ddc86ced4a (patch)
treeaadac21fcc1d55b37e65762022f8f30f565c2d8d
parenta8533e563045f686b4c5af8d293903ab5c238244 (diff)
v2: Module documentation; refactor LoadModule(); new caddy struct tags (#2924)
This commit goes a long way toward making automated documentation of Caddy config and Caddy modules possible. It's a broad, sweeping change, but mostly internal. It allows us to automatically generate docs for all Caddy modules (including future third-party ones) and make them viewable on a web page; it also doubles as godoc comments. As such, this commit makes significant progress in migrating the docs from our temporary wiki page toward our new website which is still under construction. With this change, all host modules will use ctx.LoadModule() and pass in both the struct pointer and the field name as a string. This allows the reflect package to read the struct tag from that field so that it can get the necessary information like the module namespace and the inline key. This has the nice side-effect of unifying the code and documentation. It also simplifies module loading, and handles several variations on field types for raw module fields (i.e. variations on json.RawMessage, such as arrays and maps). I also renamed ModuleInfo.Name -> ModuleInfo.ID, to make it clear that the ID is the "full name" which includes both the module namespace and the name. This clarity is helpful when describing module hierarchy. As of this change, Caddy modules are no longer an experimental design. I think the architecture is good enough to go forward.
-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"},
},
},
{