mirror of
https://github.com/caddyserver/caddy.git
synced 2025-12-23 21:37:38 -05:00
Remove old docs
parent
d7a73c3262
commit
24df13116c
@ -1,387 +1,5 @@
|
||||
### THESE DOCS ARE MOVING.
|
||||
### THESE DOCS HAVE MOVED, YAY!
|
||||
|
||||
### Please visit this page on our NEW documentation site: https://caddyserver.com/docs/extending-caddy
|
||||
|
||||
**The new site is still a work-in-progress. Please be patient if you encounter errors. Thank you for understanding.**
|
||||
|
||||
---------------
|
||||
---------------
|
||||
---------------
|
||||
|
||||
**OBSOLETE DOCS**
|
||||
|
||||
|
||||
Extending Caddy 2 is easy because of Caddy's modular architecture.
|
||||
|
||||
NOTE: Caddy 2 is not yet a stable 2.0 release, so breaking changes are still possible. We will try to minimize them but please stay attentive just in case. :)
|
||||
|
||||
If you are writing a Caddy 2 module, please let us know in our [forum](https://caddy.community) or post an issue, especially if you encounter difficulties. We want to make this as easy and flexible as possible, so let us know what you're making and how it goes!
|
||||
|
||||
# Contents
|
||||
|
||||
- [Module Basics](#module-basics)
|
||||
- [Module Requirements](#module-requirements)
|
||||
- [Module Names](#module-names)
|
||||
- [Module Implementation](#module-implementation)
|
||||
- [Configuration](#configuration)
|
||||
- [Table of Namespaces and Interfaces](#table-of-namespaces-and-interfaces)
|
||||
- [Module Lifecycle](#module-lifecycle)
|
||||
- [Provisioning](#provisioning)
|
||||
- [Logs](#logs)
|
||||
- [Validating](#validating)
|
||||
- [Interface guards](#interface-guards)
|
||||
- [Host Modules](#host-modules)
|
||||
- [Complete Example](#complete-example)
|
||||
|
||||
|
||||
# Module Basics
|
||||
|
||||
Caddy extensions are called "modules" (distinct from Go modules), or "plugins".
|
||||
|
||||
Modules register themselves with Caddy when they are imported. Here's a template you can copy & paste:
|
||||
|
||||
```go
|
||||
package mymodule
|
||||
|
||||
import "github.com/caddyserver/caddy/v2"
|
||||
|
||||
func init() {
|
||||
err := caddy.RegisterModule(Gizmo{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Gizmo is an example; put your own type here.
|
||||
type Gizmo struct {
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (Gizmo) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "foo.gizmo",
|
||||
New: func() caddy.Module { return new(Gizmo) },
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A module can then be "plugged in" by adding an import to Caddy's `main` package:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import _ "github.com/example/mymodule"
|
||||
```
|
||||
|
||||
# Module Requirements
|
||||
|
||||
Caddy modules:
|
||||
|
||||
1. Implement the `caddy.Module` interface to provide a name and constructor
|
||||
2. Have a unique name in the proper namespace
|
||||
3. Satisfy the host module's defined interface for that namespace
|
||||
|
||||
We will show you how to satisfy these requirements in the next sections. It's really quite easy!
|
||||
|
||||
# Module Names
|
||||
|
||||
Each Caddy module has a unique ID, consisting of a namespace and name:
|
||||
|
||||
- A complete ID looks like `a.b.c.module_name`
|
||||
- The namespace would be `a.b.c`
|
||||
- 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 names should use `snake_case` convention.
|
||||
|
||||
## Apps
|
||||
|
||||
Apps are modules which define their own top-level namespace, and which appear in the `"apps"` property of the top-level of Caddy's config:
|
||||
|
||||
```json
|
||||
{
|
||||
"apps": {}
|
||||
}
|
||||
```
|
||||
|
||||
Example apps are `http` and `tls`. Their module name and namespace are the same. Guest modules use a namespace inherited from their host module. For example, HTTP handlers use the `http.handlers` namespace and TLS certificate loaders use the `tls.certificates` namespace. App modules implement the `caddy.App` interface. See below for a table of namespaces and their corresponding interfaces.
|
||||
|
||||
## Namespaces
|
||||
|
||||
You must properly namespace your guest module in order for it to be recognized by a host module. You can find a table of standard namespaces below.
|
||||
|
||||
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.
|
||||
|
||||
The `caddy` and `admin` namespaces are reserved.
|
||||
|
||||
To write modules which plug into 3rd-party host modules, consult those modules for their namespace documentation.
|
||||
|
||||
# Module Implementation
|
||||
|
||||
Typically, a namespace is associated with at least one interface the module must implement in order to be used. When a module is loaded, the `New` function is called to obtain a new instance of type `interface{}`. Typically, this is then type-asserted to be a specific interface so that it can be used; if your module does not implement the interface for that namespace, it will panic or error.
|
||||
|
||||
## Configuration
|
||||
|
||||
A module should usually be configurable. Its configuration will automatically be unmarshaled into its type when it is loaded. If your module is a struct type, you will need to use JSON struct tags on your fields and use `snake_casing`:
|
||||
|
||||
```go
|
||||
type Gizmo struct {
|
||||
MyField string `json:"my_field,omitempty"`
|
||||
Number int `json:"number,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
This will ensure that config properties are consisently named across all of Caddy and that users can configure your module easily.
|
||||
|
||||
You can expect that instances of your module are filled out with any configuration they were loaded with. It is also possible to perform additional [provisioning](#provisioning) and [verification](#validating) steps when your module is loaded.
|
||||
|
||||
|
||||
## Table of Namespaces and Interfaces
|
||||
|
||||
| Kind of module | Namespace | Interface |
|
||||
| -------------- | --------- | --------- |
|
||||
| Caddy app | | caddy.App |
|
||||
| HTTP handler | `http.handlers` | caddyhttp.MiddlewareHandler |
|
||||
| HTTP encoder | `http.encoders` | caddyhttp.Encoder |
|
||||
| HTTP request matcher | `http.matchers` | caddyhttp.RequestMatcher |
|
||||
| TLS certificate management | `tls.management` | (TODO) |
|
||||
| TLS handshake matcher | `tls.handshake_match` | caddytls.ConnectionMatcher |
|
||||
| TLS session tickets | `tls.stek` | caddytls.STEKProvider |
|
||||
| Caddy storage | `caddy.storage` | caddy.StorageConverter |
|
||||
|
||||
For example, HTTP handlers satisfy the `caddyhttp.MiddlewareHandler` interface.
|
||||
|
||||
|
||||
## Module Lifecycle
|
||||
|
||||
When your module is loaded by a host module, the following happens:
|
||||
|
||||
1. `New` is called to get an instance of the module's value.
|
||||
2. The module's configuration is unmarshaled into that instance.
|
||||
3. If the module is a `caddy.Provisioner`, the `Provision()` method is called.
|
||||
4. If the module is a `caddy.Validator`, the `Validate()` method is called.
|
||||
5. At this point, the module value will probably be type-asserted by the host module into the interface it is expected to implement so that it can be used. However, this can vary; it could be that some modules may optionally satisfy interfaces. Check the host module's documentation to know for sure.
|
||||
6. When a module is no longer needed, and if it is a `caddy.CleanerUpper`, the `Cleanup()` method is called.
|
||||
|
||||
Note that multiple loaded instances of your module may overlap at a given time. During config reloads, modules are started before the old ones are stopped. Be sure to use global state carefully. Use the `caddy.UsagePool` type to help manage global state across module loads.
|
||||
|
||||
## Provisioning
|
||||
|
||||
A module's configuration will be unmarshaled into its value for you.
|
||||
|
||||
However, if your module requires additional provisioning steps, you can implement the `caddy.Provisioner` interface:
|
||||
|
||||
```go
|
||||
// Provision sets up the module.
|
||||
func (g *Gizmo) Provision(ctx caddy.Context) error {
|
||||
// TODO: set up the module
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
This is typically where host modules will load their guest/child modules, but it can be used for pretty much anything.
|
||||
|
||||
Provisioning MUST NOT depend on other apps; since provisioning is when modules are loaded. To rely on other app modules, you must wait until after the Provision phase. The provisioning of apps is done in an arbitrary order.
|
||||
|
||||
Additionally, you should avoid performing "expensive" operations in Provision, since provisioning is performed even if a config is only being validated.
|
||||
|
||||
### Logs
|
||||
|
||||
If your module emits logs, **do not use `log.Print*()` from the Go standard library**. In other words, do not use Go's global logger. Caddy uses high-performance, highly flexible, structured logging with [zap](https://github.com/uber-go/zap).
|
||||
|
||||
Instead, get a logger in your module's Provision method:
|
||||
|
||||
```go
|
||||
func (g *Gizmo) Provision(ctx caddy.Context) error {
|
||||
g.logger = ctx.Logger(g) // g.logger is a *zap.Logger
|
||||
}
|
||||
```
|
||||
|
||||
Then you can emit structured, leveled logs using `g.logger`. See the [zap godoc](https://godoc.org/go.uber.org/zap#Logger) for details.
|
||||
|
||||
|
||||
## Validating
|
||||
|
||||
Modules which would like to validate their configuration may do so by satisfying the `caddy.Validator` interface:
|
||||
|
||||
```go
|
||||
// Validate validates that the module has a usable config.
|
||||
func (g Gizmo) Validate() error {
|
||||
// TODO: validate the module's setup
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Validate should be a read-only function. It is run during the provisioning phase, after the `Provision()` method (if any).
|
||||
|
||||
|
||||
## Interface guards
|
||||
|
||||
Caddy module behavior is implicit because Go interfaces are satisfied implicitly. Simply adding the right methods to your module's type is all it takes to make or break your module's correctness.
|
||||
|
||||
Fortunately, there is an easy, no-overhead, compile-time check you can add to your code to ensure you've added the right methods. These are called interface guards:
|
||||
|
||||
```go
|
||||
var _ InterfaceType = (*YourType)(nil)
|
||||
```
|
||||
|
||||
Replace `InterfaceType` with the interface you intend to satisfy, and `YourType` with the name of your module's type.
|
||||
|
||||
For example, an HTTP handler such as the static file server might satisfy multiple interfaces:
|
||||
|
||||
```go
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*FileServer)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*FileServer)(nil)
|
||||
)
|
||||
```
|
||||
|
||||
This prevents the program from compiling if `*FileServer` does not satisfy those interfaces.
|
||||
|
||||
Without interface guards, confusing bugs can slip in. For example, if your module must provision itself before being used but your `Provision()` method has a mistake (e.g. misspelled or wrong signature), provisioning will never happen, leading to head-scratching. Interface guards are super easy and can prevent that.
|
||||
|
||||
|
||||
# Host Modules
|
||||
|
||||
As explained above, a host module is a module which can load its own guest modules. In other words, is configuration/behavior is itself modular or pluggable.
|
||||
|
||||
A host module will need to load and use its guest modules. Caddy provides facilities for this. This section describes how to do this in a conventional way.
|
||||
|
||||
First, host modules need a way to receive the guest modules' configuration. This is typically done by adding `json.RawMessage` fields to the struct, and then using the Provision method to load them into a non-JSON type. For example, if your module `Gizmo` has a `Gadget` guest module that implements a `Gadgeter` interface, you would add two fields, `GadgetRaw` and `Gadget`:
|
||||
|
||||
```go
|
||||
GadgetRaw json.RawMessage `json:"gadget,omitempty" caddy:"namespace=foo.gizmo.gadgets inline_key=gadgeter"`
|
||||
|
||||
// the decoded value of the guest module will be
|
||||
// stored here, but it doesn't get used for JSON
|
||||
// so make sure to either exclude it with "-" or
|
||||
// unexport it
|
||||
Gadget Gadgeter `json:"-"`
|
||||
```
|
||||
|
||||
The configuration for `Gadget` would come in on the `GadgetRaw` field, then your `Provision()` method would be implemented to load the instance of the Gadget from the RawMessage.
|
||||
|
||||
You can load guest modules with the `LoadModule()` method. You pass in a struct pointer and the field name, and it loads a module from the json.RawMessage on the field by reading the caddy struct tag.
|
||||
|
||||
The struct tag must define a namespace from which to load the module, and also a way to obtain the module name. If `inline_key` is specified in the struct tag, then the module name will be found _inline_ with its configuration at that key. If `inline_key` is omitted from the struct tag, then the module name must be the key of a `caddy.ModuleMap` (or `map[string]json.RawMessage`); if the field is not this type when `inline_key` is missing, that is an error, since it does not know how to get the module name.
|
||||
|
||||
Here's an example of using `LoadModule()` to get a Gadgeter for our Gizmo module:
|
||||
|
||||
```go
|
||||
// Provision sets up g and loads its gadget.
|
||||
func (g *Gizmo) Provision(ctx caddy.Context) error {
|
||||
if g.GadgetRaw != nil {
|
||||
val, err := ctx.LoadModule(g, "GadgetRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading gadget module: %v", err)
|
||||
}
|
||||
g.Gadget = val.(Gadgeter)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
If the guest module is a required field, you should return an error if the Raw field is nil or empty.
|
||||
|
||||
# Complete Example
|
||||
|
||||
Let's suppose we want to write an HTTP handler module. This will be a contrived middleware for demonstration purposes which prints the visitor's IP address to a stream on every HTTP request.
|
||||
|
||||
We also want it to be configurable via the Caddyfile, because most people prefer to use the Caddyfile in non-automated situations. We do this by registering a Caddyfile handler directive, which is a kind of directive that can add a handler to the HTTP route. We also implement the `caddyfile.Unmarshaler` interface. By adding these few lines of code, this module can be configured with the Caddyfile! For example: `visitor_ip stdout`.
|
||||
|
||||
Here is the code for such a module, with explanatory comments:
|
||||
|
||||
```go
|
||||
package visitorip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/config/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/config/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(Middleware{})
|
||||
httpcaddyfile.RegisterHandlerDirective("visitor_ip", parseCaddyfile)
|
||||
}
|
||||
|
||||
// Middleware implements an HTTP handler that writes the
|
||||
// visitor's IP address to a file or stream.
|
||||
type Middleware struct {
|
||||
// The file or stream to write to. Can be "stdout"
|
||||
// or "stderr".
|
||||
Output string `json:"output,omitempty"`
|
||||
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (Middleware) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "http.handlers.visitor_ip",
|
||||
New: func() caddy.Module { return new(Middleware) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision implements caddy.Provisioner.
|
||||
func (m *Middleware) Provision(ctx caddy.Context) error {
|
||||
switch m.Output {
|
||||
case "stdout":
|
||||
m.w = os.Stdout
|
||||
case "stderr":
|
||||
m.w = os.Stderr
|
||||
default:
|
||||
return fmt.Errorf("an output stream is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate implements caddy.Validator.
|
||||
func (m *Middleware) Validate() error {
|
||||
if m.w == nil {
|
||||
return fmt.Errorf("no writer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeHTTP implements caddyhttp.MiddlewareHandler.
|
||||
func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||
m.w.Write([]byte(r.RemoteAddr))
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (m *Middleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
if !d.Args(&m.Output) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseCaddyfile unmarshals tokens from h into a new Middleware.
|
||||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
var m Middleware
|
||||
err := m.UnmarshalCaddyfile(h.Dispenser)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*Middleware)(nil)
|
||||
_ caddy.Validator = (*Middleware)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*Middleware)(nil)
|
||||
_ caddyfile.Unmarshaler = (*Middleware)(nil)
|
||||
)
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user