Understanding Caddy's Module and Plugin System¶
This guide explains how Caddy uses Go Interfaces to implement a dynamic, compile-time module and plugin system.
1. What is a Caddy Module?¶
At its core, Caddy is just a controller. All actual servers, protocols, and handlers are pluggable modules. Caddy standard modules live in modules/ and are registered statically at compile time.
2. Compile-Time Registration¶
Every Caddy module registers itself inside its file's init() block:
It registers its Module ID and a constructor function:
func (MyModule) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.my_handler",
New: func() caddy.Module { return new(MyModule) },
}
}
3. Interface Guards (Go Idiom)¶
In Go, there is no implements keyword. A struct implements an interface implicitly by declaring its methods.
To ensure that a struct doesn't accidentally break its interface requirements (which would cause compiler errors elsewhere), Caddy developers use Interface Guards:
var (
_ caddy.Provisioner = (*MyModule)(nil)
_ caddy.Validator = (*MyModule)(nil)
_ caddyfile.Unmarshaler = (*MyModule)(nil)
)
nil pointer of type *MyModule to variables of interface types (caddy.Provisioner, etc.).
* If MyModule is missing any of the interface's required methods, the Go compiler will fail immediately at compile-time with a clear message pointing to this line.
4. The Module Lifecycle¶
Caddy loads and configures modules in a strict order during a configuration change:
New(): Creates an empty instance of your module struct.- JSON Unmarshal: Populates the struct fields from the configuration JSON.
Provision(caddy.Context):- Interface:
caddy.Provisioner - Purpose: Set up internal states, establish loggers, compile regex patterns, or prepare socket listeners.
Validate():- Interface:
caddy.Validator - Purpose: Validate settings (e.g. check that a required string isn't empty, check that a path exists).
Start()/ Use:- Interface:
caddy.App(for top-level servers) or specific handler interfaces (likehttp.Handler). - Purpose: Run the code and handle incoming requests.
Stop()/Cleanup():- Interface:
caddy.CleanerUpper - Purpose: Close open database connections, shut down sockets, and release resources.