From 2c8897f6a7d469af001b582e3691e270e0f3fca2 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 28 May 2020 14:19:14 -0600 Subject: [PATCH] docs: Expand "Extending Caddy" to new Developers section --- src/docs/markdown/extending-caddy.md | 71 ++++++---- .../markdown/extending-caddy/caddyfile.md | 124 ++++++++++++++++++ .../extending-caddy/config-adapters.md | 62 +++++++++ .../markdown/extending-caddy/namespaces.md | 31 +++++ src/includes/docs-nav.html | 11 +- 5 files changed, 272 insertions(+), 27 deletions(-) create mode 100644 src/docs/markdown/extending-caddy/caddyfile.md create mode 100644 src/docs/markdown/extending-caddy/config-adapters.md create mode 100644 src/docs/markdown/extending-caddy/namespaces.md diff --git a/src/docs/markdown/extending-caddy.md b/src/docs/markdown/extending-caddy.md index 92473d1..b5c36f3 100644 --- a/src/docs/markdown/extending-caddy.md +++ b/src/docs/markdown/extending-caddy.md @@ -4,17 +4,20 @@ title: "Extending Caddy" # Extending Caddy -Caddy is easy to extend because of its modular architecture. Most kinds of Caddy extensions (or plugins) are known as _modules_ if they extend or plug into Caddy's configuration structure. To be clear, Caddy modules are distinct from [Go modules](https://github.com/golang/go/wiki/Modules) (but they are also Go modules). On this page, we refer to Caddy modules. +Caddy is easy to extend because of its modular architecture. Most kinds of Caddy extensions (or plugins) are known as _modules_ if they extend or plug into Caddy's configuration structure. To be clear, Caddy modules are distinct from [Go modules](https://github.com/golang/go/wiki/Modules) (but they are also Go modules). + +**Prerequisites:** +- Basic understanding of [Caddy's architecture](/docs/architecture) +- Go language proficiency +- [`go`](https://golang.org/dl) +- [`xcaddy`](https://github.com/caddyserver/xcaddy) +## Quick Start -## Module Basics +A Caddy module is any named type that registers itself as a Caddy module when its package is imported. Crucially, a module always implements the [caddy.Module](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Module) interface, which provides its name and a constructor function. -A Caddy module is any named type that registers itself as a Caddy module when its package is imported. - -Crucially, a module always implements the [caddy.Module](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Module) interface, which provides its name and a constructor function. - -Here's a template you can copy & paste: +In a new Go module, paste the following template into a Go file and customize your package name, type name, and Caddy module ID: ```go package mymodule @@ -38,13 +41,27 @@ func (Gizmo) CaddyModule() caddy.ModuleInfo { } ``` -A module is "plugged in" by adding an import to the program: +Then run this command from your project's directory, and you should see your module in the list: + +
xcaddy list-modules
+...
+foo.gizmo
+...
+ + + +Congratulations, your module registers with Caddy and can be used in [Caddy's config document](/docs/json/) in whatever places use modules in the same namespace. + +Under the hood, `xcaddy` is simply making a new Go module that requires both Caddy and your plugin (with an appropriate `replace` to use your local development version), then adds an import to ensure it is compiled in: ```go import _ "github.com/example/mymodule" ``` -## Module Requirements + +## Module Basics Caddy modules: @@ -52,7 +69,10 @@ Caddy modules: 2. Have a unique name in the proper namespace 3. Usually satisfy some interface(s) that are meaningful to the host module for that namespace -The next sections explain how to satisfy these properties! +**Host modules** (or _parent modules_) are modules which load/initialize other modules. They typically define namespaces for guest modules. + +**Guest modules** (or _child modules_) are modules which get loaded or initialized. All modules are guest modules. + ## Module IDs @@ -62,32 +82,28 @@ Each Caddy module has a unique ID, consisting of a namespace and name: - The namespace would be `foo.bar` - The name would be `module_name` which must be unique in its namespace -**Host modules** (or _parent modules_) are modules which load/initialize other modules. They typically define namespaces for guest modules. - -**Guest modules** (or _child modules_) are modules which get loaded or initialized. All modules are guest modules. - Module IDs must use `snake_case` convention. ### Namespaces -Namespaces are like classes, i.e. a namespace defines some functionality that is common among all modules within it. For example, we can expect that all modules within the `http.handlers` namespace are HTTP handlers. It follows that a host module may type-assert guest modules in that namespace from `interface{}` types into a more specific, useful type such as `http.Handler`. +Namespaces are like classes, i.e. a namespace defines some functionality that is common among all modules within it. For example, we can expect that all modules within the `http.handlers` namespace are HTTP handlers. It follows that a host module may type-assert guest modules in that namespace from `interface{}` types into a more specific, useful type such as `caddyhttp.MiddlewareHandler`. -A guest module must be properly namespaced in order for it to be recognized by a host module, because host modules will often ask Caddy core for a list of all modules within a certain namespace for a specific functionality it requires. +A guest module must be properly namespaced in order for it to be recognized by a host module because host modules will ask Caddy for modules within a certain namespace to provide the functionality desired by the host module. For example, if you were to write an HTTP handler module called `gizmo`, your module's name would be `http.handlers.gizmo`, because the `http` app will look for handlers in the `http.handlers` namespace. -Usually, but not always, a Caddy module namespaces correlates with a Go interface type that the modules in that namespace are expected to implement. +Put another way, Caddy modules are expected to implement [certain interfaces](/docs/extending-caddy/namespaces) depending on their module namespace. With this convention, module developers can say intuitive things such as, "All modules in the `http.handlers` namespace are HTTP handlers." More technically, this usually means, "All modules in the `http.handlers` namespace implement the `caddyhttp.MiddlewareHandler` interface." Because that method set is known, the more specific type can be asserted and used. -For example, if you were to write an HTTP handler module called `gizmo`, your module's name would be `http.handlers.gizmo`, because the `http` app will look for handlers in the `http.handlers` namespace. +**[View a table mapping all the standard Caddy namespaces to their Go types.](/docs/extending-caddy/namespaces)** -The `caddy` and `admin` namespaces are reserved. +The `caddy` and `admin` namespaces are reserved and cannot be app names. To write modules which plug into 3rd-party host modules, consult those modules for their namespace documentation. ### Names -The name within a namespace is not particularly important, as long as it is unique, concise, and makes sense for what it does. +The name within a namespace is significant and highly visible to users, but is not particularly important, as long as it is unique, concise, and makes sense for what it does. -### Apps +## App Modules Apps are modules with an empty namespace, and which conventionally become their own top-level namespace. App modules implement the [caddy.App](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#App) interface. @@ -141,7 +157,7 @@ Note that multiple loaded instances of your module may overlap at a given time! A module's configuration will be unmarshaled into its value automatically. This means, for example, that struct fields will be filled out for you. -However, if your module requires additional provisioning steps, you can implement the [caddy.Provisioner](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Provisioner) interface: +However, if your module requires additional provisioning steps, you can implement the (optional) [caddy.Provisioner](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Provisioner) interface: ```go // Provision sets up the module. @@ -151,9 +167,9 @@ func (g *Gizmo) Provision(ctx caddy.Context) error { } ``` -This is typically where host modules will load their guest/child modules, but it can be used for pretty much anything. +This is typically where host modules will load their guest/child modules, but it can be used for pretty much anything. Module provisioning is done in an arbitrary order. -Provisioning MUST NOT depend on other apps, since provisioning apps is done in an arbitrary order. To rely on other app modules, you must wait until after the Provision phase. +A module may access other apps by calling `ctx.App()`, but modules must not have circular dependencies. In other words, a module loaded by the `http` app cannot depend on the `tls` app if a module loaded by the `tls` app depends on the `http` app. (Very similar to rules forbidding import cycles in Go.) Additionally, you should avoid performing expensive operations in `Provision`, since provisioning is performed even if a config is only being validated. When in the provisioning phase, do not expect that the module will actually be used. @@ -174,7 +190,7 @@ Then you can emit structured, leveled logs using `g.logger`. See [zap's godoc](h ### Validating -Modules which would like to validate their configuration may do so by satisfying the [`caddy.Validator`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Validator) interface: +Modules which would like to validate their configuration may do so by satisfying the (optional) [`caddy.Validator`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Validator) interface: ```go // Validate validates that the module has a usable config. @@ -184,7 +200,7 @@ func (g Gizmo) Validate() error { } ``` -Validate should be a read-only function. It is run during the provisioning phase right after the `Provision()` method. +Validate should be a read-only function. It is run after the `Provision()` method. ### Interface guards @@ -271,6 +287,9 @@ Note that the `LoadModule()` call takes a pointer to the struct and the field na If a guest module must explicitly be set by the user, you should return an error if the Raw field is nil or empty before trying to load it. +Notice how the loaded module is type-asserted: `g.Gadget = val.(Gadgeter)` - this is because the returned `val` is a `interface{}` type which is not very useful. However, we expect that all modules in the declared namespace (`foo.gizmo.gadgets` from the struct tag in our example) implement the `Gadgeter` interface, so this type assertion is safe, and then we can use it! + +If your host module defines a new namespace, be sure to document both that namespace and its Go type(s) for developers [like we have done here](/docs/extending-caddy/namespaces). ## Complete Example diff --git a/src/docs/markdown/extending-caddy/caddyfile.md b/src/docs/markdown/extending-caddy/caddyfile.md new file mode 100644 index 0000000..fb8db6c --- /dev/null +++ b/src/docs/markdown/extending-caddy/caddyfile.md @@ -0,0 +1,124 @@ +--- +title: "Caddyfile Support" +--- + +# Caddyfile Support + +Caddy modules are automatically added to the [native JSON config](/docs/json/) by virtue of their namespace when they are [registered](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#RegisterModule), making them both usable and documented. This makes Caddyfile support purely optional, but it is often requested by users who prefer the Caddyfile. + +## Unmarshaler + +To add Caddyfile support for your module, simply implement the [`caddyfile.Unmarshaler`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/caddyfile?tab=doc#Unmarshaler) interface. You get to choose the Caddyfile syntax your module has by how you parse the tokens. + +An unmarshaler's job is simply to set up your module's type, e.g. by populating its fields, using the [`caddyfile.Dispenser`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/caddyfile?tab=doc#Dispenser) passed to it. For example, a module type named `Gizmo` might have this method: + +```go +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. Syntax: +// +// gizmo [