@marcel91200 / caddy Mirror · read-only / commits / 3c90e370
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

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.

Matt Holt committed Dec 10, 2019 at 13:36 UTC 3c90e370a49cafe7f58c7195187822ddc86ced4a
65 files changed +1359 -627
admin.go
+27 -6
@@ -39,12 +39,33 @@ import (
39
40 // TODO: is there a way to make the admin endpoint so that it can be plugged into the HTTP app? see issue #2833
41
42 -// AdminConfig configures the admin endpoint.
42 +// AdminConfig configures Caddy's API endpoint, which is used
43 +// to manage Caddy while it is running.
44 type AdminConfig struct {
44 - Disabled bool `json:"disabled,omitempty"`
45 - Listen string `json:"listen,omitempty"`
46 - EnforceOrigin bool `json:"enforce_origin,omitempty"`
47 - Origins []string `json:"origins,omitempty"`
45 + // If true, the admin endpoint will be completely disabled.
46 + // Note that this makes any runtime changes to the config
47 + // impossible, since the interface to do so is through the
48 + // admin endpoint.
49 + Disabled bool `json:"disabled,omitempty"`
50 +
51 + // The address to which the admin endpoint's listener should
52 + // bind itself. Can be any single network address that can be
53 + // parsed by Caddy.
54 + Listen string `json:"listen,omitempty"`
55 +
56 + // If true, CORS headers will be emitted, and requests to the
57 + // API will be rejected if their `Host` and `Origin` headers
58 + // do not match the expected value(s). Use `origins` to
59 + // customize which origins/hosts are allowed.If `origins` is
60 + // not set, the listen address is the only value allowed by
61 + // default.
62 + EnforceOrigin bool `json:"enforce_origin,omitempty"`
63 +
64 + // The list of allowed origins for API requests. Only used if
65 + // `enforce_origin` is true. If not set, the listener address
66 + // will be the default value. If set but empty, no origins will
67 + // be allowed.
68 + Origins []string `json:"origins,omitempty"`
69 }
70
71 // listenAddr extracts a singular listen address from ac.Listen,
@@ -706,7 +727,7 @@ traverseLoop:
727 ptr = v[partInt]
728
729 default:
709 - return fmt.Errorf("invalid path: %s", parts[:i+1])
730 + return fmt.Errorf("invalid traversal path at: %s", strings.Join(parts[:i+1], "/"))
731 }
732 }
733
caddy.go
+49 -25
@@ -32,23 +32,44 @@ import (
32 "github.com/mholt/certmagic"
33 )
34
35 -// Config represents a Caddy configuration. It is the
36 -// top of the module structure: all Caddy modules will
37 -// be loaded, starting with this struct. In order to
38 -// be loaded and run successfully, a Config and all its
39 -// modules must be JSON-encodable; i.e. when filling a
40 -// Config struct manually, its JSON-encodable fields
41 -// (the ones with JSON struct tags, usually ending with
42 -// "Raw" if they decode into a separate field) must be
43 -// set so that they can be unmarshaled and provisioned.
44 -// Setting the fields for the decoded values instead
45 -// will result in those values being overwritten at
46 -// unmarshaling/provisioning.
35 +// Config is the top (or beginning) of the Caddy configuration structure.
36 +// Caddy config is expressed natively as a JSON document. If you prefer
37 +// not to work with JSON directly, there are [many config adapters](/docs/config-adapters)
38 +// available that can convert various inputs into Caddy JSON.
39 +//
40 +// Many parts of this config are extensible through the use of Caddy modules.
41 +// Fields which have a json.RawMessage type and which appear as dots (•••) in
42 +// the online docs can be fulfilled by modules in a certain module
43 +// namespace. The docs show which modules can be used in a given place.
44 +//
45 +// Whenever a module is used, its name must be given either inline as part of
46 +// the module, or as the key to the module's value. The docs will make it clear
47 +// which to use.
48 +//
49 +// Generally, all config settings are optional, as it is Caddy convention to
50 +// have good, documented default values. If a parameter is required, the docs
51 +// should say so.
52 +//
53 +// Go programs which are directly building a Config struct value should take
54 +// care to populate the JSON-encodable fields of the struct (i.e. the fields
55 +// with `json` struct tags) if employing the module lifecycle (e.g. Provision
56 +// method calls).
57 type Config struct {
48 - Admin *AdminConfig `json:"admin,omitempty"`
49 - Logging *Logging `json:"logging,omitempty"`
50 - StorageRaw json.RawMessage `json:"storage,omitempty"`
51 - AppsRaw map[string]json.RawMessage `json:"apps,omitempty"`
58 + Admin *AdminConfig `json:"admin,omitempty"`
59 + Logging *Logging `json:"logging,omitempty"`
60 +
61 + // StorageRaw is a storage module that defines how/where Caddy
62 + // stores assets (such as TLS certificates). By default, this is
63 + // the local file system (`caddy.storage.file_system` module).
64 + // If the `XDG_DATA_HOME` environment variable is set, then
65 + // `$XDG_DATA_HOME/caddy` is the default folder. Otherwise,
66 + // `$HOME/.local/share/caddy` is the default folder.
67 + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
68 +
69 + // AppsRaw are the apps that Caddy will load and run. The
70 + // app module name is the key, and the app's config is the
71 + // associated value.
72 + AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
73
74 apps map[string]App
75 storage certmagic.Storage
@@ -322,7 +343,7 @@ func run(newCfg *Config, start bool) error {
343 // set up global storage and make it CertMagic's default storage, too
344 err = func() error {
345 if newCfg.StorageRaw != nil {
325 - val, err := ctx.LoadModuleInline("module", "caddy.storage", newCfg.StorageRaw)
346 + val, err := ctx.LoadModule(newCfg, "StorageRaw")
347 if err != nil {
348 return fmt.Errorf("loading storage module: %v", err)
349 }
@@ -331,8 +352,8 @@ func run(newCfg *Config, start bool) error {
352 return fmt.Errorf("creating storage value: %v", err)
353 }
354 newCfg.storage = stor
334 - newCfg.StorageRaw = nil // allow GC to deallocate
355 }
356 +
357 if newCfg.storage == nil {
358 newCfg.storage = &certmagic.FileStorage{Path: dataDir()}
359 }
@@ -346,12 +367,12 @@ func run(newCfg *Config, start bool) error {
367
368 // Load, Provision, Validate each app and their submodules
369 err = func() error {
349 - for modName, rawMsg := range newCfg.AppsRaw {
350 - val, err := ctx.LoadModule(modName, rawMsg)
351 - if err != nil {
352 - return fmt.Errorf("loading app module '%s': %v", modName, err)
353 - }
354 - newCfg.apps[modName] = val.(App)
370 + appsIface, err := ctx.LoadModule(newCfg, "AppsRaw")
371 + if err != nil {
372 + return fmt.Errorf("loading app modules: %v", err)
373 + }
374 + for appName, appIface := range appsIface.(map[string]interface{}) {
375 + newCfg.apps[appName] = appIface.(App)
376 }
377 return nil
378 }()
@@ -447,7 +468,10 @@ func Validate(cfg *Config) error {
468 return err
469 }
470
450 -// Duration is a JSON-string-unmarshable duration type.
471 +// Duration can be an integer or a string. An integer is
472 +// interpreted as nanoseconds. If a string, it is a Go
473 +// time.Duration value such as `300ms`, `1.5h`, or `2h45m`;
474 +// valid units are `ns`, `us`/`µs`, `ms`, `s`, `m`, and `h`.
475 type Duration time.Duration
476
477 // UnmarshalJSON satisfies json.Unmarshaler.
caddyconfig/httpcaddyfile/builtins.go
+2 -1
@@ -21,6 +21,7 @@ import (
21 "net/http"
22 "reflect"
23
24 + "github.com/caddyserver/caddy/v2"
25 "github.com/caddyserver/caddy/v2/caddyconfig"
26 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
27 "github.com/caddyserver/caddy/v2/modules/caddytls"
@@ -71,7 +72,7 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
72 },
73 }
74 if matcherSet != nil {
74 - route.MatcherSetsRaw = []map[string]json.RawMessage{matcherSet}
75 + route.MatcherSetsRaw = []caddy.ModuleMap{matcherSet}
76 }
77
78 return h.NewVarsRoute(route), nil
caddyconfig/httpcaddyfile/directives.go
+5 -5
@@ -88,7 +88,7 @@ type Helper struct {
88 *caddyfile.Dispenser
89 options map[string]interface{}
90 warnings *[]caddyconfig.Warning
91 - matcherDefs map[string]map[string]json.RawMessage
91 + matcherDefs map[string]caddy.ModuleMap
92 parentBlock caddyfile.ServerBlock
93 }
94
@@ -125,7 +125,7 @@ func (h Helper) JSON(val interface{}, warnings *[]caddyconfig.Warning) json.RawM
125 // if so, returns the matcher set along with a true value. If the current
126 // token is not a matcher, nil and false is returned. Note that a true
127 // value may be returned with a nil matcher set if it is a catch-all.
128 -func (h Helper) MatcherToken() (map[string]json.RawMessage, bool, error) {
128 +func (h Helper) MatcherToken() (caddy.ModuleMap, bool, error) {
129 if !h.NextArg() {
130 return nil, false, nil
131 }
@@ -133,13 +133,13 @@ func (h Helper) MatcherToken() (map[string]json.RawMessage, bool, error) {
133 }
134
135 // NewRoute returns config values relevant to creating a new HTTP route.
136 -func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
136 +func (h Helper) NewRoute(matcherSet caddy.ModuleMap,
137 handler caddyhttp.MiddlewareHandler) []ConfigValue {
138 mod, err := caddy.GetModule(caddy.GetModuleName(handler))
139 if err != nil {
140 // TODO: append to warnings
141 }
142 - var matcherSetsRaw []map[string]json.RawMessage
142 + var matcherSetsRaw []caddy.ModuleMap
143 if matcherSet != nil {
144 matcherSetsRaw = append(matcherSetsRaw, matcherSet)
145 }
@@ -148,7 +148,7 @@ func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
148 Class: "route",
149 Value: caddyhttp.Route{
150 MatcherSetsRaw: matcherSetsRaw,
151 - HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID(), h.warnings)},
151 + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID.Name(), h.warnings)},
152 },
153 },
154 }
caddyconfig/httpcaddyfile/httptype.go
+18 -18
@@ -172,7 +172,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
172 }
173
174 // now for the TLS app! (TODO: refactor into own func)
175 - tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)}
175 + tlsApp := caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
176 for _, p := range pairings {
177 for i, sblock := range p.serverBlocks {
178 // tls automation policies
@@ -189,7 +189,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
189 }
190 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
191 Hosts: sblockHosts,
192 - ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &warnings),
192 + ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID.Name(), &warnings),
193 })
194 } else {
195 warnings = append(warnings, caddyconfig.Warning{
@@ -204,7 +204,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
204 for _, clVal := range clVals {
205 loader := clVal.Value.(caddytls.CertificateLoader)
206 loaderName := caddy.GetModuleID(loader)
207 - tlsApp.Certificates[loaderName] = caddyconfig.JSON(loader, &warnings)
207 + tlsApp.CertificatesRaw[loaderName] = caddyconfig.JSON(loader, &warnings)
208 }
209 }
210 }
@@ -243,17 +243,17 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
243 }
244
245 // annnd the top-level config, then we're done!
246 - cfg := &caddy.Config{AppsRaw: make(map[string]json.RawMessage)}
246 + cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
247 if !reflect.DeepEqual(httpApp, caddyhttp.App{}) {
248 cfg.AppsRaw["http"] = caddyconfig.JSON(httpApp, &warnings)
249 }
250 - if !reflect.DeepEqual(tlsApp, caddytls.TLS{Certificates: make(map[string]json.RawMessage)}) {
250 + if !reflect.DeepEqual(tlsApp, caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) {
251 cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings)
252 }
253 if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok {
254 cfg.StorageRaw = caddyconfig.JSONModuleObject(storageCvtr,
255 "module",
256 - storageCvtr.(caddy.Module).CaddyModule().ID(),
256 + storageCvtr.(caddy.Module).CaddyModule().ID.Name(),
257 &warnings)
258 }
259 if adminConfig, ok := options["admin"].(string); ok && adminConfig != "" {
@@ -337,7 +337,7 @@ func (st *ServerType) serversFromPairings(
337 }
338
339 // TODO: are matchers needed if every hostname of the config is matched?
340 - cp.Matchers = map[string]json.RawMessage{
340 + cp.MatchersRaw = caddy.ModuleMap{
341 "sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
342 }
343 srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
@@ -469,9 +469,9 @@ func consolidateAutomationPolicies(aps []caddytls.AutomationPolicy) []caddytls.A
469
470 func matcherSetFromMatcherToken(
471 tkn caddyfile.Token,
472 - matcherDefs map[string]map[string]json.RawMessage,
472 + matcherDefs map[string]caddy.ModuleMap,
473 warnings *[]caddyconfig.Warning,
474 -) (map[string]json.RawMessage, bool, error) {
474 +) (caddy.ModuleMap, bool, error) {
475 // matcher tokens can be wildcards, simple path matchers,
476 // or refer to a pre-defined matcher by some name
477 if tkn.Text == "*" {
@@ -479,7 +479,7 @@ func matcherSetFromMatcherToken(
479 return nil, true, nil
480 } else if strings.HasPrefix(tkn.Text, "/") || strings.HasPrefix(tkn.Text, "=/") {
481 // convenient way to specify a single path match
482 - return map[string]json.RawMessage{
482 + return caddy.ModuleMap{
483 "path": caddyconfig.JSON(caddyhttp.MatchPath{tkn.Text}, warnings),
484 }, true, nil
485 } else if strings.HasPrefix(tkn.Text, "match:") {
@@ -495,7 +495,7 @@ func matcherSetFromMatcherToken(
495 return nil, false, nil
496 }
497
498 -func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([]map[string]json.RawMessage, error) {
498 +func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([]caddy.ModuleMap, error) {
499 type hostPathPair struct {
500 hostm caddyhttp.MatchHost
501 pathm caddyhttp.MatchPath
@@ -562,7 +562,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
562 }
563
564 // finally, encode each of the matcher sets
565 - var matcherSetsEnc []map[string]json.RawMessage
565 + var matcherSetsEnc []caddy.ModuleMap
566 for _, ms := range matcherSets {
567 msEncoded, err := encodeMatcherSet(ms)
568 if err != nil {
@@ -574,8 +574,8 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
574 return matcherSetsEnc, nil
575 }
576
577 -func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json.RawMessage, error) {
578 - matchers := make(map[string]map[string]json.RawMessage)
577 +func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]caddy.ModuleMap, error) {
578 + matchers := make(map[string]caddy.ModuleMap)
579 for d.Next() {
580 definitionName := d.Val()
581 for nesting := d.Nesting(); d.NextBlock(nesting); {
@@ -597,7 +597,7 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json
597 return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
598 }
599 if _, ok := matchers[definitionName]; !ok {
600 - matchers[definitionName] = make(map[string]json.RawMessage)
600 + matchers[definitionName] = make(caddy.ModuleMap)
601 }
602 matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
603 }
@@ -605,8 +605,8 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json
605 return matchers, nil
606 }
607
608 -func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (map[string]json.RawMessage, error) {
609 - msEncoded := make(map[string]json.RawMessage)
608 +func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.ModuleMap, error) {
609 + msEncoded := make(caddy.ModuleMap)
610 for matcherName, val := range matchers {
611 jsonBytes, err := json.Marshal(val)
612 if err != nil {
@@ -628,7 +628,7 @@ func tryInt(val interface{}, warnings *[]caddyconfig.Warning) int {
628 }
629
630 type matcherSetAndTokens struct {
631 - matcherSet map[string]json.RawMessage
631 + matcherSet caddy.ModuleMap
632 tokens []caddyfile.Token
633 }
634
caddyconfig/httpcaddyfile/options.go
+2 -2
@@ -96,7 +96,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
96 }
97 unm, ok := mod.New().(caddyfile.Unmarshaler)
98 if !ok {
99 - return nil, fmt.Errorf("storage module '%s' is not a Caddyfile unmarshaler", mod.Name)
99 + return nil, fmt.Errorf("storage module '%s' is not a Caddyfile unmarshaler", mod.ID)
100 }
101 err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
102 if err != nil {
@@ -104,7 +104,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
104 }
105 storage, ok := unm.(caddy.StorageConverter)
106 if !ok {
107 - return nil, fmt.Errorf("module %s is not a StorageConverter", mod.Name)
107 + return nil, fmt.Errorf("module %s is not a StorageConverter", mod.ID)
108 }
109 return storage, nil
110 }
cmd/commandfuncs.go
+6 -6
@@ -321,11 +321,11 @@ func cmdListModules(fl Flags) (int, error) {
321 return caddy.ExitCodeSuccess, nil
322 }
323
324 - for _, modName := range caddy.Modules() {
325 - modInfo, err := caddy.GetModule(modName)
324 + for _, modID := range caddy.Modules() {
325 + modInfo, err := caddy.GetModule(modID)
326 if err != nil {
327 // that's weird
328 - fmt.Println(modName)
328 + fmt.Println(modID)
329 continue
330 }
331
@@ -354,13 +354,13 @@ func cmdListModules(fl Flags) (int, error) {
354 }
355
356 // if we could find no matching module, just print out
357 - // the module name instead
357 + // the module ID instead
358 if matched == nil {
359 - fmt.Println(modName)
359 + fmt.Println(modID)
360 continue
361 }
362
363 - fmt.Printf("%s %s\n", modName, matched.Version)
363 + fmt.Printf("%s %s\n", modID, matched.Version)
364 }
365
366 return caddy.ExitCodeSuccess, nil
context.go
+222 -35
@@ -80,24 +80,211 @@ func (ctx *Context) OnCancel(f func()) {
80 ctx.cleanupFuncs = append(ctx.cleanupFuncs, f)
81 }
82
83 -// LoadModule decodes rawMsg into a new instance of mod and
84 -// returns the value. If mod.New() does not return a pointer
85 -// value, it is converted to one so that it is unmarshaled
86 -// into the underlying concrete type. If mod.New is nil, an
87 -// error is returned. If the module implements Validator or
88 -// Provisioner interfaces, those methods are invoked to
89 -// ensure the module is fully configured and valid before
90 -// being used.
91 -func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
92 - modulesMu.Lock()
93 - mod, ok := modules[name]
94 - modulesMu.Unlock()
83 +// LoadModule loads the Caddy module(s) from the specified field of the parent struct
84 +// pointer and returns the loaded module(s). The struct pointer and its field name as
85 +// a string are necessary so that reflection can be used to read the struct tag on the
86 +// field to get the module namespace and inline module name key (if specified).
87 +//
88 +// The field can be any one of the supported raw module types: json.RawMessage,
89 +// []json.RawMessage, map[string]json.RawMessage, or []map[string]json.RawMessage.
90 +// ModuleMap may be used in place of map[string]json.RawMessage. The return value's
91 +// underlying type mirrors the input field's type:
92 +//
93 +// json.RawMessage => interface{}
94 +// []json.RawMessage => []interface{}
95 +// map[string]json.RawMessage => map[string]interface{}
96 +// []map[string]json.RawMessage => []map[string]interface{}
97 +//
98 +// The field must have a "caddy" struct tag in this format:
99 +//
100 +// caddy:"key1=val1 key2=val2"
101 +//
102 +// To load modules, a "namespace" key is required. For example, to load modules
103 +// in the "http.handlers" namespace, you'd put: `namespace=http.handlers` in the
104 +// Caddy struct tag.
105 +//
106 +// The module name must also be available. If the field type is a map or slice of maps,
107 +// then key is assumed to be the module name if an "inline_key" is NOT specified in the
108 +// caddy struct tag. In this case, the module name does NOT need to be specified in-line
109 +// with the module itself.
110 +//
111 +// If not a map, or if inline_key is non-empty, then the module name must be embedded
112 +// into the values, which must be objects; then there must be a key in those objects
113 +// where its associated value is the module name. This is called the "inline key",
114 +// meaning the key containing the module's name that is defined inline with the module
115 +// itself. You must specify the inline key in a struct tag, along with the namespace:
116 +//
117 +// caddy:"namespace=http.handlers inline_key=handler"
118 +//
119 +// This will look for a key/value pair like `"handler": "..."` in the json.RawMessage
120 +// in order to know the module name.
121 +//
122 +// To make use of the loaded module(s) (the return value), you will probably want
123 +// to type-assert each interface{} value(s) to the types that are useful to you
124 +// and store them on the same struct. Storing them on the same struct makes for
125 +// easy garbage collection when your host module is no longer needed.
126 +//
127 +// Loaded modules have already been provisioned and validated.
128 +func (ctx Context) LoadModule(structPointer interface{}, fieldName string) (interface{}, error) {
129 + val := reflect.ValueOf(structPointer).Elem().FieldByName(fieldName)
130 + typ := val.Type()
131 +
132 + field, ok := reflect.TypeOf(structPointer).Elem().FieldByName(fieldName)
133 + if !ok {
134 + panic(fmt.Sprintf("field %s does not exist in %#v", fieldName, structPointer))
135 + }
136 +
137 + opts, err := ParseStructTag(field.Tag.Get("caddy"))
138 + if err != nil {
139 + panic(fmt.Sprintf("malformed tag on field %s: %v", fieldName, err))
140 + }
141 +
142 + moduleNamespace, ok := opts["namespace"]
143 + if !ok {
144 + panic(fmt.Sprintf("missing 'namespace' key in struct tag on field %s", fieldName))
145 + }
146 + inlineModuleKey := opts["inline_key"]
147 +
148 + var result interface{}
149 +
150 + switch val.Kind() {
151 + case reflect.Slice:
152 + if isJSONRawMessage(typ) {
153 + // val is `json.RawMessage` ([]uint8 under the hood)
154 +
155 + if inlineModuleKey == "" {
156 + panic("unable to determine module name without inline_key when type is not a ModuleMap")
157 + }
158 + val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Interface().(json.RawMessage))
159 + if err != nil {
160 + return nil, err
161 + }
162 + result = val
163 +
164 + } else if isJSONRawMessage(typ.Elem()) {
165 + // val is `[]json.RawMessage`
166 +
167 + if inlineModuleKey == "" {
168 + panic("unable to determine module name without inline_key because type is not a ModuleMap")
169 + }
170 + var all []interface{}
171 + for i := 0; i < val.Len(); i++ {
172 + val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Index(i).Interface().(json.RawMessage))
173 + if err != nil {
174 + return nil, fmt.Errorf("position %d: %v", i, err)
175 + }
176 + all = append(all, val)
177 + }
178 + result = all
179 +
180 + } else if isModuleMapType(typ.Elem()) {
181 + // val is `[]map[string]json.RawMessage`
182 +
183 + var all []map[string]interface{}
184 + for i := 0; i < val.Len(); i++ {
185 + thisSet, err := ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val.Index(i))
186 + if err != nil {
187 + return nil, err
188 + }
189 + all = append(all, thisSet)
190 + }
191 + result = all
192 + }
193 +
194 + case reflect.Map:
195 + // val is a ModuleMap or some other kind of map
196 + result, err = ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val)
197 + if err != nil {
198 + return nil, err
199 + }
200 +
201 + default:
202 + return nil, fmt.Errorf("unrecognized type for module: %s", typ)
203 + }
204 +
205 + // we're done with the raw bytes; allow GC to deallocate
206 + val.Set(reflect.Zero(typ))
207 +
208 + return result, nil
209 +}
210 +
211 +// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]interface{}.
212 +// Depending on inlineModuleKey, it will be interpeted as either a ModuleMap (key is the module
213 +// name) or as a regular map (key is not the module name, and module name is defined inline).
214 +func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]interface{}, error) {
215 + // if no inline_key is specified, then val must be a ModuleMap,
216 + // where the key is the module name
217 + if inlineModuleKey == "" {
218 + if !isModuleMapType(val.Type()) {
219 + panic(fmt.Sprintf("expected ModuleMap because inline_key is empty; but we do not recognize this type: %s", val.Type()))
220 + }
221 + return ctx.loadModuleMap(namespace, val)
222 + }
223 +
224 + // otherwise, val is a map with modules, but the module name is
225 + // inline with each value (the key means something else)
226 + return ctx.loadModulesFromRegularMap(namespace, inlineModuleKey, val)
227 +}
228 +
229 +// loadModulesFromRegularMap loads modules from val, where val is a map[string]json.RawMessage.
230 +// Map keys are NOT interpreted as module names, so module names are still expected to appear
231 +// inline with the objects.
232 +func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]interface{}, error) {
233 + mods := make(map[string]interface{})
234 + iter := val.MapRange()
235 + for iter.Next() {
236 + k := iter.Key()
237 + v := iter.Value()
238 + mod, err := ctx.loadModuleInline(inlineModuleKey, namespace, v.Interface().(json.RawMessage))
239 + if err != nil {
240 + return nil, fmt.Errorf("key %s: %v", k, err)
241 + }
242 + mods[k.String()] = mod
243 + }
244 + return mods, nil
245 +}
246 +
247 +// loadModuleMap loads modules from a ModuleMap, i.e. map[string]interface{}, where the key is the
248 +// module name. With a module map, module names do not need to be defined inline with their values.
249 +func (ctx Context) loadModuleMap(namespace string, val reflect.Value) (map[string]interface{}, error) {
250 + all := make(map[string]interface{})
251 + iter := val.MapRange()
252 + for iter.Next() {
253 + k := iter.Key().Interface().(string)
254 + v := iter.Value().Interface().(json.RawMessage)
255 + moduleName := namespace + "." + k
256 + if namespace == "" {
257 + moduleName = k
258 + }
259 + val, err := ctx.LoadModuleByID(moduleName, v)
260 + if err != nil {
261 + return nil, fmt.Errorf("module name '%s': %v", k, err)
262 + }
263 + all[k] = val
264 + }
265 + return all, nil
266 +}
267 +
268 +// LoadModuleByID decodes rawMsg into a new instance of mod and
269 +// returns the value. If mod.New is nil, an error is returned.
270 +// If the module implements Validator or Provisioner interfaces,
271 +// those methods are invoked to ensure the module is fully
272 +// configured and valid before being used.
273 +//
274 +// This is a lower-level method and will usually not be called
275 +// directly by most modules. However, this method is useful when
276 +// dynamically loading/unloading modules in their own context,
277 +// like from embedded scripts, etc.
278 +func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (interface{}, error) {
279 + modulesMu.RLock()
280 + mod, ok := modules[id]
281 + modulesMu.RUnlock()
282 if !ok {
96 - return nil, fmt.Errorf("unknown module: %s", name)
283 + return nil, fmt.Errorf("unknown module: %s", id)
284 }
285
286 if mod.New == nil {
100 - return nil, fmt.Errorf("module '%s' has no constructor", mod.Name)
287 + return nil, fmt.Errorf("module '%s' has no constructor", mod.ID)
288 }
289
290 val := mod.New().(interface{})
@@ -108,7 +295,7 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
295 if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
296 log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+
297 " so we are using reflection to make a pointer instead; please fix this by"+
111 - " using new(Type) or &Type notation in your module's New() function.", name)
298 + " using new(Type) or &Type notation in your module's New() function.", id)
299 val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)
300 }
301
@@ -116,7 +303,7 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
303 if len(rawMsg) > 0 {
304 err := strictUnmarshalJSON(rawMsg, &val)
305 if err != nil {
119 - return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
306 + return nil, fmt.Errorf("decoding module config: %s: %v", mod, err)
307 }
308 }
309
@@ -124,8 +311,8 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
311 // returned module values are almost always type-asserted
312 // before being used, so a nil value would panic; and there
313 // is no good reason to explicitly declare null modules in
127 - // a config; it might be because the user is trying to
128 - // achieve a result they aren't expecting, which is a smell
314 + // a config; it might be because the user is trying to achieve
315 + // a result the developer isn't expecting, which is a smell
316 return nil, fmt.Errorf("module value cannot be null")
317 }
318
@@ -140,7 +327,7 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
327 err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
328 }
329 }
143 - return nil, fmt.Errorf("provision %s: %v", mod.Name, err)
330 + return nil, fmt.Errorf("provision %s: %v", mod, err)
331 }
332 }
333
@@ -154,33 +341,33 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
341 err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
342 }
343 }
157 - return nil, fmt.Errorf("%s: invalid configuration: %v", mod.Name, err)
344 + return nil, fmt.Errorf("%s: invalid configuration: %v", mod, err)
345 }
346 }
347
161 - ctx.moduleInstances[name] = append(ctx.moduleInstances[name], val)
348 + ctx.moduleInstances[id] = append(ctx.moduleInstances[id], val)
349
350 return val, nil
351 }
352
166 -// LoadModuleInline loads a module from a JSON raw message which decodes
167 -// to a map[string]interface{}, where one of the keys is moduleNameKey
168 -// and the corresponding value is the module name as a string, which
169 -// can be found in the given scope.
170 -//
171 -// This allows modules to be decoded into their concrete types and
172 -// used when their names cannot be the unique key in a map, such as
173 -// when there are multiple instances in the map or it appears in an
174 -// array (where there are no custom keys). In other words, the key
175 -// containing the module name is treated special/separate from all
176 -// the other keys.
177 -func (ctx Context) LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
353 +// loadModuleInline loads a module from a JSON raw message which decodes to
354 +// a map[string]interface{}, where one of the object keys is moduleNameKey
355 +// and the corresponding value is the module name (as a string) which can
356 +// be found in the given scope. In other words, the module name is declared
357 +// in-line with the module itself.
358 +//
359 +// This allows modules to be decoded into their concrete types and used when
360 +// their names cannot be the unique key in a map, such as when there are
361 +// multiple instances in the map or it appears in an array (where there are
362 +// no custom keys). In other words, the key containing the module name is
363 +// treated special/separate from all the other keys in the object.
364 +func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
365 moduleName, raw, err := getModuleNameInline(moduleNameKey, raw)
366 if err != nil {
367 return nil, err
368 }
369
183 - val, err := ctx.LoadModule(moduleScope+"."+moduleName, raw)
370 + val, err := ctx.LoadModuleByID(moduleScope+"."+moduleName, raw)
371 if err != nil {
372 return nil, fmt.Errorf("loading module '%s': %v", moduleName, err)
373 }
@@ -195,7 +382,7 @@ func (ctx Context) App(name string) (interface{}, error) {
382 if app, ok := ctx.cfg.apps[name]; ok {
383 return app, nil
384 }
198 - modVal, err := ctx.LoadModule(name, nil)
385 + modVal, err := ctx.LoadModuleByID(name, nil)
386 if err != nil {
387 return nil, fmt.Errorf("instantiating new module %s: %v", name, err)
388 }
logging.go
+95 -33
@@ -36,8 +36,36 @@ func init() {
36 }
37
38 // Logging facilitates logging within Caddy.
39 +//
40 +// By default, all logs at INFO level and higher are written to
41 +// standard error ("stderr" writer) in a human-readable format
42 +// ("console" encoder). The default log is called "default" and
43 +// you can customize it. You can also define additional logs.
44 +//
45 +// All defined logs accept all log entries by default, but you
46 +// can filter by level and module/logger names. A logger's name
47 +// is the same as the module's name, but a module may append to
48 +// logger names for more specificity. For example, you can
49 +// filter logs emitted only by HTTP handlers using the name
50 +// "http.handlers", because all HTTP handler module names have
51 +// that prefix.
52 +//
53 +// Caddy logs (except the sink) are mostly zero-allocation, so
54 +// they are very high-performing in terms of memory and CPU time.
55 +// Enabling sampling can further increase throughput on extremely
56 +// high-load servers.
57 type Logging struct {
40 - Sink *StandardLibLog `json:"sink,omitempty"`
58 + // Sink is the destination for all unstructured logs emitted
59 + // from Go's standard library logger. These logs are common
60 + // in dependencies that are not designed specifically for use
61 + // in Caddy. Because it is global and unstructured, the sink
62 + // lacks most advanced features and customizations.
63 + Sink *StandardLibLog `json:"sink,omitempty"`
64 +
65 + // Logs are your logs, keyed by an arbitrary name of your
66 + // choosing. The default log can be customized by defining
67 + // a log called "default". You can further define other logs
68 + // and filter what kinds of entries they accept.
69 Logs map[string]*CustomLog `json:"logs,omitempty"`
70
71 // a list of all keys for open writers; all writers
@@ -169,12 +197,12 @@ func (logging *Logging) closeLogs() error {
197
198 // Logger returns a logger that is ready for the module to use.
199 func (logging *Logging) Logger(mod Module) *zap.Logger {
172 - modName := mod.CaddyModule().Name
200 + modID := string(mod.CaddyModule().ID)
201 var cores []zapcore.Core
202
203 if logging != nil {
204 for _, l := range logging.Logs {
177 - if l.matchesModule(modName) {
205 + if l.matchesModule(modID) {
206 if len(l.Include) == 0 && len(l.Exclude) == 0 {
207 cores = append(cores, l.core)
208 continue
@@ -186,7 +214,7 @@ func (logging *Logging) Logger(mod Module) *zap.Logger {
214
215 multiCore := zapcore.NewTee(cores...)
216
189 - return zap.New(multiCore).Named(modName)
217 + return zap.New(multiCore).Named(string(modID))
218 }
219
220 // openWriter opens a writer using opener, and returns true if
@@ -231,26 +259,27 @@ func (wdest writerDestructor) Destruct() error {
259 // StandardLibLog configures the default Go standard library
260 // global logger in the log package. This is necessary because
261 // module dependencies which are not built specifically for
234 -// Caddy will use the standard logger.
262 +// Caddy will use the standard logger. This is also known as
263 +// the "sink" logger.
264 type StandardLibLog struct {
236 - WriterRaw json.RawMessage `json:"writer,omitempty"`
265 + // The module that writes out log entries for the sink.
266 + WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
267
268 writer io.WriteCloser
269 }
270
271 func (sll *StandardLibLog) provision(ctx Context, logging *Logging) error {
272 if sll.WriterRaw != nil {
243 - val, err := ctx.LoadModuleInline("output", "caddy.logging.writers", sll.WriterRaw)
273 + mod, err := ctx.LoadModule(sll, "WriterRaw")
274 if err != nil {
275 return fmt.Errorf("loading sink log writer module: %v", err)
276 }
247 - wo := val.(WriterOpener)
248 - sll.WriterRaw = nil // allow GC to deallocate
277 + wo := mod.(WriterOpener)
278
279 var isNew bool
280 sll.writer, isNew, err = logging.openWriter(wo)
281 if err != nil {
253 - return fmt.Errorf("opening sink log writer %#v: %v", val, err)
282 + return fmt.Errorf("opening sink log writer %#v: %v", mod, err)
283 }
284
285 if isNew {
@@ -264,13 +293,40 @@ func (sll *StandardLibLog) provision(ctx Context, logging *Logging) error {
293 }
294
295 // CustomLog represents a custom logger configuration.
296 +//
297 +// By default, a log will emit all log entries. Some entries
298 +// will be skipped if sampling is enabled. Further, the Include
299 +// and Exclude parameters define which loggers (by name) are
300 +// allowed or rejected from emitting in this log. If both Include
301 +// and Exclude are populated, their values must be mutually
302 +// exclusive, and longer namespaces have priority. If neither
303 +// are populated, all logs are emitted.
304 type CustomLog struct {
268 - WriterRaw json.RawMessage `json:"writer,omitempty"`
269 - EncoderRaw json.RawMessage `json:"encoder,omitempty"`
270 - Level string `json:"level,omitempty"`
271 - Sampling *LogSampling `json:"sampling,omitempty"`
272 - Include []string `json:"include,omitempty"`
273 - Exclude []string `json:"exclude,omitempty"`
305 + // The writer defines where log entries are emitted.
306 + WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
307 +
308 + // The encoder is how the log entries are formatted or encoded.
309 + EncoderRaw json.RawMessage `json:"encoder,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
310 +
311 + // Level is the minimum level to emit, and is inclusive.
312 + // Possible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL
313 + Level string `json:"level,omitempty"`
314 +
315 + // Sampling configures log entry sampling. If enabled,
316 + // only some log entries will be emitted. This is useful
317 + // for improving performance on extremely high-pressure
318 + // servers.
319 + Sampling *LogSampling `json:"sampling,omitempty"`
320 +
321 + // Include defines the names of loggers to emit in this
322 + // log. For example, to include only logs emitted by the
323 + // admin API, you would include "admin.api".
324 + Include []string `json:"include,omitempty"`
325 +
326 + // Exclude defines the names of loggers that should be
327 + // skipped by this log. For example, to exclude only
328 + // HTTP access logs, you would exclude "http.log.access".
329 + Exclude []string `json:"exclude,omitempty"`
330
331 writerOpener WriterOpener
332 writer io.WriteCloser
@@ -336,24 +392,22 @@ func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
392 }
393
394 if cl.EncoderRaw != nil {
339 - val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", cl.EncoderRaw)
395 + mod, err := ctx.LoadModule(cl, "EncoderRaw")
396 if err != nil {
397 return fmt.Errorf("loading log encoder module: %v", err)
398 }
343 - cl.EncoderRaw = nil // allow GC to deallocate
344 - cl.encoder = val.(zapcore.Encoder)
399 + cl.encoder = mod.(zapcore.Encoder)
400 }
401 if cl.encoder == nil {
402 cl.encoder = newDefaultProductionLogEncoder()
403 }
404
405 if cl.WriterRaw != nil {
351 - val, err := ctx.LoadModuleInline("output", "caddy.logging.writers", cl.WriterRaw)
406 + mod, err := ctx.LoadModule(cl, "WriterRaw")
407 if err != nil {
408 return fmt.Errorf("loading log writer module: %v", err)
409 }
355 - cl.WriterRaw = nil // allow GC to deallocate
356 - cl.writerOpener = val.(WriterOpener)
410 + cl.writerOpener = mod.(WriterOpener)
411 }
412 if cl.writerOpener == nil {
413 cl.writerOpener = StderrWriter{}
@@ -398,8 +452,8 @@ func (cl *CustomLog) buildCore() {
452 cl.core = c
453 }
454
401 -func (cl *CustomLog) matchesModule(moduleName string) bool {
402 - return cl.loggerAllowed(moduleName, true)
455 +func (cl *CustomLog) matchesModule(moduleID string) bool {
456 + return cl.loggerAllowed(string(moduleID), true)
457 }
458
459 // loggerAllowed returns true if name is allowed to emit
@@ -493,9 +547,17 @@ func (fc *filteringCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapco
547
548 // LogSampling configures log entry sampling.
549 type LogSampling struct {
496 - Interval time.Duration `json:"interval,omitempty"`
497 - First int `json:"first,omitempty"`
498 - Thereafter int `json:"thereafter,omitempty"`
550 + // The window over which to conduct sampling.
551 + Interval time.Duration `json:"interval,omitempty"`
552 +
553 + // Log this many entries within a given level and
554 + // message for each interval.
555 + First int `json:"first,omitempty"`
556 +
557 + // If more entries with the same level and message
558 + // are seen during the same interval, keep one in
559 + // this many entries until the end of the interval.
560 + Thereafter int `json:"thereafter,omitempty"`
561 }
562
563 type (
@@ -512,24 +574,24 @@ type (
574 // CaddyModule returns the Caddy module information.
575 func (StdoutWriter) CaddyModule() ModuleInfo {
576 return ModuleInfo{
515 - Name: "caddy.logging.writers.stdout",
516 - New: func() Module { return new(StdoutWriter) },
577 + ID: "caddy.logging.writers.stdout",
578 + New: func() Module { return new(StdoutWriter) },
579 }
580 }
581
582 // CaddyModule returns the Caddy module information.
583 func (StderrWriter) CaddyModule() ModuleInfo {
584 return ModuleInfo{
523 - Name: "caddy.logging.writers.stderr",
524 - New: func() Module { return new(StderrWriter) },
585 + ID: "caddy.logging.writers.stderr",
586 + New: func() Module { return new(StderrWriter) },
587 }
588 }
589
590 // CaddyModule returns the Caddy module information.
591 func (DiscardWriter) CaddyModule() ModuleInfo {
592 return ModuleInfo{
531 - Name: "caddy.logging.writers.discard",
532 - New: func() Module { return new(DiscardWriter) },
593 + ID: "caddy.logging.writers.discard",
594 + New: func() Module { return new(DiscardWriter) },
595 }
596 }
597
modules.go
+141 -56
@@ -18,58 +18,107 @@ import (
18 "bytes"
19 "encoding/json"
20 "fmt"
21 + "reflect"
22 "sort"
23 "strings"
24 "sync"
25 )
26
26 -// Module is a type that is used as a Caddy module.
27 +// Module is a type that is used as a Caddy module. In
28 +// addition to this interface, most modules will implement
29 +// some interface expected by their host module in order
30 +// to be useful. To learn which interface(s) to implement,
31 +// see the documentation for the host module. At a bare
32 +// minimum, this interface, when implemented, only provides
33 +// the module's ID and constructor function.
34 +//
35 +// Modules will often implement additional interfaces
36 +// including Provisioner, Validator, and CleanerUpper.
37 +// If a module implements these interfaces, their
38 +// methods are called during the module's lifespan.
39 +//
40 +// When a module is loaded by a host module, the following
41 +// happens: 1) ModuleInfo.New() is called to get a new
42 +// instance of the module. 2) The module's configuration is
43 +// unmarshaled into that instance. 3) If the module is a
44 +// Provisioner, the Provision() method is called. 4) If the
45 +// module is a Validator, the Validate() method is called.
46 +// 5) The module will probably be type-asserted from
47 +// interface{} to some other, more useful interface expected
48 +// by the host module. For example, HTTP handler modules are
49 +// type-asserted as caddyhttp.MiddlewareHandler values.
50 +// 6) When a module's containing Context is canceled, if it is
51 +// a CleanerUpper, its Cleanup() method is called.
52 type Module interface {
28 - // This method indicates the type is a Caddy
29 - // module. The returned ModuleInfo must have
30 - // both a name and a constructor function.
31 - // This method must not have any side-effects.
53 + // This method indicates that the type is a Caddy
54 + // module. The returned ModuleInfo must have both
55 + // a name and a constructor function. This method
56 + // must not have any side-effects.
57 CaddyModule() ModuleInfo
58 }
59
60 // ModuleInfo represents a registered Caddy module.
61 type ModuleInfo struct {
37 - // Name is the full name of the module. It
62 + // ID is the "full name" of the module. It
63 // must be unique and properly namespaced.
39 - Name string
64 + ID ModuleID
65
66 // New returns a pointer to a new, empty
42 - // instance of the module's type. The host
43 - // module which instantiates this module will
44 - // likely type-assert and invoke methods on
45 - // the returned value. This function must not
46 - // have any side-effects.
67 + // instance of the module's type. This
68 + // function must not have any side-effects.
69 New func() Module
70 }
71
50 -// Namespace returns the module's namespace (scope)
51 -// which is all but the last element of its name.
52 -// If there is no explicit namespace in the name,
53 -// the whole name is considered the namespace.
54 -func (mi ModuleInfo) Namespace() string {
55 - lastDot := strings.LastIndex(mi.Name, ".")
72 +// ModuleID is a string that uniquely identifies a Caddy module. A
73 +// module ID is lightly structured. It consists of dot-separated
74 +// labels which form a simple hierarchy from left to right. The last
75 +// label is the module name, and the labels before that constitute
76 +// the namespace (or scope).
77 +//
78 +// Thus, a module ID has the form: <namespace>.<id>
79 +//
80 +// An ID with no dot has the empty namespace, which is appropriate
81 +// for app modules (these are "top-level" modules that Caddy core
82 +// loads and runs).
83 +//
84 +// Module IDs should be lowercase and use underscore (_) instead of
85 +// spaces.
86 +//
87 +// Example valid names:
88 +// - http
89 +// - http.handlers.file_server
90 +// - caddy.logging.encoders.json
91 +type ModuleID string
92 +
93 +// Namespace returns the namespace (or scope) portion of a module ID,
94 +// which is all but the last label of the ID. If the ID has only one
95 +// label, then
96 +func (id ModuleID) Namespace() string {
97 + lastDot := strings.LastIndex(string(id), ".")
98 if lastDot < 0 {
57 - return mi.Name
99 + return string(id)
100 }
59 - return mi.Name[:lastDot]
101 + return string(id)[:lastDot]
102 }
103
62 -// ID returns a module's ID, which is the
63 -// last element of its name.
64 -func (mi ModuleInfo) ID() string {
65 - if mi.Name == "" {
104 +// Name returns the Name (last element) of a module name.
105 +func (id ModuleID) Name() string {
106 + if id == "" {
107 return ""
108 }
68 - parts := strings.Split(mi.Name, ".")
109 + parts := strings.Split(string(id), ".")
110 return parts[len(parts)-1]
111 }
112
72 -func (mi ModuleInfo) String() string { return mi.Name }
113 +func (mi ModuleInfo) String() string { return string(mi.ID) }
114 +
115 +// ModuleMap is a map that can contain multiple modules,
116 +// where the map key is the module's name. (The namespace
117 +// is usually read from an associated field's struct tag.)
118 +// Because the module's name is given as the key in a
119 +// module map, the name does not have to be given in the
120 +// json.RawMessage.
121 +type ModuleMap map[string]json.RawMessage
122
123 // RegisterModule registers a module by receiving a
124 // plain/empty value of the module. For registration to
@@ -82,11 +131,11 @@ func (mi ModuleInfo) String() string { return mi.Name }
131 func RegisterModule(instance Module) error {
132 mod := instance.CaddyModule()
133
85 - if mod.Name == "" {
86 - return fmt.Errorf("missing ModuleInfo.Name")
134 + if mod.ID == "" {
135 + return fmt.Errorf("module ID missing")
136 }
88 - if mod.Name == "caddy" || mod.Name == "admin" {
89 - return fmt.Errorf("module name '%s' is reserved", mod.Name)
137 + if mod.ID == "caddy" || mod.ID == "admin" {
138 + return fmt.Errorf("module ID '%s' is reserved", mod.ID)
139 }
140 if mod.New == nil {
141 return fmt.Errorf("missing ModuleInfo.New")
@@ -98,17 +147,17 @@ func RegisterModule(instance Module) error {
147 modulesMu.Lock()
148 defer modulesMu.Unlock()
149
101 - if _, ok := modules[mod.Name]; ok {
102 - return fmt.Errorf("module already registered: %s", mod.Name)
150 + if _, ok := modules[string(mod.ID)]; ok {
151 + return fmt.Errorf("module already registered: %s", mod.ID)
152 }
104 - modules[mod.Name] = mod
153 + modules[string(mod.ID)] = mod
154 return nil
155 }
156
108 -// GetModule returns module information from its full name.
157 +// GetModule returns module information from its ID (full name).
158 func GetModule(name string) (ModuleInfo, error) {
110 - modulesMu.Lock()
111 - defer modulesMu.Unlock()
159 + modulesMu.RLock()
160 + defer modulesMu.RUnlock()
161 m, ok := modules[name]
162 if !ok {
163 return ModuleInfo{}, fmt.Errorf("module not registered: %s", name)
@@ -116,25 +165,25 @@ func GetModule(name string) (ModuleInfo, error) {
165 return m, nil
166 }
167
119 -// GetModuleName returns a module's name from an instance of its value.
120 -// If the value is not a module, an empty name will be returned.
168 +// GetModuleName returns a module's name (the last label of its ID)
169 +// from an instance of its value. If the value is not a module, an
170 +// empty string will be returned.
171 func GetModuleName(instance interface{}) string {
172 var name string
173 if mod, ok := instance.(Module); ok {
124 - name = mod.CaddyModule().Name
174 + name = mod.CaddyModule().ID.Name()
175 }
176 return name
177 }
178
129 -// GetModuleID returns a module's ID (the last element of its name)
130 -// from an instance of its value. If the value is not a module,
131 -// an empty string will be returned.
179 +// GetModuleID returns a module's ID from an instance of its value.
180 +// If the value is not a module, an empty string will be returned.
181 func GetModuleID(instance interface{}) string {
133 - var name string
182 + var id string
183 if mod, ok := instance.(Module); ok {
135 - name = mod.CaddyModule().ID()
184 + id = string(mod.CaddyModule().ID)
185 }
137 - return name
186 + return id
187 }
188
189 // GetModules returns all modules in the given scope/namespace.
@@ -144,11 +193,11 @@ func GetModuleID(instance interface{}) string {
193 // scopes are not matched (i.e. scope "foo.ba" does not match
194 // name "foo.bar").
195 //
147 -// Because modules are registered to a map, the returned slice
148 -// will be sorted to keep it deterministic.
196 +// Because modules are registered to a map under the hood, the
197 +// returned slice will be sorted to keep it deterministic.
198 func GetModules(scope string) []ModuleInfo {
150 - modulesMu.Lock()
151 - defer modulesMu.Unlock()
199 + modulesMu.RLock()
200 + defer modulesMu.RUnlock()
201
202 scopeParts := strings.Split(scope, ".")
203
@@ -160,8 +209,8 @@ func GetModules(scope string) []ModuleInfo {
209
210 var mods []ModuleInfo
211 iterateModules:
163 - for name, m := range modules {
164 - modParts := strings.Split(name, ".")
212 + for id, m := range modules {
213 + modParts := strings.Split(string(id), ".")
214
215 // match only the next level of nesting
216 if len(modParts) != len(scopeParts)+1 {
@@ -180,7 +229,7 @@ iterateModules:
229
230 // make return value deterministic
231 sort.Slice(mods, func(i, j int) bool {
183 - return mods[i].Name < mods[j].Name
232 + return mods[i].ID < mods[j].ID
233 })
234
235 return mods
@@ -189,12 +238,12 @@ iterateModules:
238 // Modules returns the names of all registered modules
239 // in ascending lexicographical order.
240 func Modules() []string {
192 - modulesMu.Lock()
193 - defer modulesMu.Unlock()
241 + modulesMu.RLock()
242 + defer modulesMu.RUnlock()
243
244 var names []string
245 for name := range modules {
197 - names = append(names, name)
246 + names = append(names, string(name))
247 }
248
249 sort.Strings(names)
@@ -261,6 +310,25 @@ type CleanerUpper interface {
310 Cleanup() error
311 }
312
313 +// ParseStructTag parses a caddy struct tag into its keys and values.
314 +// It is very simple. The expected syntax is:
315 +// `caddy:"key1=val1 key2=val2 ..."`
316 +func ParseStructTag(tag string) (map[string]string, error) {
317 + results := make(map[string]string)
318 + pairs := strings.Split(tag, " ")
319 + for i, pair := range pairs {
320 + if pair == "" {
321 + continue
322 + }
323 + parts := strings.SplitN(pair, "=", 2)
324 + if len(parts) != 2 {
325 + return nil, fmt.Errorf("missing key in '%s' (pair %d)", pair, i)
326 + }
327 + results[parts[0]] = parts[1]
328 + }
329 + return results, nil
330 +}
331 +
332 // strictUnmarshalJSON is like json.Unmarshal but returns an error
333 // if any of the fields are unrecognized. Useful when decoding
334 // module configurations, where you want to be more sure they're
@@ -271,7 +339,24 @@ func strictUnmarshalJSON(data []byte, v interface{}) error {
339 return dec.Decode(v)
340 }
341
342 +// isJSONRawMessage returns true if the type is encoding/json.RawMessage.
343 +func isJSONRawMessage(typ reflect.Type) bool {
344 + return typ.PkgPath() == "encoding/json" && typ.Name() == "RawMessage"
345 +}
346 +
347 +// isModuleMapType returns true if the type is map[string]json.RawMessage.
348 +// It assumes that the string key is the module name, but this is not
349 +// always the case. To know for sure, this function must return true, but
350 +// also the struct tag where this type appears must NOT define an inline_key
351 +// attribute, which would mean that the module names appear inline with the
352 +// values, not in the key.
353 +func isModuleMapType(typ reflect.Type) bool {
354 + return typ.Kind() == reflect.Map &&
355 + typ.Key().Kind() == reflect.String &&
356 + isJSONRawMessage(typ.Elem())
357 +}
358 +
359 var (
360 modules = make(map[string]ModuleInfo)
276 - modulesMu sync.Mutex
361 + modulesMu sync.RWMutex
362 )
modules/caddyhttp/caddyauth/basicauth.go
+5 -6
@@ -28,7 +28,7 @@ func init() {
28
29 // HTTPBasicAuth facilitates HTTP basic authentication.
30 type HTTPBasicAuth struct {
31 - HashRaw json.RawMessage `json:"hash,omitempty"`
31 + HashRaw json.RawMessage `json:"hash,omitempty" caddy:"namespace=http.authentication.hashes inline_key=algorithm"`
32 AccountList []Account `json:"accounts,omitempty"`
33 Realm string `json:"realm,omitempty"`
34
@@ -39,8 +39,8 @@ type HTTPBasicAuth struct {
39 // CaddyModule returns the Caddy module information.
40 func (HTTPBasicAuth) CaddyModule() caddy.ModuleInfo {
41 return caddy.ModuleInfo{
42 - Name: "http.handlers.authentication.providers.http_basic",
43 - New: func() caddy.Module { return new(HTTPBasicAuth) },
42 + ID: "http.authentication.providers.http_basic",
43 + New: func() caddy.Module { return new(HTTPBasicAuth) },
44 }
45 }
46
@@ -51,12 +51,11 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
51 }
52
53 // load password hasher
54 - hashIface, err := ctx.LoadModuleInline("algorithm", "http.handlers.authentication.hashes", hba.HashRaw)
54 + hasherIface, err := ctx.LoadModule(hba, "HashRaw")
55 if err != nil {
56 return fmt.Errorf("loading password hasher module: %v", err)
57 }
58 - hba.Hash = hashIface.(Comparer)
59 - hba.HashRaw = nil // allow GC to deallocate
58 + hba.Hash = hasherIface.(Comparer)
59
60 if hba.Hash == nil {
61 return fmt.Errorf("hash is required")
modules/caddyhttp/caddyauth/caddyauth.go
+9 -12
@@ -15,7 +15,6 @@
15 package caddyauth
16
17 import (
18 - "encoding/json"
18 "fmt"
19 "log"
20 "net/http"
@@ -30,7 +29,7 @@ func init() {
29
30 // Authentication is a middleware which provides user authentication.
31 type Authentication struct {
33 - ProvidersRaw map[string]json.RawMessage `json:"providers,omitempty"`
32 + ProvidersRaw caddy.ModuleMap `json:"providers,omitempty" caddy:"namespace=http.authentication.providers"`
33
34 Providers map[string]Authenticator `json:"-"`
35 }
@@ -38,23 +37,21 @@ type Authentication struct {
37 // CaddyModule returns the Caddy module information.
38 func (Authentication) CaddyModule() caddy.ModuleInfo {
39 return caddy.ModuleInfo{
41 - Name: "http.handlers.authentication",
42 - New: func() caddy.Module { return new(Authentication) },
40 + ID: "http.handlers.authentication",
41 + New: func() caddy.Module { return new(Authentication) },
42 }
43 }
44
45 // Provision sets up a.
46 func (a *Authentication) Provision(ctx caddy.Context) error {
47 a.Providers = make(map[string]Authenticator)
49 - for modName, rawMsg := range a.ProvidersRaw {
50 - val, err := ctx.LoadModule("http.handlers.authentication.providers."+modName, rawMsg)
51 - if err != nil {
52 - return fmt.Errorf("loading authentication provider module '%s': %v", modName, err)
53 - }
54 - a.Providers[modName] = val.(Authenticator)
48 + mods, err := ctx.LoadModule(a, "ProvidersRaw")
49 + if err != nil {
50 + return fmt.Errorf("loading authentication providers: %v", err)
51 + }
52 + for modName, modIface := range mods.(map[string]interface{}) {
53 + a.Providers[modName] = modIface.(Authenticator)
54 }
56 - a.ProvidersRaw = nil // allow GC to deallocate
57 -
55 return nil
56 }
57
modules/caddyhttp/caddyauth/caddyfile.go
+2 -2
@@ -16,8 +16,8 @@ package caddyauth
16
17 import (
18 "encoding/base64"
19 - "encoding/json"
19
20 + "github.com/caddyserver/caddy/v2"
21 "github.com/caddyserver/caddy/v2/caddyconfig"
22 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
23 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
@@ -97,7 +97,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
97 }
98
99 return Authentication{
100 - ProvidersRaw: map[string]json.RawMessage{
100 + ProvidersRaw: caddy.ModuleMap{
101 "http_basic": caddyconfig.JSON(ba, nil),
102 },
103 }, nil
modules/caddyhttp/caddyauth/hashes.go
+4 -4
@@ -33,8 +33,8 @@ type BcryptHash struct{}
33 // CaddyModule returns the Caddy module information.
34 func (BcryptHash) CaddyModule() caddy.ModuleInfo {
35 return caddy.ModuleInfo{
36 - Name: "http.handlers.authentication.hashes.bcrypt",
37 - New: func() caddy.Module { return new(BcryptHash) },
36 + ID: "http.authentication.hashes.bcrypt",
37 + New: func() caddy.Module { return new(BcryptHash) },
38 }
39 }
40
@@ -61,8 +61,8 @@ type ScryptHash struct {
61 // CaddyModule returns the Caddy module information.
62 func (ScryptHash) CaddyModule() caddy.ModuleInfo {
63 return caddy.ModuleInfo{
64 - Name: "http.handlers.authentication.hashes.scrypt",
65 - New: func() caddy.Module { return new(ScryptHash) },
64 + ID: "http.authentication.hashes.scrypt",
65 + New: func() caddy.Module { return new(ScryptHash) },
66 }
67 }
68
modules/caddyhttp/caddyhttp.go
+26 -9
@@ -44,12 +44,27 @@ func init() {
44 }
45 }
46
47 -// App is the HTTP app for Caddy.
47 +// App is a robust, flexible HTTP server for Caddy.
48 type App struct {
49 - HTTPPort int `json:"http_port,omitempty"`
50 - HTTPSPort int `json:"https_port,omitempty"`
51 - GracePeriod caddy.Duration `json:"grace_period,omitempty"`
52 - Servers map[string]*Server `json:"servers,omitempty"`
49 + // HTTPPort specifies the port to use for HTTP (as opposed to HTTPS),
50 + // which is used when setting up HTTP->HTTPS redirects or ACME HTTP
51 + // challenge solvers. Default: 80.
52 + HTTPPort int `json:"http_port,omitempty"`
53 +
54 + // HTTPSPort specifies the port to use for HTTPS, which is used when
55 + // solving the ACME TLS-ALPN challenges, or whenever HTTPS is needed
56 + // but no specific port number is given. Default: 443.
57 + HTTPSPort int `json:"https_port,omitempty"`
58 +
59 + // GracePeriod is how long to wait for active connections when shutting
60 + // down the server. Once the grace period is over, connections will
61 + // be forcefully closed.
62 + GracePeriod caddy.Duration `json:"grace_period,omitempty"`
63 +
64 + // Servers is the list of servers, keyed by arbitrary names chosen
65 + // at your discretion for your own convenience; the keys do not
66 + // affect functionality.
67 + Servers map[string]*Server `json:"servers,omitempty"`
68
69 servers []*http.Server
70 h3servers []*http3.Server
@@ -62,8 +77,8 @@ type App struct {
77 // CaddyModule returns the Caddy module information.
78 func (App) CaddyModule() caddy.ModuleInfo {
79 return caddy.ModuleInfo{
65 - Name: "http",
66 - New: func() caddy.Module { return new(App) },
80 + ID: "http",
81 + New: func() caddy.Module { return new(App) },
82 }
83 }
84
@@ -561,8 +576,10 @@ var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error {
576
577 // WeakString is a type that unmarshals any JSON value
578 // as a string literal, with the following exceptions:
564 -// 1) actual string values are decoded as strings; and
565 -// 2) null is decoded as empty string;
579 +//
580 +// 1. actual string values are decoded as strings; and
581 +// 2. null is decoded as empty string;
582 +//
583 // and provides methods for getting the value as various
584 // primitive types. However, using this type removes any
585 // type safety as far as deserializing JSON is concerned.
modules/caddyhttp/encode/brotli/brotli.go
+2 -2
@@ -37,8 +37,8 @@ type Brotli struct {
37 // CaddyModule returns the Caddy module information.
38 func (Brotli) CaddyModule() caddy.ModuleInfo {
39 return caddy.ModuleInfo{
40 - Name: "http.encoders.brotli",
41 - New: func() caddy.Module { return new(Brotli) },
40 + ID: "http.encoders.brotli",
41 + New: func() caddy.Module { return new(Brotli) },
42 }
43 }
44
modules/caddyhttp/encode/caddyfile.go
+6 -7
@@ -15,7 +15,6 @@
15 package encode
16
17 import (
18 - "encoding/json"
18 "fmt"
19
20 "github.com/caddyserver/caddy/v2"
@@ -52,14 +51,14 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
51 for _, arg := range d.RemainingArgs() {
52 mod, err := caddy.GetModule("http.encoders." + arg)
53 if err != nil {
55 - return fmt.Errorf("finding encoder module '%s': %v", mod.Name, err)
54 + return fmt.Errorf("finding encoder module '%s': %v", mod, err)
55 }
56 encoding, ok := mod.New().(Encoding)
57 if !ok {
59 - return fmt.Errorf("module %s is not an HTTP encoding", mod.Name)
58 + return fmt.Errorf("module %s is not an HTTP encoding", mod)
59 }
60 if enc.EncodingsRaw == nil {
62 - enc.EncodingsRaw = make(map[string]json.RawMessage)
61 + enc.EncodingsRaw = make(caddy.ModuleMap)
62 }
63 enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
64 }
@@ -72,7 +71,7 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
71 }
72 unm, ok := mod.New().(caddyfile.Unmarshaler)
73 if !ok {
75 - return fmt.Errorf("encoder module '%s' is not a Caddyfile unmarshaler", mod.Name)
74 + return fmt.Errorf("encoder module '%s' is not a Caddyfile unmarshaler", mod)
75 }
76 err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
77 if err != nil {
@@ -80,10 +79,10 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
79 }
80 encoding, ok := unm.(Encoding)
81 if !ok {
83 - return fmt.Errorf("module %s is not an HTTP encoding", mod.Name)
82 + return fmt.Errorf("module %s is not an HTTP encoding", mod)
83 }
84 if enc.EncodingsRaw == nil {
86 - enc.EncodingsRaw = make(map[string]json.RawMessage)
85 + enc.EncodingsRaw = make(caddy.ModuleMap)
86 }
87 enc.EncodingsRaw[name] = caddyconfig.JSON(encoding, nil)
88 }
modules/caddyhttp/encode/encode.go
+12 -15
@@ -21,7 +21,6 @@ package encode
21
22 import (
23 "bytes"
24 - "encoding/json"
24 "fmt"
25 "io"
26 "net/http"
@@ -40,9 +39,9 @@ func init() {
39
40 // Encode is a middleware which can encode responses.
41 type Encode struct {
43 - EncodingsRaw map[string]json.RawMessage `json:"encodings,omitempty"`
44 - Prefer []string `json:"prefer,omitempty"`
45 - MinLength int `json:"minimum_length,omitempty"`
42 + EncodingsRaw caddy.ModuleMap `json:"encodings,omitempty" caddy:"namespace=http.encoders"`
43 + Prefer []string `json:"prefer,omitempty"`
44 + MinLength int `json:"minimum_length,omitempty"`
45
46 writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads...
47 }
@@ -50,25 +49,23 @@ type Encode struct {
49 // CaddyModule returns the Caddy module information.
50 func (Encode) CaddyModule() caddy.ModuleInfo {
51 return caddy.ModuleInfo{
53 - Name: "http.handlers.encode",
54 - New: func() caddy.Module { return new(Encode) },
52 + ID: "http.handlers.encode",
53 + New: func() caddy.Module { return new(Encode) },
54 }
55 }
56
57 // Provision provisions enc.
58 func (enc *Encode) Provision(ctx caddy.Context) error {
60 - for modName, rawMsg := range enc.EncodingsRaw {
61 - val, err := ctx.LoadModule("http.encoders."+modName, rawMsg)
62 - if err != nil {
63 - return fmt.Errorf("loading encoder module '%s': %v", modName, err)
64 - }
65 - encoding := val.(Encoding)
66 - err = enc.addEncoding(encoding)
59 + mods, err := ctx.LoadModule(enc, "EncodingsRaw")
60 + if err != nil {
61 + return fmt.Errorf("loading encoder modules: %v", err)
62 + }
63 + for modName, modIface := range mods.(map[string]interface{}) {
64 + err = enc.addEncoding(modIface.(Encoding))
65 if err != nil {
68 - return err
66 + return fmt.Errorf("adding encoding %s: %v", modName, err)
67 }
68 }
71 - enc.EncodingsRaw = nil // allow GC to deallocate
69
70 if enc.MinLength == 0 {
71 enc.MinLength = defaultMinLength
modules/caddyhttp/encode/gzip/gzip.go
+2 -2
@@ -37,8 +37,8 @@ type Gzip struct {
37 // CaddyModule returns the Caddy module information.
38 func (Gzip) CaddyModule() caddy.ModuleInfo {
39 return caddy.ModuleInfo{
40 - Name: "http.encoders.gzip",
41 - New: func() caddy.Module { return new(Gzip) },
40 + ID: "http.encoders.gzip",
41 + New: func() caddy.Module { return new(Gzip) },
42 }
43 }
44
modules/caddyhttp/encode/zstd/zstd.go
+2 -2
@@ -31,8 +31,8 @@ type Zstd struct{}
31 // CaddyModule returns the Caddy module information.
32 func (Zstd) CaddyModule() caddy.ModuleInfo {
33 return caddy.ModuleInfo{
34 - Name: "http.encoders.zstd",
35 - New: func() caddy.Module { return new(Zstd) },
34 + ID: "http.encoders.zstd",
35 + New: func() caddy.Module { return new(Zstd) },
36 }
37 }
38
modules/caddyhttp/fileserver/caddyfile.go
+2 -3
@@ -15,8 +15,7 @@
15 package fileserver
16
17 import (
18 - "encoding/json"
19 -
18 + "github.com/caddyserver/caddy/v2"
19 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
20 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
21 "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
@@ -122,7 +121,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
121 URI: "{http.matchers.file.relative}{http.request.uri.query_string}",
122 }
123
125 - matcherSet := map[string]json.RawMessage{
124 + matcherSet := caddy.ModuleMap{
125 "file": h.JSON(MatchFile{
126 TryFiles: try,
127 }, nil),
modules/caddyhttp/fileserver/command.go
+3 -3
@@ -75,8 +75,8 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
75 },
76 }
77 if domain != "" {
78 - route.MatcherSetsRaw = []map[string]json.RawMessage{
79 - map[string]json.RawMessage{
78 + route.MatcherSetsRaw = []caddy.ModuleMap{
79 + caddy.ModuleMap{
80 "host": caddyconfig.JSON(caddyhttp.MatchHost{domain}, nil),
81 },
82 }
@@ -100,7 +100,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
100
101 cfg := &caddy.Config{
102 Admin: &caddy.AdminConfig{Disabled: true},
103 - AppsRaw: map[string]json.RawMessage{
103 + AppsRaw: caddy.ModuleMap{
104 "http": caddyconfig.JSON(httpApp, nil),
105 },
106 }
modules/caddyhttp/fileserver/matcher.go
+2 -2
@@ -54,8 +54,8 @@ type MatchFile struct {
54 // CaddyModule returns the Caddy module information.
55 func (MatchFile) CaddyModule() caddy.ModuleInfo {
56 return caddy.ModuleInfo{
57 - Name: "http.matchers.file",
58 - New: func() caddy.Module { return new(MatchFile) },
57 + ID: "http.matchers.file",
58 + New: func() caddy.Module { return new(MatchFile) },
59 }
60 }
61
modules/caddyhttp/fileserver/staticfiles.go
+2 -2
@@ -53,8 +53,8 @@ type FileServer struct {
53 // CaddyModule returns the Caddy module information.
54 func (FileServer) CaddyModule() caddy.ModuleInfo {
55 return caddy.ModuleInfo{
56 - Name: "http.handlers.file_server",
57 - New: func() caddy.Module { return new(FileServer) },
56 + ID: "http.handlers.file_server",
57 + New: func() caddy.Module { return new(FileServer) },
58 }
59 }
60
modules/caddyhttp/headers/headers.go
+2 -2
@@ -37,8 +37,8 @@ type Handler struct {
37 // CaddyModule returns the Caddy module information.
38 func (Handler) CaddyModule() caddy.ModuleInfo {
39 return caddy.ModuleInfo{
40 - Name: "http.handlers.headers",
41 - New: func() caddy.Module { return new(Handler) },
40 + ID: "http.handlers.headers",
41 + New: func() caddy.Module { return new(Handler) },
42 }
43 }
44
modules/caddyhttp/httpcache/httpcache.go
+2 -2
@@ -43,8 +43,8 @@ type Cache struct {
43 // CaddyModule returns the Caddy module information.
44 func (Cache) CaddyModule() caddy.ModuleInfo {
45 return caddy.ModuleInfo{
46 - Name: "http.handlers.cache",
47 - New: func() caddy.Module { return new(Cache) },
46 + ID: "http.handlers.cache",
47 + New: func() caddy.Module { return new(Cache) },
48 }
49 }
50
modules/caddyhttp/markdown/markdown.go
+2 -2
@@ -38,8 +38,8 @@ type Markdown struct {
38 // CaddyModule returns the Caddy module information.
39 func (Markdown) CaddyModule() caddy.ModuleInfo {
40 return caddy.ModuleInfo{
41 - Name: "http.handlers.markdown",
42 - New: func() caddy.Module { return new(Markdown) },
41 + ID: "http.handlers.markdown",
42 + New: func() caddy.Module { return new(Markdown) },
43 }
44 }
45
modules/caddyhttp/matchers.go
+30 -31
@@ -66,7 +66,7 @@ type (
66
67 // MatchNegate matches requests by negating its matchers' results.
68 MatchNegate struct {
69 - MatchersRaw map[string]json.RawMessage `json:"-"`
69 + MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
70
71 Matchers MatcherSet `json:"-"`
72 }
@@ -95,8 +95,8 @@ func init() {
95 // CaddyModule returns the Caddy module information.
96 func (MatchHost) CaddyModule() caddy.ModuleInfo {
97 return caddy.ModuleInfo{
98 - Name: "http.matchers.host",
99 - New: func() caddy.Module { return new(MatchHost) },
98 + ID: "http.matchers.host",
99 + New: func() caddy.Module { return new(MatchHost) },
100 }
101 }
102
@@ -149,8 +149,8 @@ outer:
149 // CaddyModule returns the Caddy module information.
150 func (MatchPath) CaddyModule() caddy.ModuleInfo {
151 return caddy.ModuleInfo{
152 - Name: "http.matchers.path",
153 - New: func() caddy.Module { return new(MatchPath) },
152 + ID: "http.matchers.path",
153 + New: func() caddy.Module { return new(MatchPath) },
154 }
155 }
156
@@ -208,8 +208,8 @@ func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
208 // CaddyModule returns the Caddy module information.
209 func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
210 return caddy.ModuleInfo{
211 - Name: "http.matchers.path_regexp",
212 - New: func() caddy.Module { return new(MatchPathRE) },
211 + ID: "http.matchers.path_regexp",
212 + New: func() caddy.Module { return new(MatchPathRE) },
213 }
214 }
215
@@ -222,8 +222,8 @@ func (m MatchPathRE) Match(r *http.Request) bool {
222 // CaddyModule returns the Caddy module information.
223 func (MatchMethod) CaddyModule() caddy.ModuleInfo {
224 return caddy.ModuleInfo{
225 - Name: "http.matchers.method",
226 - New: func() caddy.Module { return new(MatchMethod) },
225 + ID: "http.matchers.method",
226 + New: func() caddy.Module { return new(MatchMethod) },
227 }
228 }
229
@@ -248,8 +248,8 @@ func (m MatchMethod) Match(r *http.Request) bool {
248 // CaddyModule returns the Caddy module information.
249 func (MatchQuery) CaddyModule() caddy.ModuleInfo {
250 return caddy.ModuleInfo{
251 - Name: "http.matchers.query",
252 - New: func() caddy.Module { return new(MatchQuery) },
251 + ID: "http.matchers.query",
252 + New: func() caddy.Module { return new(MatchQuery) },
253 }
254 }
255
@@ -291,8 +291,8 @@ func (m MatchQuery) Match(r *http.Request) bool {
291 // CaddyModule returns the Caddy module information.
292 func (MatchHeader) CaddyModule() caddy.ModuleInfo {
293 return caddy.ModuleInfo{
294 - Name: "http.matchers.header",
295 - New: func() caddy.Module { return new(MatchHeader) },
294 + ID: "http.matchers.header",
295 + New: func() caddy.Module { return new(MatchHeader) },
296 }
297 }
298
@@ -349,8 +349,8 @@ func (m MatchHeader) Match(r *http.Request) bool {
349 // CaddyModule returns the Caddy module information.
350 func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo {
351 return caddy.ModuleInfo{
352 - Name: "http.matchers.header_regexp",
353 - New: func() caddy.Module { return new(MatchHeaderRE) },
352 + ID: "http.matchers.header_regexp",
353 + New: func() caddy.Module { return new(MatchHeaderRE) },
354 }
355 }
356
@@ -406,8 +406,8 @@ func (m MatchHeaderRE) Validate() error {
406 // CaddyModule returns the Caddy module information.
407 func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
408 return caddy.ModuleInfo{
409 - Name: "http.matchers.protocol",
410 - New: func() caddy.Module { return new(MatchProtocol) },
409 + ID: "http.matchers.protocol",
410 + New: func() caddy.Module { return new(MatchProtocol) },
411 }
412 }
413
@@ -439,8 +439,8 @@ func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
439 // CaddyModule returns the Caddy module information.
440 func (MatchNegate) CaddyModule() caddy.ModuleInfo {
441 return caddy.ModuleInfo{
442 - Name: "http.matchers.not",
443 - New: func() caddy.Module { return new(MatchNegate) },
442 + ID: "http.matchers.not",
443 + New: func() caddy.Module { return new(MatchNegate) },
444 }
445 }
446
@@ -486,7 +486,7 @@ func (m *MatchNegate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
486 // we should now be functional, but we also need
487 // to be able to marshal as JSON, otherwise config
488 // adaptation won't work properly
489 - m.MatchersRaw = make(map[string]json.RawMessage)
489 + m.MatchersRaw = make(caddy.ModuleMap)
490 for name, matchers := range matcherMap {
491 jsonBytes, err := json.Marshal(matchers)
492 if err != nil {
@@ -500,14 +500,13 @@ func (m *MatchNegate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
500
501 // Provision loads the matcher modules to be negated.
502 func (m *MatchNegate) Provision(ctx caddy.Context) error {
503 - for modName, rawMsg := range m.MatchersRaw {
504 - val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
505 - if err != nil {
506 - return fmt.Errorf("loading matcher module '%s': %v", modName, err)
507 - }
508 - m.Matchers = append(m.Matchers, val.(RequestMatcher))
503 + mods, err := ctx.LoadModule(m, "MatchersRaw")
504 + if err != nil {
505 + return fmt.Errorf("loading matchers: %v", err)
506 + }
507 + for _, modIface := range mods.(map[string]interface{}) {
508 + m.Matchers = append(m.Matchers, modIface.(RequestMatcher))
509 }
510 - m.MatchersRaw = nil // allow GC to deallocate
510 return nil
511 }
512
@@ -520,8 +519,8 @@ func (m MatchNegate) Match(r *http.Request) bool {
519 // CaddyModule returns the Caddy module information.
520 func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
521 return caddy.ModuleInfo{
523 - Name: "http.matchers.remote_ip",
524 - New: func() caddy.Module { return new(MatchRemoteIP) },
522 + ID: "http.matchers.remote_ip",
523 + New: func() caddy.Module { return new(MatchRemoteIP) },
524 }
525 }
526
@@ -597,8 +596,8 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
596 // CaddyModule returns the Caddy module information.
597 func (MatchStarlarkExpr) CaddyModule() caddy.ModuleInfo {
598 return caddy.ModuleInfo{
600 - Name: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'?
601 - New: func() caddy.Module { return new(MatchStarlarkExpr) },
599 + ID: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'?
600 + New: func() caddy.Module { return new(MatchStarlarkExpr) },
601 }
602 }
603
modules/caddyhttp/requestbody/requestbody.go
+2 -2
@@ -33,8 +33,8 @@ type RequestBody struct {
33 // CaddyModule returns the Caddy module information.
34 func (RequestBody) CaddyModule() caddy.ModuleInfo {
35 return caddy.ModuleInfo{
36 - Name: "http.handlers.request_body", // TODO: better name for this?
37 - New: func() caddy.Module { return new(RequestBody) },
36 + ID: "http.handlers.request_body", // TODO: better name for this?
37 + New: func() caddy.Module { return new(RequestBody) },
38 }
39 }
40
modules/caddyhttp/reverseproxy/caddyfile.go
+6 -6
@@ -108,11 +108,11 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
108 name := d.Val()
109 mod, err := caddy.GetModule("http.handlers.reverse_proxy.selection_policies." + name)
110 if err != nil {
111 - return d.Errf("getting load balancing policy module '%s': %v", mod.Name, err)
111 + return d.Errf("getting load balancing policy module '%s': %v", mod, err)
112 }
113 unm, ok := mod.New().(caddyfile.Unmarshaler)
114 if !ok {
115 - return d.Errf("load balancing policy module '%s' is not a Caddyfile unmarshaler", mod.Name)
115 + return d.Errf("load balancing policy module '%s' is not a Caddyfile unmarshaler", mod)
116 }
117 err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
118 if err != nil {
@@ -120,7 +120,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
120 }
121 sel, ok := unm.(Selector)
122 if !ok {
123 - return d.Errf("module %s is not a Selector", mod.Name)
123 + return d.Errf("module %s is not a Selector", mod)
124 }
125 if h.LoadBalancing == nil {
126 h.LoadBalancing = new(LoadBalancing)
@@ -391,11 +391,11 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
391 name := d.Val()
392 mod, err := caddy.GetModule("http.handlers.reverse_proxy.transport." + name)
393 if err != nil {
394 - return d.Errf("getting transport module '%s': %v", mod.Name, err)
394 + return d.Errf("getting transport module '%s': %v", mod, err)
395 }
396 unm, ok := mod.New().(caddyfile.Unmarshaler)
397 if !ok {
398 - return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod.Name)
398 + return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod)
399 }
400 err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
401 if err != nil {
@@ -403,7 +403,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
403 }
404 rt, ok := unm.(http.RoundTripper)
405 if !ok {
406 - return d.Errf("module %s is not a RoundTripper", mod.Name)
406 + return d.Errf("module %s is not a RoundTripper", mod)
407 }
408 h.TransportRaw = caddyconfig.JSONModuleObject(rt, "protocol", name, nil)
409
modules/caddyhttp/reverseproxy/circuitbreaker.go
+2 -2
@@ -41,8 +41,8 @@ type localCircuitBreaker struct {
41 // CaddyModule returns the Caddy module information.
42 func (localCircuitBreaker) CaddyModule() caddy.ModuleInfo {
43 return caddy.ModuleInfo{
44 - Name: "http.handlers.reverse_proxy.circuit_breakers.local",
45 - New: func() caddy.Module { return new(localCircuitBreaker) },
44 + ID: "http.reverse_proxy.circuit_breakers.local",
45 + New: func() caddy.Module { return new(localCircuitBreaker) },
46 }
47 }
48
modules/caddyhttp/reverseproxy/command.go
+3 -3
@@ -113,8 +113,8 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
113 }
114 urlHost := fromURL.Hostname()
115 if urlHost != "" {
116 - route.MatcherSetsRaw = []map[string]json.RawMessage{
117 - map[string]json.RawMessage{
116 + route.MatcherSetsRaw = []caddy.ModuleMap{
117 + caddy.ModuleMap{
118 "host": caddyconfig.JSON(caddyhttp.MatchHost{urlHost}, nil),
119 },
120 }
@@ -138,7 +138,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
138
139 cfg := &caddy.Config{
140 Admin: &caddy.AdminConfig{Disabled: true},
141 - AppsRaw: map[string]json.RawMessage{
141 + AppsRaw: caddy.ModuleMap{
142 "http": caddyconfig.JSON(httpApp, nil),
143 },
144 }
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+9 -8
@@ -18,6 +18,7 @@ import (
18 "encoding/json"
19 "net/http"
20
21 + "github.com/caddyserver/caddy/v2"
22 "github.com/caddyserver/caddy/v2/caddyconfig"
23 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
24 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
@@ -121,12 +122,12 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
122 }
123
124 // route to redirect to canonical path if index PHP file
124 - redirMatcherSet := map[string]json.RawMessage{
125 + redirMatcherSet := caddy.ModuleMap{
126 "file": h.JSON(fileserver.MatchFile{
127 TryFiles: []string{"{http.request.uri.path}/index.php"},
128 }, nil),
129 "not": h.JSON(caddyhttp.MatchNegate{
129 - MatchersRaw: map[string]json.RawMessage{
130 + MatchersRaw: caddy.ModuleMap{
131 "path": h.JSON(caddyhttp.MatchPath{"*/"}, nil),
132 },
133 }, nil),
@@ -136,12 +137,12 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
137 Headers: http.Header{"Location": []string{"{http.request.uri.path}/"}},
138 }
139 redirRoute := caddyhttp.Route{
139 - MatcherSetsRaw: []map[string]json.RawMessage{redirMatcherSet},
140 + MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
141 HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
142 }
143
144 // route to rewrite to PHP index file
144 - rewriteMatcherSet := map[string]json.RawMessage{
145 + rewriteMatcherSet := caddy.ModuleMap{
146 "file": h.JSON(fileserver.MatchFile{
147 TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php", "index.php"},
148 }, nil),
@@ -151,13 +152,13 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
152 Rehandle: true,
153 }
154 rewriteRoute := caddyhttp.Route{
154 - MatcherSetsRaw: []map[string]json.RawMessage{rewriteMatcherSet},
155 + MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet},
156 HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)},
157 }
158
159 // route to actually reverse proxy requests to PHP files;
160 // match only requests that are for PHP files
160 - rpMatcherSet := map[string]json.RawMessage{
161 + rpMatcherSet := caddy.ModuleMap{
162 "path": h.JSON([]string{"*.php"}, nil),
163 }
164
@@ -193,7 +194,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
194 // create the final reverse proxy route which is
195 // conditional on matching PHP files
196 rpRoute := caddyhttp.Route{
196 - MatcherSetsRaw: []map[string]json.RawMessage{rpMatcherSet},
197 + MatcherSetsRaw: []caddy.ModuleMap{rpMatcherSet},
198 HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rpHandler, "handler", "reverse_proxy", nil)},
199 }
200
@@ -207,7 +208,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
208 {
209 Class: "route",
210 Value: caddyhttp.Route{
210 - MatcherSetsRaw: []map[string]json.RawMessage{userMatcherSet},
211 + MatcherSetsRaw: []caddy.ModuleMap{userMatcherSet},
212 HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)},
213 },
214 },
modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
+2 -2
@@ -73,8 +73,8 @@ type Transport struct {
73 // CaddyModule returns the Caddy module information.
74 func (Transport) CaddyModule() caddy.ModuleInfo {
75 return caddy.ModuleInfo{
76 - Name: "http.handlers.reverse_proxy.transport.fastcgi",
77 - New: func() caddy.Module { return new(Transport) },
76 + ID: "http.reverse_proxy.transport.fastcgi",
77 + New: func() caddy.Module { return new(Transport) },
78 }
79 }
80
modules/caddyhttp/reverseproxy/httptransport.go
+3 -2
@@ -40,6 +40,7 @@ type HTTPTransport struct {
40 // TODO: It's possible that other transports (like fastcgi) might be
41 // able to borrow/use at least some of these config fields; if so,
42 // maybe move them into a type called CommonTransport and embed it?
43 +
44 TLS *TLSConfig `json:"tls,omitempty"`
45 KeepAlive *KeepAlive `json:"keep_alive,omitempty"`
46 Compression *bool `json:"compression,omitempty"`
@@ -59,8 +60,8 @@ type HTTPTransport struct {
60 // CaddyModule returns the Caddy module information.
61 func (HTTPTransport) CaddyModule() caddy.ModuleInfo {
62 return caddy.ModuleInfo{
62 - Name: "http.handlers.reverse_proxy.transport.http",
63 - New: func() caddy.Module { return new(HTTPTransport) },
63 + ID: "http.reverse_proxy.transport.http",
64 + New: func() caddy.Module { return new(HTTPTransport) },
65 }
66 }
67
modules/caddyhttp/reverseproxy/ntlm.go
+4 -4
@@ -30,12 +30,12 @@ func init() {
30 caddy.RegisterModule(NTLMTransport{})
31 }
32
33 -// NTLMTransport proxies HTTP+NTLM authentication is being used.
33 +// NTLMTransport proxies HTTP with NTLM authentication.
34 // It basically wraps HTTPTransport so that it is compatible with
35 // NTLM's HTTP-hostile requirements. Specifically, it will use
36 // HTTPTransport's single, default *http.Transport for all requests
37 // (unless the client's connection is already mapped to a different
38 -// transport) until a request comes in with Authorization header
38 +// transport) until a request comes in with an Authorization header
39 // that has "NTLM" or "Negotiate"; when that happens, NTLMTransport
40 // maps the client's connection (by its address, req.RemoteAddr)
41 // to a new transport that is used only by that downstream conn.
@@ -56,8 +56,8 @@ type NTLMTransport struct {
56 // CaddyModule returns the Caddy module information.
57 func (NTLMTransport) CaddyModule() caddy.ModuleInfo {
58 return caddy.ModuleInfo{
59 - Name: "http.handlers.reverse_proxy.transport.http_ntlm",
60 - New: func() caddy.Module { return new(NTLMTransport) },
59 + ID: "http.reverse_proxy.transport.http_ntlm",
60 + New: func() caddy.Module { return new(NTLMTransport) },
61 }
62 }
63
modules/caddyhttp/reverseproxy/reverseproxy.go
+24 -26
@@ -42,8 +42,8 @@ func init() {
42
43 // Handler implements a highly configurable and production-ready reverse proxy.
44 type Handler struct {
45 - TransportRaw json.RawMessage `json:"transport,omitempty"`
46 - CBRaw json.RawMessage `json:"circuit_breaker,omitempty"`
45 + TransportRaw json.RawMessage `json:"transport,omitempty" caddy:"namespace=http.reverse_proxy.transport inline_key=protocol"`
46 + CBRaw json.RawMessage `json:"circuit_breaker,omitempty" caddy:"namespace=http.reverse_proxy.circuit_breakers inline_key=type"`
47 LoadBalancing *LoadBalancing `json:"load_balancing,omitempty"`
48 HealthChecks *HealthChecks `json:"health_checks,omitempty"`
49 Upstreams UpstreamPool `json:"upstreams,omitempty"`
@@ -60,8 +60,8 @@ type Handler struct {
60 // CaddyModule returns the Caddy module information.
61 func (Handler) CaddyModule() caddy.ModuleInfo {
62 return caddy.ModuleInfo{
63 - Name: "http.handlers.reverse_proxy",
64 - New: func() caddy.Module { return new(Handler) },
63 + ID: "http.handlers.reverse_proxy",
64 + New: func() caddy.Module { return new(Handler) },
65 }
66 }
67
@@ -71,30 +71,25 @@ func (h *Handler) Provision(ctx caddy.Context) error {
71
72 // start by loading modules
73 if h.TransportRaw != nil {
74 - val, err := ctx.LoadModuleInline("protocol", "http.handlers.reverse_proxy.transport", h.TransportRaw)
74 + mod, err := ctx.LoadModule(h, "TransportRaw")
75 if err != nil {
76 - return fmt.Errorf("loading transport module: %s", err)
76 + return fmt.Errorf("loading transport: %v", err)
77 }
78 - h.Transport = val.(http.RoundTripper)
79 - h.TransportRaw = nil // allow GC to deallocate
78 + h.Transport = mod.(http.RoundTripper)
79 }
80 if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil {
82 - val, err := ctx.LoadModuleInline("policy",
83 - "http.handlers.reverse_proxy.selection_policies",
84 - h.LoadBalancing.SelectionPolicyRaw)
81 + mod, err := ctx.LoadModule(h.LoadBalancing, "SelectionPolicyRaw")
82 if err != nil {
86 - return fmt.Errorf("loading load balancing selection module: %s", err)
83 + return fmt.Errorf("loading load balancing selection policy: %s", err)
84 }
88 - h.LoadBalancing.SelectionPolicy = val.(Selector)
89 - h.LoadBalancing.SelectionPolicyRaw = nil // allow GC to deallocate
85 + h.LoadBalancing.SelectionPolicy = mod.(Selector)
86 }
87 if h.CBRaw != nil {
92 - val, err := ctx.LoadModuleInline("type", "http.handlers.reverse_proxy.circuit_breakers", h.CBRaw)
88 + mod, err := ctx.LoadModule(h, "CBRaw")
89 if err != nil {
94 - return fmt.Errorf("loading circuit breaker module: %s", err)
90 + return fmt.Errorf("loading circuit breaker: %s", err)
91 }
96 - h.CB = val.(CircuitBreaker)
97 - h.CBRaw = nil // allow GC to deallocate
92 + h.CB = mod.(CircuitBreaker)
93 }
94
95 // set up transport
@@ -128,12 +123,14 @@ func (h *Handler) Provision(ctx caddy.Context) error {
123 // defaulting to a sane wait period between attempts
124 h.LoadBalancing.TryInterval = caddy.Duration(250 * time.Millisecond)
125 }
131 - lbMatcherSets, err := h.LoadBalancing.RetryMatchRaw.Setup(ctx)
126 + lbMatcherSets, err := ctx.LoadModule(h.LoadBalancing, "RetryMatchRaw")
127 + if err != nil {
128 + return err
129 + }
130 + err = h.LoadBalancing.RetryMatch.FromInterface(lbMatcherSets)
131 if err != nil {
132 return err
133 }
135 - h.LoadBalancing.RetryMatch = lbMatcherSets
136 - h.LoadBalancing.RetryMatchRaw = nil // allow GC to deallocate
134
135 // if active health checks are enabled, configure them and start a worker
136 if h.HealthChecks != nil &&
@@ -407,7 +404,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
404 // do the round-trip
405 start := time.Now()
406 res, err := h.Transport.RoundTrip(req)
410 - latency := time.Since(start)
407 + duration := time.Since(start)
408 if err != nil {
409 return err
410 }
@@ -415,12 +412,13 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
412 h.logger.Debug("upstream roundtrip",
413 zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}),
414 zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)),
415 + zap.Duration("duration", duration),
416 zap.Int("status", res.StatusCode),
417 )
418
419 // update circuit breaker on current conditions
420 if di.Upstream.cb != nil {
423 - di.Upstream.cb.RecordMetric(res.StatusCode, latency)
421 + di.Upstream.cb.RecordMetric(res.StatusCode, duration)
422 }
423
424 // perform passive health checks (if enabled)
@@ -434,7 +432,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
432
433 // strike if the roundtrip took too long
434 if h.HealthChecks.Passive.UnhealthyLatency > 0 &&
437 - latency >= time.Duration(h.HealthChecks.Passive.UnhealthyLatency) {
435 + duration >= time.Duration(h.HealthChecks.Passive.UnhealthyLatency) {
436 h.countFailure(di.Upstream)
437 }
438 }
@@ -651,10 +649,10 @@ func removeConnectionHeaders(h http.Header) {
649
650 // LoadBalancing has parameters related to load balancing.
651 type LoadBalancing struct {
654 - SelectionPolicyRaw json.RawMessage `json:"selection_policy,omitempty"`
652 + SelectionPolicyRaw json.RawMessage `json:"selection_policy,omitempty" caddy:"namespace=http.reverse_proxy.selection_policies inline_key=policy"`
653 TryDuration caddy.Duration `json:"try_duration,omitempty"`
654 TryInterval caddy.Duration `json:"try_interval,omitempty"`
657 - RetryMatchRaw caddyhttp.RawMatcherSets `json:"retry_match,omitempty"`
655 + RetryMatchRaw caddyhttp.RawMatcherSets `json:"retry_match,omitempty" caddy:"namespace=http.matchers"`
656
657 SelectionPolicy Selector `json:"-"`
658 RetryMatch caddyhttp.MatcherSets `json:"-"`
modules/caddyhttp/reverseproxy/selectionpolicies.go
+16 -16
@@ -48,8 +48,8 @@ type RandomSelection struct{}
48 // CaddyModule returns the Caddy module information.
49 func (RandomSelection) CaddyModule() caddy.ModuleInfo {
50 return caddy.ModuleInfo{
51 - Name: "http.handlers.reverse_proxy.selection_policies.random",
52 - New: func() caddy.Module { return new(RandomSelection) },
51 + ID: "http.reverse_proxy.selection_policies.random",
52 + New: func() caddy.Module { return new(RandomSelection) },
53 }
54 }
55
@@ -84,8 +84,8 @@ type RandomChoiceSelection struct {
84 // CaddyModule returns the Caddy module information.
85 func (RandomChoiceSelection) CaddyModule() caddy.ModuleInfo {
86 return caddy.ModuleInfo{
87 - Name: "http.handlers.reverse_proxy.selection_policies.random_choose",
88 - New: func() caddy.Module { return new(RandomChoiceSelection) },
87 + ID: "http.reverse_proxy.selection_policies.random_choose",
88 + New: func() caddy.Module { return new(RandomChoiceSelection) },
89 }
90 }
91
@@ -154,8 +154,8 @@ type LeastConnSelection struct{}
154 // CaddyModule returns the Caddy module information.
155 func (LeastConnSelection) CaddyModule() caddy.ModuleInfo {
156 return caddy.ModuleInfo{
157 - Name: "http.handlers.reverse_proxy.selection_policies.least_conn",
158 - New: func() caddy.Module { return new(LeastConnSelection) },
157 + ID: "http.reverse_proxy.selection_policies.least_conn",
158 + New: func() caddy.Module { return new(LeastConnSelection) },
159 }
160 }
161
@@ -199,8 +199,8 @@ type RoundRobinSelection struct {
199 // CaddyModule returns the Caddy module information.
200 func (RoundRobinSelection) CaddyModule() caddy.ModuleInfo {
201 return caddy.ModuleInfo{
202 - Name: "http.handlers.reverse_proxy.selection_policies.round_robin",
203 - New: func() caddy.Module { return new(RoundRobinSelection) },
202 + ID: "http.reverse_proxy.selection_policies.round_robin",
203 + New: func() caddy.Module { return new(RoundRobinSelection) },
204 }
205 }
206
@@ -227,8 +227,8 @@ type FirstSelection struct{}
227 // CaddyModule returns the Caddy module information.
228 func (FirstSelection) CaddyModule() caddy.ModuleInfo {
229 return caddy.ModuleInfo{
230 - Name: "http.handlers.reverse_proxy.selection_policies.first",
231 - New: func() caddy.Module { return new(FirstSelection) },
230 + ID: "http.reverse_proxy.selection_policies.first",
231 + New: func() caddy.Module { return new(FirstSelection) },
232 }
233 }
234
@@ -249,8 +249,8 @@ type IPHashSelection struct{}
249 // CaddyModule returns the Caddy module information.
250 func (IPHashSelection) CaddyModule() caddy.ModuleInfo {
251 return caddy.ModuleInfo{
252 - Name: "http.handlers.reverse_proxy.selection_policies.ip_hash",
253 - New: func() caddy.Module { return new(IPHashSelection) },
252 + ID: "http.reverse_proxy.selection_policies.ip_hash",
253 + New: func() caddy.Module { return new(IPHashSelection) },
254 }
255 }
256
@@ -270,8 +270,8 @@ type URIHashSelection struct{}
270 // CaddyModule returns the Caddy module information.
271 func (URIHashSelection) CaddyModule() caddy.ModuleInfo {
272 return caddy.ModuleInfo{
273 - Name: "http.handlers.reverse_proxy.selection_policies.uri_hash",
274 - New: func() caddy.Module { return new(URIHashSelection) },
273 + ID: "http.reverse_proxy.selection_policies.uri_hash",
274 + New: func() caddy.Module { return new(URIHashSelection) },
275 }
276 }
277
@@ -289,8 +289,8 @@ type HeaderHashSelection struct {
289 // CaddyModule returns the Caddy module information.
290 func (HeaderHashSelection) CaddyModule() caddy.ModuleInfo {
291 return caddy.ModuleInfo{
292 - Name: "http.handlers.reverse_proxy.selection_policies.header",
293 - New: func() caddy.Module { return new(HeaderHashSelection) },
292 + ID: "http.reverse_proxy.selection_policies.header",
293 + New: func() caddy.Module { return new(HeaderHashSelection) },
294 }
295 }
296
modules/caddyhttp/rewrite/rewrite.go
+2 -2
@@ -47,8 +47,8 @@ type Rewrite struct {
47 // CaddyModule returns the Caddy module information.
48 func (Rewrite) CaddyModule() caddy.ModuleInfo {
49 return caddy.ModuleInfo{
50 - Name: "http.handlers.rewrite",
51 - New: func() caddy.Module { return new(Rewrite) },
50 + ID: "http.handlers.rewrite",
51 + New: func() caddy.Module { return new(Rewrite) },
52 }
53 }
54
modules/caddyhttp/routes.go
+99 -44
@@ -22,14 +22,73 @@ import (
22 "github.com/caddyserver/caddy/v2"
23 )
24
25 -// Route represents a set of matching rules,
26 -// middlewares, and a responder for handling HTTP
27 -// requests.
25 +// Route consists of a set of rules for matching HTTP requests,
26 +// a list of handlers to execute, and optional flow control
27 +// parameters which customize the handling of HTTP requests
28 +// in a highly flexible and performant manner.
29 type Route struct {
29 - Group string `json:"group,omitempty"`
30 - MatcherSetsRaw RawMatcherSets `json:"match,omitempty"`
31 - HandlersRaw []json.RawMessage `json:"handle,omitempty"`
32 - Terminal bool `json:"terminal,omitempty"`
30 + // Group is an optional name for a group to which this
31 + // route belongs. If a route belongs to a group, only
32 + // the first matching route in the group will be used.
33 + Group string `json:"group,omitempty"`
34 +
35 + // The matcher sets which will be used to qualify this
36 + // route for a request. Essentially the "if" statement
37 + // of this route. Each matcher set is OR'ed, but matchers
38 + // within a set are AND'ed together.
39 + MatcherSetsRaw RawMatcherSets `json:"match,omitempty" caddy:"namespace=http.matchers"`
40 +
41 + // The list of handlers for this route. Upon matching a request, they are chained
42 + // together in a middleware fashion: requests flow from the first handler to the last
43 + // (top of the list to the bottom), with the possibility that any handler could stop
44 + // the chain and/or return an error. Responses flow back through the chain (bottom of
45 + // the list to the top) as they are written out to the client.
46 + //
47 + // Not all handlers call the next handler in the chain. For example, the reverse_proxy
48 + // handler always sends a request upstream or returns an error. Thus, configuring
49 + // handlers after reverse_proxy in the same route is illogical, since they would never
50 + // be executed. You will want to put handlers which originate the response at the very
51 + // end of your route(s). The documentation for a module should state whether it invokes
52 + // the next handler, but sometimes it is common sense.
53 + //
54 + // Some handlers manipulate the response. Remember that requests flow down the list, and
55 + // responses flow up the list.
56 + //
57 + // For example, if you wanted to use both `templates` and `encode` handlers, you would
58 + // need to put `templates` after `encode` in your route, because responses flow up.
59 + // Thus, `templates` will be able to parse and execute the plain-text response as a
60 + // template, and then return it up to the `encode` handler which will then compress it
61 + // into a binary format.
62 + //
63 + // If `templates` came before `encode`, then `encode` would write a compressed,
64 + // binary-encoded response to `templates` which would not be able to parse the response
65 + // properly.
66 + //
67 + // The correct order, then, is this:
68 + //
69 + // [
70 + // {"handler": "encode"},
71 + // {"handler": "templates"},
72 + // {"handler": "file_server"}
73 + // ]
74 + //
75 + // The request flows ⬇️ DOWN (`encode` -> `templates` -> `file_server`).
76 + //
77 + // 1. First, `encode` will choose how to `encode` the response and wrap the response.
78 + // 2. Then, `templates` will wrap the response with a buffer.
79 + // 3. Finally, `file_server` will originate the content from a file.
80 + //
81 + // The response flows ⬆️ UP (`file_server` -> `templates` -> `encode`):
82 + //
83 + // 1. First, `file_server` will write the file to the response.
84 + // 2. That write will be buffered and then executed by `templates`.
85 + // 3. Lastly, the write from `templates` will flow into `encode` which will compress the stream.
86 + //
87 + // If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes.
88 + HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=http.handlers inline_key=handler"`
89 +
90 + // If true, no more routes will be executed after this one, even if they matched.
91 + Terminal bool `json:"terminal,omitempty"`
92
93 // decoded values
94 MatcherSets MatcherSets `json:"-"`
@@ -54,22 +113,23 @@ type RouteList []Route
113 func (routes RouteList) Provision(ctx caddy.Context) error {
114 for i, route := range routes {
115 // matchers
57 - matcherSets, err := route.MatcherSetsRaw.Setup(ctx)
116 + matchersIface, err := ctx.LoadModule(&route, "MatcherSetsRaw")
117 + if err != nil {
118 + return fmt.Errorf("loadng matchers in route %d: %v", i, err)
119 + }
120 + err = routes[i].MatcherSets.FromInterface(matchersIface)
121 if err != nil {
59 - return err
122 + return fmt.Errorf("route %d: %v", i, err)
123 }
61 - routes[i].MatcherSets = matcherSets
62 - routes[i].MatcherSetsRaw = nil // allow GC to deallocate
124
125 // handlers
65 - for j, rawMsg := range route.HandlersRaw {
66 - mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
67 - if err != nil {
68 - return fmt.Errorf("loading handler module in position %d: %v", j, err)
69 - }
70 - routes[i].Handlers = append(routes[i].Handlers, mh.(MiddlewareHandler))
126 + handlersIface, err := ctx.LoadModule(&route, "HandlersRaw")
127 + if err != nil {
128 + return fmt.Errorf("loading handler modules in route %d: %v", i, err)
129 + }
130 + for _, handler := range handlersIface.([]interface{}) {
131 + routes[i].Handlers = append(routes[i].Handlers, handler.(MiddlewareHandler))
132 }
72 - routes[i].HandlersRaw = nil // allow GC to deallocate
133 }
134 return nil
135 }
@@ -171,28 +231,7 @@ func (mset MatcherSet) Match(r *http.Request) bool {
231
232 // RawMatcherSets is a group of matcher sets
233 // in their raw, JSON form.
174 -type RawMatcherSets []map[string]json.RawMessage
175 -
176 -// Setup sets up all matcher sets by loading each matcher module
177 -// and returning the group of provisioned matcher sets.
178 -func (rm RawMatcherSets) Setup(ctx caddy.Context) (MatcherSets, error) {
179 - if rm == nil {
180 - return nil, nil
181 - }
182 - var ms MatcherSets
183 - for _, matcherSet := range rm {
184 - var matchers MatcherSet
185 - for modName, rawMsg := range matcherSet {
186 - val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
187 - if err != nil {
188 - return nil, fmt.Errorf("loading matcher module '%s': %v", modName, err)
189 - }
190 - matchers = append(matchers, val.(RequestMatcher))
191 - }
192 - ms = append(ms, matchers)
193 - }
194 - return ms, nil
195 -}
234 +type RawMatcherSets []caddy.ModuleMap
235
236 // MatcherSets is a group of matcher sets capable
237 // of checking whether a request matches any of
@@ -202,11 +241,27 @@ type MatcherSets []MatcherSet
241 // AnyMatch returns true if req matches any of the
242 // matcher sets in mss or if there are no matchers,
243 // in which case the request always matches.
205 -func (mss MatcherSets) AnyMatch(req *http.Request) bool {
206 - for _, ms := range mss {
207 - if ms.Match(req) {
244 +func (ms MatcherSets) AnyMatch(req *http.Request) bool {
245 + for _, m := range ms {
246 + if m.Match(req) {
247 return true
248 }
249 }
211 - return len(mss) == 0
250 + return len(ms) == 0
251 +}
252 +
253 +// FromInterface fills ms from an interface{} value obtained from LoadModule.
254 +func (ms *MatcherSets) FromInterface(matcherSets interface{}) error {
255 + for _, matcherSetIfaces := range matcherSets.([]map[string]interface{}) {
256 + var matcherSet MatcherSet
257 + for _, matcher := range matcherSetIfaces {
258 + reqMatcher, ok := matcher.(RequestMatcher)
259 + if !ok {
260 + return fmt.Errorf("decoded module is not a RequestMatcher: %#v", matcher)
261 + }
262 + matcherSet = append(matcherSet, reqMatcher)
263 + }
264 + *ms = append(*ms, matcherSet)
265 + }
266 + return nil
267 }
modules/caddyhttp/server.go
+96 -16
@@ -31,23 +31,101 @@ import (
31 "go.uber.org/zap/zapcore"
32 )
33
34 -// Server is an HTTP server.
34 +// Server describes an HTTP server.
35 type Server struct {
36 - Listen []string `json:"listen,omitempty"`
37 - ReadTimeout caddy.Duration `json:"read_timeout,omitempty"`
38 - ReadHeaderTimeout caddy.Duration `json:"read_header_timeout,omitempty"`
39 - WriteTimeout caddy.Duration `json:"write_timeout,omitempty"`
40 - IdleTimeout caddy.Duration `json:"idle_timeout,omitempty"`
41 - MaxHeaderBytes int `json:"max_header_bytes,omitempty"`
42 - Routes RouteList `json:"routes,omitempty"`
43 - Errors *HTTPErrorConfig `json:"errors,omitempty"`
44 - TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"`
45 - AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`
46 - MaxRehandles *int `json:"max_rehandles,omitempty"`
47 - StrictSNIHost *bool `json:"strict_sni_host,omitempty"`
48 - Logs *ServerLogConfig `json:"logs,omitempty"`
49 -
50 - // This field is not subject to compatibility promises
36 + // Socket interfaces to which to bind listeners. Caddy network
37 + // addresses have the following form:
38 + //
39 + // network/address
40 + //
41 + // The network part is anything that [Go's `net` package](https://golang.org/pkg/net/)
42 + // recognizes, and is optional. The default network is `tcp`. If
43 + // a network is specified, a single forward slash `/` is used to
44 + // separate the network and address portions.
45 + //
46 + // The address part may be any of these forms:
47 + //
48 + // - `host`
49 + // - `host:port`
50 + // - `:port`
51 + // - `/path/to/unix/socket`
52 + //
53 + // The host may be any hostname, resolvable domain name, or IP address.
54 + // The port may be a single value (`:8080`) or a range (`:8080-8085`).
55 + // A port range will be multiplied into singular addresses. Not all
56 + // config parameters accept port ranges, but Listen does.
57 + //
58 + // Valid examples:
59 + //
60 + // :8080
61 + // 127.0.0.1:8080
62 + // localhost:8080
63 + // localhost:8080-8085
64 + // tcp/localhost:8080
65 + // tcp/localhost:8080-8085
66 + // udp/localhost:9005
67 + // unix//path/to/socket
68 + //
69 + Listen []string `json:"listen,omitempty"`
70 +
71 + // How long to allow a read from a client's upload. Setting this
72 + // to a short, non-zero value can mitigate slowloris attacks, but
73 + // may also affect legitimately slow clients.
74 + ReadTimeout caddy.Duration `json:"read_timeout,omitempty"`
75 +
76 + // ReadHeaderTimeout is like ReadTimeout but for request headers.
77 + ReadHeaderTimeout caddy.Duration `json:"read_header_timeout,omitempty"`
78 +
79 + // WriteTimeout is how long to allow a write to a client. Note
80 + // that setting this to a small value when serving large files
81 + // may negatively affect legitimately slow clients.
82 + WriteTimeout caddy.Duration `json:"write_timeout,omitempty"`
83 +
84 + // IdleTimeout is the maximum time to wait for the next request
85 + // when keep-alives are enabled. If zero, ReadTimeout is used.
86 + // If both are zero, there is no timeout.
87 + IdleTimeout caddy.Duration `json:"idle_timeout,omitempty"`
88 +
89 + // MaxHeaderBytes is the maximum size to parse from a client's
90 + // HTTP request headers.
91 + MaxHeaderBytes int `json:"max_header_bytes,omitempty"`
92 +
93 + // Routes describes how this server will handle requests.
94 + // When a request comes in, each route's matchers will
95 + // be evaluated against the request, and matching routes
96 + // will be compiled into a middleware chain in the order
97 + // in which they appear in the list.
98 + Routes RouteList `json:"routes,omitempty"`
99 +
100 + // Errors is how this server will handle errors returned from
101 + // any of the handlers in the primary routes.
102 + Errors *HTTPErrorConfig `json:"errors,omitempty"`
103 +
104 + // How to handle TLS connections.
105 + TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"`
106 +
107 + // AutoHTTPS configures or disables automatic HTTPS within this server.
108 + // HTTPS is enabled automatically and by default when qualifying names
109 + // are present in a Host matcher.
110 + AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`
111 +
112 + // MaxRehandles is the maximum number of times to allow a
113 + // request to be rehandled, to prevent accidental infinite
114 + // loops. Default: 1.
115 + MaxRehandles *int `json:"max_rehandles,omitempty"`
116 +
117 + // If true, will require that a request's Host header match
118 + // the value of the ServerName sent by the client's TLS
119 + // ClientHello; often a necessary safeguard when using TLS
120 + // client authentication.
121 + StrictSNIHost *bool `json:"strict_sni_host,omitempty"`
122 +
123 + // Logs customizes how access logs are handled in this server.
124 + Logs *ServerLogConfig `json:"logs,omitempty"`
125 +
126 + // Enable experimental HTTP/3 support. Note that HTTP/3 is not a
127 + // finished standard and has extremely limited client support.
128 + // This field is not subject to compatibility promises.
129 ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"`
130
131 tlsApp *caddytls.TLS
@@ -296,6 +374,8 @@ func (s *Server) hasTLSClientAuth() bool {
374
375 // AutoHTTPSConfig is used to disable automatic HTTPS
376 // or certain aspects of it for a specific server.
377 +// HTTPS is enabled automatically and by default when
378 +// qualifying hostnames are available from the config.
379 type AutoHTTPSConfig struct {
380 // If true, automatic HTTPS will be entirely disabled.
381 Disabled bool `json:"disable,omitempty"`
modules/caddyhttp/starlarkmw/internal/lib/module.go
+2 -2
@@ -64,7 +64,7 @@ func (r *LoadMiddleware) Run(thread *starlark.Thread, fn *starlark.Builtin, args
64 name = fmt.Sprintf("http.handlers.%s", name)
65 }
66
67 - inst, err := r.Ctx.LoadModule(name, js)
67 + inst, err := r.Ctx.LoadModuleByID(name, js)
68 if err != nil {
69 return starlark.None, err
70 }
@@ -112,7 +112,7 @@ func (r *LoadResponder) Run(thread *starlark.Thread, fn *starlark.Builtin, args
112 name = fmt.Sprintf("http.handlers.%s", name)
113 }
114
115 - inst, err := r.Ctx.LoadModule(name, js)
115 + inst, err := r.Ctx.LoadModuleByID(name, js)
116 if err != nil {
117 return starlark.None, err
118 }
modules/caddyhttp/starlarkmw/starlarkmw.go
+3 -3
@@ -7,8 +7,8 @@ import (
7
8 "github.com/caddyserver/caddy/v2"
9 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
10 - caddyscript "github.com/caddyserver/caddy/v2/pkg/caddyscript/lib"
10 "github.com/caddyserver/caddy/v2/modules/caddyhttp/starlarkmw/internal/lib"
11 + caddyscript "github.com/caddyserver/caddy/v2/pkg/caddyscript/lib"
12 "github.com/starlight-go/starlight/convert"
13 "go.starlark.net/starlark"
14 )
@@ -34,8 +34,8 @@ type StarlarkMW struct {
34 // CaddyModule returns the Caddy module information.
35 func (StarlarkMW) CaddyModule() caddy.ModuleInfo {
36 return caddy.ModuleInfo{
37 - Name: "http.handlers.starlark",
38 - New: func() caddy.Module { return new(StarlarkMW) },
37 + ID: "http.handlers.starlark",
38 + New: func() caddy.Module { return new(StarlarkMW) },
39 }
40 }
41
modules/caddyhttp/staticerror.go
+2 -2
@@ -35,8 +35,8 @@ type StaticError struct {
35 // CaddyModule returns the Caddy module information.
36 func (StaticError) CaddyModule() caddy.ModuleInfo {
37 return caddy.ModuleInfo{
38 - Name: "http.handlers.error",
39 - New: func() caddy.Module { return new(StaticError) },
38 + ID: "http.handlers.error",
39 + New: func() caddy.Module { return new(StaticError) },
40 }
41 }
42
modules/caddyhttp/staticresp.go
+2 -2
@@ -38,8 +38,8 @@ type StaticResponse struct {
38 // CaddyModule returns the Caddy module information.
39 func (StaticResponse) CaddyModule() caddy.ModuleInfo {
40 return caddy.ModuleInfo{
41 - Name: "http.handlers.static_response",
42 - New: func() caddy.Module { return new(StaticResponse) },
41 + ID: "http.handlers.static_response",
42 + New: func() caddy.Module { return new(StaticResponse) },
43 }
44 }
45
modules/caddyhttp/subroute.go
+2 -2
@@ -44,8 +44,8 @@ type Subroute struct {
44 // CaddyModule returns the Caddy module information.
45 func (Subroute) CaddyModule() caddy.ModuleInfo {
46 return caddy.ModuleInfo{
47 - Name: "http.handlers.subroute",
48 - New: func() caddy.Module { return new(Subroute) },
47 + ID: "http.handlers.subroute",
48 + New: func() caddy.Module { return new(Subroute) },
49 }
50 }
51
modules/caddyhttp/templates/templates.go
+2 -2
@@ -39,8 +39,8 @@ type Templates struct {
39 // CaddyModule returns the Caddy module information.
40 func (Templates) CaddyModule() caddy.ModuleInfo {
41 return caddy.ModuleInfo{
42 - Name: "http.handlers.templates",
43 - New: func() caddy.Module { return new(Templates) },
42 + ID: "http.handlers.templates",
43 + New: func() caddy.Module { return new(Templates) },
44 }
45 }
46
modules/caddyhttp/vars.go
+4 -4
@@ -32,8 +32,8 @@ type VarsMiddleware map[string]string
32 // CaddyModule returns the Caddy module information.
33 func (VarsMiddleware) CaddyModule() caddy.ModuleInfo {
34 return caddy.ModuleInfo{
35 - Name: "http.handlers.vars",
36 - New: func() caddy.Module { return new(VarsMiddleware) },
35 + ID: "http.handlers.vars",
36 + New: func() caddy.Module { return new(VarsMiddleware) },
37 }
38 }
39
@@ -55,8 +55,8 @@ type VarsMatcher map[string]string
55 // CaddyModule returns the Caddy module information.
56 func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
57 return caddy.ModuleInfo{
58 - Name: "http.matchers.vars",
59 - New: func() caddy.Module { return new(VarsMatcher) },
58 + ID: "http.matchers.vars",
59 + New: func() caddy.Module { return new(VarsMatcher) },
60 }
61 }
62
modules/caddytls/acmemanager.go
+50 -18
@@ -40,16 +40,50 @@ func init() {
40 // after you have configured this struct
41 // to your liking.
42 type ACMEManagerMaker struct {
43 - CA string `json:"ca,omitempty"`
44 - Email string `json:"email,omitempty"`
45 - RenewAhead caddy.Duration `json:"renew_ahead,omitempty"`
46 - KeyType string `json:"key_type,omitempty"`
47 - ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
48 - MustStaple bool `json:"must_staple,omitempty"`
49 - Challenges *ChallengesConfig `json:"challenges,omitempty"`
50 - OnDemand bool `json:"on_demand,omitempty"`
51 - Storage json.RawMessage `json:"storage,omitempty"`
52 - TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"`
43 + // The URL to the CA's ACME directory endpoint.
44 + CA string `json:"ca,omitempty"`
45 +
46 + // Your email address, so the CA can contact you if necessary.
47 + // Not required, but strongly recommended to provide one so
48 + // you can be reached if there is a problem. Your email is
49 + // not sent to any Caddy mothership or used for any purpose
50 + // other than ACME transactions.
51 + Email string `json:"email,omitempty"`
52 +
53 + // How long before a certificate's expiration to try renewing it.
54 + // Should usually be about 1/3 of certificate lifetime, but long
55 + // enough to give yourself time to troubleshoot problems before
56 + // expiration. Default: 30d
57 + RenewAhead caddy.Duration `json:"renew_ahead,omitempty"`
58 +
59 + // The type of key to generate for the certificate.
60 + // Supported values: `rsa2048`, `rsa4096`, `p256`, `p384`.
61 + KeyType string `json:"key_type,omitempty"`
62 +
63 + // Time to wait before timing out an ACME operation.
64 + ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
65 +
66 + // If true, certificates will be requested with MustStaple. Not all
67 + // CAs support this, and there are potentially serious consequences
68 + // of enabling this feature without proper threat modeling.
69 + MustStaple bool `json:"must_staple,omitempty"`
70 +
71 + // Configures the various ACME challenge types.
72 + Challenges *ChallengesConfig `json:"challenges,omitempty"`
73 +
74 + // If true, certificates will be managed "on demand", that is, during
75 + // TLS handshakes or when needed, as opposed to at startup or config
76 + // load.
77 + OnDemand bool `json:"on_demand,omitempty"`
78 +
79 + // Optionally configure a separate storage module associated with this
80 + // manager, instead of using Caddy's global/default-configured storage.
81 + Storage json.RawMessage `json:"storage,omitempty"`
82 +
83 + // An array of files of CA certificates to accept when connecting to the
84 + // ACME CA. Generally, you should only use this if the ACME CA endpoint
85 + // is internal or for development/testing purposes.
86 + TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"`
87
88 storage certmagic.Storage
89 rootPool *x509.CertPool
@@ -58,8 +92,8 @@ type ACMEManagerMaker struct {
92 // CaddyModule returns the Caddy module information.
93 func (ACMEManagerMaker) CaddyModule() caddy.ModuleInfo {
94 return caddy.ModuleInfo{
61 - Name: "tls.management.acme",
62 - New: func() caddy.Module { return new(ACMEManagerMaker) },
95 + ID: "tls.management.acme",
96 + New: func() caddy.Module { return new(ACMEManagerMaker) },
97 }
98 }
99
@@ -73,26 +107,24 @@ func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error
107 func (m *ACMEManagerMaker) Provision(ctx caddy.Context) error {
108 // DNS providers
109 if m.Challenges != nil && m.Challenges.DNSRaw != nil {
76 - val, err := ctx.LoadModuleInline("provider", "tls.dns", m.Challenges.DNSRaw)
110 + val, err := ctx.LoadModule(m.Challenges, "DNSRaw")
111 if err != nil {
78 - return fmt.Errorf("loading DNS provider module: %s", err)
112 + return fmt.Errorf("loading DNS provider module: %v", err)
113 }
114 m.Challenges.DNS = val.(challenge.Provider)
81 - m.Challenges.DNSRaw = nil // allow GC to deallocate
115 }
116
117 // policy-specific storage implementation
118 if m.Storage != nil {
86 - val, err := ctx.LoadModuleInline("module", "caddy.storage", m.Storage)
119 + val, err := ctx.LoadModule(m, "Storage")
120 if err != nil {
88 - return fmt.Errorf("loading TLS storage module: %s", err)
121 + return fmt.Errorf("loading TLS storage module: %v", err)
122 }
123 cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
124 if err != nil {
125 return fmt.Errorf("creating TLS storage configuration: %v", err)
126 }
127 m.storage = cmStorage
95 - m.Storage = nil // allow GC to deallocate
128 }
129
130 // add any custom CAs to trust store
modules/caddytls/certselection.go
+2 -2
@@ -28,8 +28,8 @@ type Policy struct {
28 // CaddyModule returns the Caddy module information.
29 func (Policy) CaddyModule() caddy.ModuleInfo {
30 return caddy.ModuleInfo{
31 - Name: "tls.certificate_selection.custom",
32 - New: func() caddy.Module { return new(Policy) },
31 + ID: "tls.certificate_selection.custom",
32 + New: func() caddy.Module { return new(Policy) },
33 }
34 }
35
modules/caddytls/connpolicy.go
+34 -17
@@ -39,23 +39,21 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) {
39 // set up each of the connection policies
40 for i, pol := range cp {
41 // matchers
42 - for modName, rawMsg := range pol.Matchers {
43 - val, err := ctx.LoadModule("tls.handshake_match."+modName, rawMsg)
44 - if err != nil {
45 - return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err)
46 - }
47 - cp[i].matchers = append(cp[i].matchers, val.(ConnectionMatcher))
42 + mods, err := ctx.LoadModule(pol, "MatchersRaw")
43 + if err != nil {
44 + return nil, fmt.Errorf("loading handshake matchers: %v", err)
45 + }
46 + for _, modIface := range mods.(map[string]interface{}) {
47 + cp[i].matchers = append(cp[i].matchers, modIface.(ConnectionMatcher))
48 }
49 - cp[i].Matchers = nil // allow GC to deallocate
49
50 // certificate selector
51 if pol.CertSelection != nil {
53 - val, err := ctx.LoadModuleInline("policy", "tls.certificate_selection", pol.CertSelection)
52 + val, err := ctx.LoadModule(pol, "CertSelection")
53 if err != nil {
54 return nil, fmt.Errorf("loading certificate selection module: %s", err)
55 }
56 cp[i].certSelector = val.(certmagic.CertificateSelector)
58 - cp[i].CertSelection = nil // allow GC to deallocate
57 }
58 }
59
@@ -109,14 +107,33 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) {
107
108 // ConnectionPolicy specifies the logic for handling a TLS handshake.
109 type ConnectionPolicy struct {
112 - Matchers map[string]json.RawMessage `json:"match,omitempty"`
113 - CertSelection json.RawMessage `json:"certificate_selection,omitempty"`
114 -
115 - CipherSuites []string `json:"cipher_suites,omitempty"`
116 - Curves []string `json:"curves,omitempty"`
117 - ALPN []string `json:"alpn,omitempty"`
118 - ProtocolMin string `json:"protocol_min,omitempty"`
119 - ProtocolMax string `json:"protocol_max,omitempty"`
110 + // How to match this policy with a TLS ClientHello. If
111 + // this policy is the first to match, it will be used.
112 + MatchersRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=tls.handshake_match"`
113 +
114 + // How to choose a certificate if more than one matched
115 + // the given ServerName (SNI) value.
116 + CertSelection json.RawMessage `json:"certificate_selection,omitempty" caddy:"namespace=tls.certificate_selection inline_key=policy"`
117 +
118 + // The list of cipher suites to support. Caddy's
119 + // defaults are modern and secure.
120 + CipherSuites []string `json:"cipher_suites,omitempty"`
121 +
122 + // The list of elliptic curves to support. Caddy's
123 + // defaults are modern and secure.
124 + Curves []string `json:"curves,omitempty"`
125 +
126 + // Protocols to use for Application-Layer Protocol
127 + // Negotiation (ALPN) during the handshake.
128 + ALPN []string `json:"alpn,omitempty"`
129 +
130 + // Minimum TLS protocol version to allow. Default: `tls1.2`
131 + ProtocolMin string `json:"protocol_min,omitempty"`
132 +
133 + // Maximum TLS protocol version to allow. Default: `tls1.3`
134 + ProtocolMax string `json:"protocol_max,omitempty"`
135 +
136 + // Enables and configures TLS client authentication.
137 ClientAuthentication *ClientAuthentication `json:"client_authentication,omitempty"`
138
139 matchers []ConnectionMatcher
modules/caddytls/distributedstek/distributedstek.go
+11 -6
@@ -39,9 +39,15 @@ func init() {
39 caddy.RegisterModule(Provider{})
40 }
41
42 -// Provider implements a distributed STEK provider.
42 +// Provider implements a distributed STEK provider. This
43 +// module will obtain STEKs from a storage module instead
44 +// of generating STEKs internally. This allows STEKs to be
45 +// coordinated, improving TLS session resumption in a cluster.
46 type Provider struct {
44 - Storage json.RawMessage `json:"storage,omitempty"`
47 + // The storage module wherein to store and obtain session
48 + // ticket keys. If unset, Caddy's default/global-configured
49 + // storage module will be used.
50 + Storage json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
51
52 storage certmagic.Storage
53 stekConfig *caddytls.SessionTicketService
@@ -51,8 +57,8 @@ type Provider struct {
57 // CaddyModule returns the Caddy module information.
58 func (Provider) CaddyModule() caddy.ModuleInfo {
59 return caddy.ModuleInfo{
54 - Name: "tls.stek.distributed",
55 - New: func() caddy.Module { return new(Provider) },
60 + ID: "tls.stek.distributed",
61 + New: func() caddy.Module { return new(Provider) },
62 }
63 }
64
@@ -60,7 +66,7 @@ func (Provider) CaddyModule() caddy.ModuleInfo {
66 func (s *Provider) Provision(ctx caddy.Context) error {
67 // unpack the storage module to use, if different from the default
68 if s.Storage != nil {
63 - val, err := ctx.LoadModuleInline("module", "caddy.storage", s.Storage)
69 + val, err := ctx.LoadModule(s, "Storage")
70 if err != nil {
71 return fmt.Errorf("loading TLS storage module: %s", err)
72 }
@@ -69,7 +75,6 @@ func (s *Provider) Provision(ctx caddy.Context) error {
75 return fmt.Errorf("creating TLS storage configuration: %v", err)
76 }
77 s.storage = cmStorage
72 - s.Storage = nil // allow GC to deallocate
78 }
79
80 // otherwise, use default storage
modules/caddytls/fileloader.go
+15 -6
@@ -32,18 +32,27 @@ type FileLoader []CertKeyFilePair
32 // CaddyModule returns the Caddy module information.
33 func (FileLoader) CaddyModule() caddy.ModuleInfo {
34 return caddy.ModuleInfo{
35 - Name: "tls.certificates.load_files",
36 - New: func() caddy.Module { return new(FileLoader) },
35 + ID: "tls.certificates.load_files",
36 + New: func() caddy.Module { return new(FileLoader) },
37 }
38 }
39
40 // CertKeyFilePair pairs certificate and key file names along with their
41 // encoding format so that they can be loaded from disk.
42 type CertKeyFilePair struct {
43 - Certificate string `json:"certificate"`
44 - Key string `json:"key"`
45 - Format string `json:"format,omitempty"` // "pem" is default
46 - Tags []string `json:"tags,omitempty"`
43 + // Path to the certificate (public key) file.
44 + Certificate string `json:"certificate"`
45 +
46 + // Path to the private key file.
47 + Key string `json:"key"`
48 +
49 + // The format of the cert and key. Can be "pem". Default: "pem"
50 + Format string `json:"format,omitempty"`
51 +
52 + // Arbitrary values to associate with this certificate.
53 + // Can be useful when you want to select a particular
54 + // certificate when there may be multiple valid candidates.
55 + Tags []string `json:"tags,omitempty"`
56 }
57
58 // LoadCertificates returns the certificates to be loaded by fl.
modules/caddytls/folderloader.go
+2 -2
@@ -39,8 +39,8 @@ type FolderLoader []string
39 // CaddyModule returns the Caddy module information.
40 func (FolderLoader) CaddyModule() caddy.ModuleInfo {
41 return caddy.ModuleInfo{
42 - Name: "tls.certificates.load_folders",
43 - New: func() caddy.Module { return new(FolderLoader) },
42 + ID: "tls.certificates.load_folders",
43 + New: func() caddy.Module { return new(FolderLoader) },
44 }
45 }
46
modules/caddytls/matchers.go
+2 -2
@@ -30,8 +30,8 @@ type MatchServerName []string
30 // CaddyModule returns the Caddy module information.
31 func (MatchServerName) CaddyModule() caddy.ModuleInfo {
32 return caddy.ModuleInfo{
33 - Name: "tls.handshake_match.sni",
34 - New: func() caddy.Module { return new(MatchServerName) },
33 + ID: "tls.handshake_match.sni",
34 + New: func() caddy.Module { return new(MatchServerName) },
35 }
36 }
37
modules/caddytls/pemloader.go
+12 -5
@@ -33,16 +33,23 @@ type PEMLoader []CertKeyPEMPair
33 // CaddyModule returns the Caddy module information.
34 func (PEMLoader) CaddyModule() caddy.ModuleInfo {
35 return caddy.ModuleInfo{
36 - Name: "tls.certificates.load_pem",
37 - New: func() caddy.Module { return PEMLoader{} },
36 + ID: "tls.certificates.load_pem",
37 + New: func() caddy.Module { return PEMLoader{} },
38 }
39 }
40
41 // CertKeyPEMPair pairs certificate and key PEM blocks.
42 type CertKeyPEMPair struct {
43 - CertificatePEM string `json:"certificate"`
44 - KeyPEM string `json:"key"`
45 - Tags []string `json:"tags,omitempty"`
43 + // The certificate (public key) in PEM format.
44 + CertificatePEM string `json:"certificate"`
45 +
46 + // The private key in PEM format.
47 + KeyPEM string `json:"key"`
48 +
49 + // Arbitrary values to associate with this certificate.
50 + // Can be useful when you want to select a particular
51 + // certificate when there may be multiple valid candidates.
52 + Tags []string `json:"tags,omitempty"`
53 }
54
55 // LoadCertificates returns the certificates contained in pl.
modules/caddytls/sessiontickets.go
+17 -7
@@ -28,11 +28,22 @@ import (
28
29 // SessionTicketService configures and manages TLS session tickets.
30 type SessionTicketService struct {
31 - KeySource json.RawMessage `json:"key_source,omitempty"`
32 - RotationInterval caddy.Duration `json:"rotation_interval,omitempty"`
33 - MaxKeys int `json:"max_keys,omitempty"`
34 - DisableRotation bool `json:"disable_rotation,omitempty"`
35 - Disabled bool `json:"disabled,omitempty"`
31 + // KeySource is the method by which Caddy produces or obtains
32 + // TLS session ticket keys (STEKs). By default, Caddy generates
33 + // them internally using a secure pseudorandom source.
34 + KeySource json.RawMessage `json:"key_source,omitempty" caddy:"namespace=tls.stek inline_key=provider"`
35 +
36 + // How often Caddy rotates STEKs. Default: 12h.
37 + RotationInterval caddy.Duration `json:"rotation_interval,omitempty"`
38 +
39 + // The maximum number of keys to keep in rotation. Default: 4.
40 + MaxKeys int `json:"max_keys,omitempty"`
41 +
42 + // Disables STEK rotation.
43 + DisableRotation bool `json:"disable_rotation,omitempty"`
44 +
45 + // Disables TLS session resumption by tickets.
46 + Disabled bool `json:"disabled,omitempty"`
47
48 keySource STEKProvider
49 configs map[*tls.Config]struct{}
@@ -57,12 +68,11 @@ func (s *SessionTicketService) provision(ctx caddy.Context) error {
68 }
69
70 // load the STEK module, which will provide keys
60 - val, err := ctx.LoadModuleInline("provider", "tls.stek", s.KeySource)
71 + val, err := ctx.LoadModule(s, "KeySource")
72 if err != nil {
73 return fmt.Errorf("loading TLS session ticket ephemeral keys provider module: %s", err)
74 }
75 s.keySource = val.(STEKProvider)
65 - s.KeySource = nil // allow GC to deallocate
76
77 // if session tickets or just rotation are
78 // disabled, no need to start service
modules/caddytls/standardstek/stek.go
+2 -2
@@ -35,8 +35,8 @@ type standardSTEKProvider struct {
35 // CaddyModule returns the Caddy module information.
36 func (standardSTEKProvider) CaddyModule() caddy.ModuleInfo {
37 return caddy.ModuleInfo{
38 - Name: "tls.stek.standard",
39 - New: func() caddy.Module { return new(standardSTEKProvider) },
38 + ID: "tls.stek.standard",
39 + New: func() caddy.Module { return new(standardSTEKProvider) },
40 }
41 }
42
modules/caddytls/tls.go
+155 -46
@@ -30,15 +30,30 @@ import (
30
31 func init() {
32 caddy.RegisterModule(TLS{})
33 + caddy.RegisterModule(AutomateLoader{})
34 }
35
35 -// TLS represents a process-wide TLS configuration.
36 +// TLS provides TLS facilities including certificate
37 +// loading and management, client auth, and more.
38 type TLS struct {
37 - Certificates map[string]json.RawMessage `json:"certificates,omitempty"`
38 - Automation *AutomationConfig `json:"automation,omitempty"`
39 - SessionTickets *SessionTicketService `json:"session_tickets,omitempty"`
39 + // Caches certificates in memory for quick use during
40 + // TLS handshakes. Each key is the name of a certificate
41 + // loader module. All loaded certificates get pooled
42 + // into the same cache and may be used to complete TLS
43 + // handshakes for the relevant server names (SNI).
44 + // Certificates loaded manually (anything other than
45 + // "automate") are not automatically managed and will
46 + // have to be refreshed manually before they expire.
47 + CertificatesRaw caddy.ModuleMap `json:"certificates,omitempty" caddy:"namespace=tls.certificates"`
48 +
49 + // Configures the automation of certificate management.
50 + Automation *AutomationConfig `json:"automation,omitempty"`
51 +
52 + // Configures session ticket ephemeral keys (STEKs).
53 + SessionTickets *SessionTicketService `json:"session_tickets,omitempty"`
54
55 certificateLoaders []CertificateLoader
56 + automateNames []string
57 certCache *certmagic.Cache
58 ctx caddy.Context
59 storageCleanTicker *time.Ticker
@@ -49,8 +64,8 @@ type TLS struct {
64 // CaddyModule returns the Caddy module information.
65 func (TLS) CaddyModule() caddy.ModuleInfo {
66 return caddy.ModuleInfo{
52 - Name: "tls",
53 - New: func() caddy.Module { return new(TLS) },
67 + ID: "tls",
68 + New: func() caddy.Module { return new(TLS) },
69 }
70 }
71
@@ -74,25 +89,32 @@ func (t *TLS) Provision(ctx caddy.Context) error {
89 // automation/management policies
90 if t.Automation != nil {
91 for i, ap := range t.Automation.Policies {
77 - val, err := ctx.LoadModuleInline("module", "tls.management", ap.ManagementRaw)
92 + val, err := ctx.LoadModule(&ap, "ManagementRaw")
93 if err != nil {
94 return fmt.Errorf("loading TLS automation management module: %s", err)
95 }
96 t.Automation.Policies[i].Management = val.(ManagerMaker)
82 - t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate
97 }
98 }
99
100 // certificate loaders
87 - for modName, rawMsg := range t.Certificates {
88 - if modName == automateKey {
89 - continue // special case; these will be loaded in later
90 - }
91 - val, err := ctx.LoadModule("tls.certificates."+modName, rawMsg)
92 - if err != nil {
93 - return fmt.Errorf("loading certificate module '%s': %s", modName, err)
101 + val, err := ctx.LoadModule(t, "CertificatesRaw")
102 + if err != nil {
103 + return fmt.Errorf("loading TLS automation management module: %s", err)
104 + }
105 + for modName, modIface := range val.(map[string]interface{}) {
106 + if modName == "automate" {
107 + // special case; these will be loaded in later
108 + // using our automation facilities, which we
109 + // want to avoid during provisioning
110 + var ok bool
111 + t.automateNames, ok = modIface.([]string)
112 + if !ok {
113 + return fmt.Errorf("loading certificates with 'automate' requires []string, got: %#v", modIface)
114 + }
115 + continue
116 }
95 - t.certificateLoaders = append(t.certificateLoaders, val.(CertificateLoader))
117 + t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader))
118 }
119
120 // session ticket ephemeral keys (STEK) service and provider
@@ -115,7 +137,8 @@ func (t *TLS) Provision(ctx caddy.Context) error {
137
138 // load manual/static (unmanaged) certificates - we do this in
139 // provision so that other apps (such as http) can know which
118 - // certificates have been manually loaded
140 + // certificates have been manually loaded, and also so that
141 + // commands like validate can be a better test
142 magic := certmagic.New(t.certCache, certmagic.Config{
143 Storage: ctx.Storage(),
144 })
@@ -137,19 +160,12 @@ func (t *TLS) Provision(ctx caddy.Context) error {
160
161 // Start activates the TLS module.
162 func (t *TLS) Start() error {
140 - // load automated (managed) certificates
141 - if automatedRawMsg, ok := t.Certificates[automateKey]; ok {
142 - var names []string
143 - err := json.Unmarshal(automatedRawMsg, &names)
144 - if err != nil {
145 - return fmt.Errorf("automate: decoding names: %v", err)
146 - }
147 - err = t.Manage(names)
148 - if err != nil {
149 - return fmt.Errorf("automate: managing %v: %v", names, err)
150 - }
163 + // now that we are running, and all manual certificates have
164 + // been loaded, time to load the automated/managed certificates
165 + err := t.Manage(t.automateNames)
166 + if err != nil {
167 + return fmt.Errorf("automate: managing %v: %v", t.automateNames, err)
168 }
152 - t.Certificates = nil // allow GC to deallocate
169
170 t.keepStorageClean()
171
@@ -311,18 +327,48 @@ type Certificate struct {
327 // AutomationConfig designates configuration for the
328 // construction and use of ACME clients.
329 type AutomationConfig struct {
314 - Policies []AutomationPolicy `json:"policies,omitempty"`
315 - OnDemand *OnDemandConfig `json:"on_demand,omitempty"`
316 - OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"`
317 - RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"`
330 + // The list of automation policies. The first matching
331 + // policy will be applied for a given certificate/name.
332 + Policies []AutomationPolicy `json:"policies,omitempty"`
333 +
334 + // On-Demand TLS defers certificate operations to the
335 + // moment they are needed, e.g. during a TLS handshake.
336 + // Useful when you don't know all the hostnames up front.
337 + // Caddy was the first web server to deploy this technology.
338 + OnDemand *OnDemandConfig `json:"on_demand,omitempty"`
339 +
340 + // Caddy staples OCSP (and caches the response) for all
341 + // qualifying certificates by default. This setting
342 + // changes how often it scans responses for freshness,
343 + // and updates them if they are getting stale.
344 + OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"`
345 +
346 + // Every so often, Caddy will scan all loaded, managed
347 + // certificates for expiration. Certificates which are
348 + // about 2/3 into their valid lifetime are due for
349 + // renewal. This setting changes how frequently the scan
350 + // is performed. If your certificate lifetimes are very
351 + // short (less than ~1 week), you should customize this.
352 + RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"`
353 }
354
355 // AutomationPolicy designates the policy for automating the
321 -// management of managed TLS certificates.
356 +// management (obtaining, renewal, and revocation) of managed
357 +// TLS certificates.
358 type AutomationPolicy struct {
323 - Hosts []string `json:"hosts,omitempty"`
324 - ManagementRaw json.RawMessage `json:"management,omitempty"`
325 - ManageSync bool `json:"manage_sync,omitempty"`
359 + // Which hostnames this policy applies to.
360 + Hosts []string `json:"hosts,omitempty"`
361 +
362 + // How to manage certificates.
363 + ManagementRaw json.RawMessage `json:"management,omitempty" caddy:"namespace=tls.management inline_key=module"`
364 +
365 + // If true, certificate management will be conducted
366 + // in the foreground; this will block config reloads
367 + // and return errors if there were problems with
368 + // obtaining or renewing certificates. This is often
369 + // not desirable, especially when serving sites out
370 + // of your control. Default: false
371 + ManageSync bool `json:"manage_sync,omitempty"`
372
373 Management ManagerMaker `json:"-"`
374 }
@@ -345,36 +391,84 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf
391
392 // ChallengesConfig configures the ACME challenges.
393 type ChallengesConfig struct {
348 - HTTP *HTTPChallengeConfig `json:"http,omitempty"`
394 + // HTTP configures the ACME HTTP challenge. This
395 + // challenge is enabled and used automatically
396 + // and by default.
397 + HTTP *HTTPChallengeConfig `json:"http,omitempty"`
398 +
399 + // TLSALPN configures the ACME TLS-ALPN challenge.
400 + // This challenge is enabled and used automatically
401 + // and by default.
402 TLSALPN *TLSALPNChallengeConfig `json:"tls-alpn,omitempty"`
350 - DNSRaw json.RawMessage `json:"dns,omitempty"`
403 +
404 + // Configures the ACME DNS challenge. Because this
405 + // challenge typically requires credentials for
406 + // interfacing with a DNS provider, this challenge is
407 + // not enabled by default. This is the only challenge
408 + // type which does not require a direct connection
409 + // to Caddy from an external server.
410 + DNSRaw json.RawMessage `json:"dns,omitempty" caddy:"namespace=tls.dns inline_key=provider"`
411
412 DNS challenge.Provider `json:"-"`
413 }
414
415 // HTTPChallengeConfig configures the ACME HTTP challenge.
416 type HTTPChallengeConfig struct {
357 - Disabled bool `json:"disabled,omitempty"`
358 - AlternatePort int `json:"alternate_port,omitempty"`
417 + // If true, the HTTP challenge will be disabled.
418 + Disabled bool `json:"disabled,omitempty"`
419 +
420 + // An alternate port on which to service this
421 + // challenge. Note that the HTTP challenge port is
422 + // hard-coded into the spec and cannot be changed,
423 + // so you would have to forward packets from the
424 + // standard HTTP challenge port to this one.
425 + AlternatePort int `json:"alternate_port,omitempty"`
426 }
427
428 // TLSALPNChallengeConfig configures the ACME TLS-ALPN challenge.
429 type TLSALPNChallengeConfig struct {
363 - Disabled bool `json:"disabled,omitempty"`
364 - AlternatePort int `json:"alternate_port,omitempty"`
430 + // If true, the TLS-ALPN challenge will be disabled.
431 + Disabled bool `json:"disabled,omitempty"`
432 +
433 + // An alternate port on which to service this
434 + // challenge. Note that the TLS-ALPN challenge port
435 + // is hard-coded into the spec and cannot be changed,
436 + // so you would have to forward packets from the
437 + // standard TLS-ALPN challenge port to this one.
438 + AlternatePort int `json:"alternate_port,omitempty"`
439 }
440
441 // OnDemandConfig configures on-demand TLS, for obtaining
368 -// needed certificates at handshake-time.
442 +// needed certificates at handshake-time. Because this
443 +// feature can easily be abused, you should set up rate
444 +// limits and/or an internal endpoint that Caddy can
445 +// "ask" if it should be allowed to manage certificates
446 +// for a given hostname.
447 type OnDemandConfig struct {
448 + // An optional rate limit to throttle the
449 + // issuance of certificates from handshakes.
450 RateLimit *RateLimit `json:"rate_limit,omitempty"`
371 - Ask string `json:"ask,omitempty"`
451 +
452 + // If Caddy needs to obtain or renew a certificate
453 + // during a TLS handshake, it will perform a quick
454 + // HTTP request to this URL to check if it should be
455 + // allowed to try to get a certificate for the name
456 + // in the "domain" query string parameter, like so:
457 + // `?domain=example.com`. The endpoint must return a
458 + // 200 OK status if a certificate is allowed;
459 + // anything else will cause it to be denied.
460 + // Redirects are not followed.
461 + Ask string `json:"ask,omitempty"`
462 }
463
464 // RateLimit specifies an interval with optional burst size.
465 type RateLimit struct {
466 + // A duration value. A certificate may be obtained 'burst'
467 + // times during this interval.
468 Interval caddy.Duration `json:"interval,omitempty"`
377 - Burst int `json:"burst,omitempty"`
469 +
470 + // How many times during an interval a certificate can be obtained.
471 + Burst int `json:"burst,omitempty"`
472 }
473
474 // ManagerMaker makes a certificate manager.
@@ -382,6 +476,21 @@ type ManagerMaker interface {
476 NewManager(interactive bool) (certmagic.Manager, error)
477 }
478
479 +// AutomateLoader is a no-op certificate loader module
480 +// that is treated as a special case: it uses this app's
481 +// automation features to load certificates for the
482 +// list of hostnames, rather than loading certificates
483 +// manually.
484 +type AutomateLoader []string
485 +
486 +// CaddyModule returns the Caddy module information.
487 +func (AutomateLoader) CaddyModule() caddy.ModuleInfo {
488 + return caddy.ModuleInfo{
489 + ID: "tls.certificates.automate",
490 + New: func() caddy.Module { return new(AutomateLoader) },
491 + }
492 +}
493 +
494 // These perpetual values are used for on-demand TLS.
495 var (
496 onDemandRateLimiter = certmagic.NewRateLimiter(0, 0)
modules/filestorage/filestorage.go
+2 -2
@@ -32,8 +32,8 @@ type FileStorage struct {
32 // CaddyModule returns the Caddy module information.
33 func (FileStorage) CaddyModule() caddy.ModuleInfo {
34 return caddy.ModuleInfo{
35 - Name: "caddy.storage.file_system",
36 - New: func() caddy.Module { return new(FileStorage) },
35 + ID: "caddy.storage.file_system",
36 + New: func() caddy.Module { return new(FileStorage) },
37 }
38 }
39
modules/logging/encoders.go
+10 -11
@@ -43,8 +43,8 @@ type ConsoleEncoder struct {
43 // CaddyModule returns the Caddy module information.
44 func (ConsoleEncoder) CaddyModule() caddy.ModuleInfo {
45 return caddy.ModuleInfo{
46 - Name: "caddy.logging.encoders.console",
47 - New: func() caddy.Module { return new(ConsoleEncoder) },
46 + ID: "caddy.logging.encoders.console",
47 + New: func() caddy.Module { return new(ConsoleEncoder) },
48 }
49 }
50
@@ -63,8 +63,8 @@ type JSONEncoder struct {
63 // CaddyModule returns the Caddy module information.
64 func (JSONEncoder) CaddyModule() caddy.ModuleInfo {
65 return caddy.ModuleInfo{
66 - Name: "caddy.logging.encoders.json",
67 - New: func() caddy.Module { return new(JSONEncoder) },
66 + ID: "caddy.logging.encoders.json",
67 + New: func() caddy.Module { return new(JSONEncoder) },
68 }
69 }
70
@@ -84,8 +84,8 @@ type LogfmtEncoder struct {
84 // CaddyModule returns the Caddy module information.
85 func (LogfmtEncoder) CaddyModule() caddy.ModuleInfo {
86 return caddy.ModuleInfo{
87 - Name: "caddy.logging.encoders.logfmt",
88 - New: func() caddy.Module { return new(LogfmtEncoder) },
87 + ID: "caddy.logging.encoders.logfmt",
88 + New: func() caddy.Module { return new(LogfmtEncoder) },
89 }
90 }
91
@@ -102,25 +102,24 @@ func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error {
102 type StringEncoder struct {
103 zapcore.Encoder
104 FieldName string `json:"field,omitempty"`
105 - FallbackRaw json.RawMessage `json:"fallback,omitempty"`
105 + FallbackRaw json.RawMessage `json:"fallback,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
106 }
107
108 // CaddyModule returns the Caddy module information.
109 func (StringEncoder) CaddyModule() caddy.ModuleInfo {
110 return caddy.ModuleInfo{
111 - Name: "caddy.logging.encoders.string",
112 - New: func() caddy.Module { return new(StringEncoder) },
111 + ID: "caddy.logging.encoders.string",
112 + New: func() caddy.Module { return new(StringEncoder) },
113 }
114 }
115
116 // Provision sets up the encoder.
117 func (se *StringEncoder) Provision(ctx caddy.Context) error {
118 if se.FallbackRaw != nil {
119 - val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", se.FallbackRaw)
119 + val, err := ctx.LoadModule(se, "FallbackRaw")
120 if err != nil {
121 return fmt.Errorf("loading fallback encoder module: %v", err)
122 }
123 - se.FallbackRaw = nil // allow GC to deallocate
123 se.Encoder = val.(zapcore.Encoder)
124 }
125 if se.Encoder == nil {
modules/logging/filewriter.go
+30 -10
@@ -28,22 +28,42 @@ func init() {
28 caddy.RegisterModule(FileWriter{})
29 }
30
31 -// FileWriter can write logs to files.
31 +// FileWriter can write logs to files. By default, log files
32 +// are rotated ("rolled") when they get large, and old log
33 +// files get deleted, to ensure that the process does not
34 +// exhaust disk space.
35 type FileWriter struct {
33 - Filename string `json:"filename,omitempty"`
34 - Roll *bool `json:"roll,omitempty"`
35 - RollSizeMB int `json:"roll_size_mb,omitempty"`
36 - RollCompress *bool `json:"roll_gzip,omitempty"`
37 - RollLocalTime bool `json:"roll_local_time,omitempty"`
38 - RollKeep int `json:"roll_keep,omitempty"`
39 - RollKeepDays int `json:"roll_keep_days,omitempty"`
36 + // Filename is the name of the file to write.
37 + Filename string `json:"filename,omitempty"`
38 +
39 + // Roll toggles log rolling or rotation, which is
40 + // enabled by default.
41 + Roll *bool `json:"roll,omitempty"`
42 +
43 + // When a log file reaches approximately this size,
44 + // it will be rotated.
45 + RollSizeMB int `json:"roll_size_mb,omitempty"`
46 +
47 + // Whether to compress rolled files. Default: true
48 + RollCompress *bool `json:"roll_gzip,omitempty"`
49 +
50 + // Whether to use local timestamps in rolled filenames.
51 + // Default: false
52 + RollLocalTime bool `json:"roll_local_time,omitempty"`
53 +
54 + // The maximum number of rolled log files to keep.
55 + // Default: 10
56 + RollKeep int `json:"roll_keep,omitempty"`
57 +
58 + // How many days to keep rolled log files. Default: 90
59 + RollKeepDays int `json:"roll_keep_days,omitempty"`
60 }
61
62 // CaddyModule returns the Caddy module information.
63 func (FileWriter) CaddyModule() caddy.ModuleInfo {
64 return caddy.ModuleInfo{
45 - Name: "caddy.logging.writers.file",
46 - New: func() caddy.Module { return new(FileWriter) },
65 + ID: "caddy.logging.writers.file",
66 + New: func() caddy.Module { return new(FileWriter) },
67 }
68 }
69
modules/logging/filterencoder.go
+20 -21
@@ -29,13 +29,17 @@ func init() {
29 caddy.RegisterModule(FilterEncoder{})
30 }
31
32 -// FilterEncoder wraps an underlying encoder. It does
33 -// not do any encoding itself, but it can manipulate
34 -// (filter) fields before they are actually encoded.
35 -// A wrapped encoder is required.
32 +// FilterEncoder can filter (manipulate) fields on
33 +// log entries before they are actually encoded by
34 +// an underlying encoder.
35 type FilterEncoder struct {
37 - WrappedRaw json.RawMessage `json:"wrap,omitempty"`
38 - FieldsRaw map[string]json.RawMessage `json:"fields,omitempty"`
36 + // The underlying encoder that actually
37 + // encodes the log entries. Required.
38 + WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
39 +
40 + // A map of field names to their filters. Note that this
41 + // is not a module map; the keys are field names.
42 + FieldsRaw map[string]json.RawMessage `json:"fields,omitempty" caddy:"namespace=caddy.logging.encoders.filter inline_key=filter"`
43
44 wrapped zapcore.Encoder
45 Fields map[string]LogFieldFilter `json:"-"`
@@ -47,8 +51,8 @@ type FilterEncoder struct {
51 // CaddyModule returns the Caddy module information.
52 func (FilterEncoder) CaddyModule() caddy.ModuleInfo {
53 return caddy.ModuleInfo{
50 - Name: "caddy.logging.encoders.filter",
51 - New: func() caddy.Module { return new(FilterEncoder) },
54 + ID: "caddy.logging.encoders.filter",
55 + New: func() caddy.Module { return new(FilterEncoder) },
56 }
57 }
58
@@ -59,28 +63,23 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
63 }
64
65 // set up wrapped encoder (required)
62 - val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", fe.WrappedRaw)
66 + val, err := ctx.LoadModule(fe, "WrappedRaw")
67 if err != nil {
68 return fmt.Errorf("loading fallback encoder module: %v", err)
69 }
66 - fe.WrappedRaw = nil // allow GC to deallocate
70 fe.wrapped = val.(zapcore.Encoder)
71
72 // set up each field filter
73 if fe.Fields == nil {
74 fe.Fields = make(map[string]LogFieldFilter)
75 }
73 - for field, filterRaw := range fe.FieldsRaw {
74 - if filterRaw == nil {
75 - continue
76 - }
77 - val, err := ctx.LoadModuleInline("filter", "caddy.logging.encoders.filter", filterRaw)
78 - if err != nil {
79 - return fmt.Errorf("loading log filter module: %v", err)
80 - }
81 - fe.Fields[field] = val.(LogFieldFilter)
82 - }
83 - fe.FieldsRaw = nil // allow GC to deallocate
76 + vals, err := ctx.LoadModule(fe, "FieldsRaw")
77 + if err != nil {
78 + return fmt.Errorf("loading log filter modules: %v", err)
79 + }
80 + for fieldName, modIface := range vals.(map[string]interface{}) {
81 + fe.Fields[fieldName] = modIface.(LogFieldFilter)
82 + }
83
84 return nil
85 }
modules/logging/filters.go
+7 -4
@@ -41,8 +41,8 @@ type DeleteFilter struct{}
41 // CaddyModule returns the Caddy module information.
42 func (DeleteFilter) CaddyModule() caddy.ModuleInfo {
43 return caddy.ModuleInfo{
44 - Name: "caddy.logging.encoders.filter.delete",
45 - New: func() caddy.Module { return new(DeleteFilter) },
44 + ID: "caddy.logging.encoders.filter.delete",
45 + New: func() caddy.Module { return new(DeleteFilter) },
46 }
47 }
48
@@ -55,15 +55,18 @@ func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
55 // IPMaskFilter is a Caddy log field filter that
56 // masks IP addresses.
57 type IPMaskFilter struct {
58 + // The IPv4 range in CIDR notation.
59 IPv4CIDR int `json:"ipv4_cidr,omitempty"`
60 +
61 + // The IPv6 range in CIDR notation.
62 IPv6CIDR int `json:"ipv6_cidr,omitempty"`
63 }
64
65 // CaddyModule returns the Caddy module information.
66 func (IPMaskFilter) CaddyModule() caddy.ModuleInfo {
67 return caddy.ModuleInfo{
65 - Name: "caddy.logging.encoders.filter.ip_mask",
66 - New: func() caddy.Module { return new(IPMaskFilter) },
68 + ID: "caddy.logging.encoders.filter.ip_mask",
69 + New: func() caddy.Module { return new(IPMaskFilter) },
70 }
71 }
72
modules_test.go
+21 -21
@@ -22,17 +22,17 @@ import (
22 func TestGetModules(t *testing.T) {
23 modulesMu.Lock()
24 modules = map[string]ModuleInfo{
25 - "a": {Name: "a"},
26 - "a.b": {Name: "a.b"},
27 - "a.b.c": {Name: "a.b.c"},
28 - "a.b.cd": {Name: "a.b.cd"},
29 - "a.c": {Name: "a.c"},
30 - "a.d": {Name: "a.d"},
31 - "b": {Name: "b"},
32 - "b.a": {Name: "b.a"},
33 - "b.b": {Name: "b.b"},
34 - "b.a.c": {Name: "b.a.c"},
35 - "c": {Name: "c"},
25 + "a": {ID: "a"},
26 + "a.b": {ID: "a.b"},
27 + "a.b.c": {ID: "a.b.c"},
28 + "a.b.cd": {ID: "a.b.cd"},
29 + "a.c": {ID: "a.c"},
30 + "a.d": {ID: "a.d"},
31 + "b": {ID: "b"},
32 + "b.a": {ID: "b.a"},
33 + "b.b": {ID: "b.b"},
34 + "b.a.c": {ID: "b.a.c"},
35 + "c": {ID: "c"},
36 }
37 modulesMu.Unlock()
38
@@ -43,24 +43,24 @@ func TestGetModules(t *testing.T) {
43 {
44 input: "",
45 expect: []ModuleInfo{
46 - {Name: "a"},
47 - {Name: "b"},
48 - {Name: "c"},
46 + {ID: "a"},
47 + {ID: "b"},
48 + {ID: "c"},
49 },
50 },
51 {
52 input: "a",
53 expect: []ModuleInfo{
54 - {Name: "a.b"},
55 - {Name: "a.c"},
56 - {Name: "a.d"},
54 + {ID: "a.b"},
55 + {ID: "a.c"},
56 + {ID: "a.d"},
57 },
58 },
59 {
60 input: "a.b",
61 expect: []ModuleInfo{
62 - {Name: "a.b.c"},
63 - {Name: "a.b.cd"},
62 + {ID: "a.b.c"},
63 + {ID: "a.b.cd"},
64 },
65 },
66 {
@@ -69,8 +69,8 @@ func TestGetModules(t *testing.T) {
69 {
70 input: "b",
71 expect: []ModuleInfo{
72 - {Name: "b.a"},
73 - {Name: "b.b"},
72 + {ID: "b.a"},
73 + {ID: "b.b"},
74 },
75 },
76 {