caddyserver / caddy

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

Home Page:https://caddyserver.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

V2: Writing plugins

JokerQyou opened this issue · comments

I was reading "extending caddy" from the doc site, and is having trouble get the final example working. Here's what I did:

  • Copied the final example code from that page, and put into a file visitorip/plugin.go
  • Inside visitorip directory I initialized a go module: go mod init visitorip
  • Manually added caddy v2 as a dependency, pasting this into go.mod:
require (
  github.com/caddyserver/caddy/v2 v2.0.0
)
  • Build caddy: Run xcaddy build --with visitorip=$(pwd) inside visitorip directory
  • Set capability: sudo setcap CAP_NET_BIND_SERVICE=+ep caddy

After having built the caddy binary, I tested it with the following Caddyfile:

{
  debug
}

localhost {
  visitor_ip stdout
  encode gzip
  reverse_proxy /* 127.0.0.1:8002
}

And that resulted in an error:

./caddy run
2020/05/23 11:55:22.504	INFO	using adjacent Caddyfile
run: adapting config using caddyfile: directive 'visitor_ip' is not ordered, so it cannot be used here

The error message seems to imply that custom directive is not supported at all, but this cannot be true, can it?

So, the solution is to either have users put the directive in a route block, or use the order global directive to specify the order relative to other directives.

The reason for this is tricky. For the Caddyfile, we have a preset directive order that is meant to allow users to write their Caddyfile how they want and have it work reasonably well in the 95% case. Some directives only make sense to run before others. You can see the default order here: https://caddyserver.com/docs/caddyfile/directives#directive-order

In Caddy v1, we had plugin authors make a PR to Caddyfile core to add their own directive to this list where they deemed it to fit. This eventually felt unsustainable because it would clutter the list and was too inflexible.

For Caddy v2 we decided to not do that anymore, but we offer two different ways for users to specify a custom order at runtime (as described in the first paragraph).

We specifically decided to not open an API to plugin authors to set an order, because it's too prone to causing conflicts between plugins. Instead, we ask plugin authors to suggest a default order global option to users or have them wrap it in a route block.

Thanks for your detailed explanation! I've managed to run the example plug-in after adding order visitor_ip before respond to global options.
I think these details should be added to the extension doc page so that plug-in authors would remember to document the recommended order for their plug-ins' directives.

Also I'd like to ask about the module lifecycle, typically this part: why is Validate() called after Provision()? Wouldn't the intuitive chain be like this?

  • New() which creates an instance of the module;
  • Unmarshall configuration (Load directive from caddyfile);
  • Validate() which validates loaded config values. If config is invalid caddy would quit with an error;
  • Provision() which sets up additional resources for this module;
  • Cleanup() when unload;

I'll have to delegate to @mholt for that question, but I assume the reason was so that validation can be done after all other modules have provisioned, so that a module can figure out if there's any conflicts caused by other modules, in which case it can back out.

Seconded #3438 (comment).
I also expected Validate to come before Provision mainly because I see Validate as config validation.

I've ended up discarding Validate a number of times cos I need it to be before Provision. Personally, I do not expect errors after successful Provision.

Thanks for the discussion so far.

I'm going to work on expanding the Extending Caddy docs into its own little sub-section with multiple pages to cover more things about directives and such.

Also I'd like to ask about the module lifecycle, typically this part: why is Validate() called after Provision()?

Francis is right. Because Provision() can change the state of a module -- or, theoretically, another module -- so validate comes after to make sure that the final provisioned config is valid.