Create a middleware plugin

1. Output of caddy version:

v2.6.2

2. How I run Caddy:

go run . run

a. System environment:

Windows 11 with WSL v2
Ubuntu 22.04.1 LTS

b. Command:

go run . run

c. Service/unit/compose file:

Paste full file contents here.
Make sure backticks stay on their own lines,
and the post looks nice in the preview pane. -->

d. My complete Caddy config:

:2019
root * ./public
file_server

3. The problem I’m having:

I’m trying to create a middleware to learn how to extend caddy. I create the file of the plugin and put some logs to debug the stages where the plugin is passing through. When i run caddy show the log of the init, CaddyModule and New functions, but never execute the ServeHTTP function.

The middleware that i’m trying to create is a simple plugin that put the remote address on the content of the response.

These are my files:

main.go

package main

import (
	caddycmd "github.com/caddyserver/caddy/v2/cmd"

	// plug in Caddy modules here
	_ "github.com/caddyserver/caddy/v2/modules/standard"
)

func main() {
	caddycmd.Main()
}

simple_middleware.go

package main

import (
	"net/http"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"go.uber.org/zap"
)

var k, s string
var logger *zap.Logger

func init() {
	k = "simple_middleware"
	s = "stage"
	logger, _ = zap.NewDevelopment()
	logger.Debug(k, zap.String(s, "init"))
	caddy.RegisterModule(Middleware{})
}

type Middleware struct{}

func (Middleware) CaddyModule() caddy.ModuleInfo {
	logger.Debug(k, zap.String(s, "CaddyModule"))
	return caddy.ModuleInfo{
		ID: "http.handlers.simple_middleware",
		New: func() caddy.Module {
			logger.Debug(k, zap.String(s, "New"))
			return new(Middleware)
		},
	}
}

func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	w.Write([]byte(r.RemoteAddr))
	logger.Debug(k, zap.String("remote addr", r.RemoteAddr))

	return next.ServeHTTP(w, r)
}

var _ caddyhttp.MiddlewareHandler = (*Middleware)(nil)

4. Error messages and/or full log output:

$ go run . list-modules
2022-10-28T10:37:15.738-0300    DEBUG   dupmiddleware/simple_middleware.go:18   simple_middleware       {"stage": "init"}
2022-10-28T10:37:15.738-0300    DEBUG   dupmiddleware/simple_middleware.go:25   simple_middleware       {"stage": "CaddyModule"}
2022-10-28T10:37:15.738-0300    DEBUG   dupmiddleware/simple_middleware.go:29   simple_middleware       {"stage": "New"}
2022-10-28T10:37:15.738-0300    DEBUG   dupmiddleware/simple_middleware.go:29   simple_middleware       {"stage": "New"}
admin.api.load
admin.api.metrics
admin.api.pki
admin.api.reverse_proxy
caddy.adapters.caddyfile
caddy.config_loaders.http
caddy.listeners.http_redirect
caddy.listeners.tls
caddy.logging.encoders.console
caddy.logging.encoders.filter
caddy.logging.encoders.filter.cookie
caddy.logging.encoders.filter.delete
caddy.logging.encoders.filter.hash
caddy.logging.encoders.filter.ip_mask
caddy.logging.encoders.filter.query
caddy.logging.encoders.filter.regexp
caddy.logging.encoders.filter.rename
caddy.logging.encoders.filter.replace
caddy.logging.encoders.json
caddy.logging.writers.discard
caddy.logging.writers.file
caddy.logging.writers.net
caddy.logging.writers.stderr
caddy.logging.writers.stdout
caddy.storage.file_system
events
http
http.authentication.hashes.bcrypt
http.authentication.hashes.scrypt
http.authentication.providers.http_basic
http.encoders.gzip
http.encoders.zstd
http.handlers.acme_server
http.handlers.authentication
http.handlers.copy_response
http.handlers.copy_response_headers
http.handlers.encode
http.handlers.error
http.handlers.file_server
http.handlers.headers
http.handlers.map
http.handlers.metrics
http.handlers.push
http.handlers.request_body
http.handlers.reverse_proxy
http.handlers.rewrite
http.handlers.static_response
http.handlers.subroute
http.handlers.templates
http.handlers.tracing
http.handlers.vars
http.matchers.expression
http.matchers.file
http.matchers.header
http.matchers.header_regexp
http.matchers.host
http.matchers.method
http.matchers.not
http.matchers.path
http.matchers.path_regexp
http.matchers.protocol
http.matchers.query
http.matchers.remote_ip
http.matchers.vars
http.matchers.vars_regexp
http.precompressed.br
http.precompressed.gzip
http.precompressed.zstd
http.reverse_proxy.selection_policies.cookie
http.reverse_proxy.selection_policies.first
http.reverse_proxy.selection_policies.header
http.reverse_proxy.selection_policies.ip_hash
http.reverse_proxy.selection_policies.least_conn
http.reverse_proxy.selection_policies.random
http.reverse_proxy.selection_policies.random_choose
http.reverse_proxy.selection_policies.round_robin
http.reverse_proxy.selection_policies.uri_hash
http.reverse_proxy.transport.fastcgi
http.reverse_proxy.transport.http
http.reverse_proxy.upstreams.a
http.reverse_proxy.upstreams.multi
http.reverse_proxy.upstreams.srv
pki
tls
tls.certificates.automate
tls.certificates.load_files
tls.certificates.load_folders
tls.certificates.load_pem
tls.certificates.load_storage
tls.client_auth.leaf
tls.get_certificate.http
tls.get_certificate.tailscale
tls.handshake_match.remote_ip
tls.handshake_match.sni
tls.issuance.acme
tls.issuance.internal
tls.issuance.zerossl
tls.stek.distributed
tls.stek.standard

  Standard modules: 99

http.handlers.simple_middleware

  Non-standard modules: 1

  Unknown modules: 0

$ go run . run
2022-10-28T10:39:50.894-0300    DEBUG   dupmiddleware/simple_middleware.go:18   simple_middleware       {"stage": "init"}
2022-10-28T10:39:50.894-0300    DEBUG   dupmiddleware/simple_middleware.go:25   simple_middleware       {"stage": "CaddyModule"}
2022-10-28T10:39:50.894-0300    DEBUG   dupmiddleware/simple_middleware.go:29   simple_middleware       {"stage": "New"}
2022/10/28 13:39:50.894 INFO    using adjacent Caddyfile
2022/10/28 13:39:50.897 INFO    admin   admin endpoint started  {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2022/10/28 13:39:50.898 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0xc0003c4a10"}
2022/10/28 13:39:50.898 INFO    http.log        server running  {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2022/10/28 13:39:50.898 INFO    tls     cleaning storage unit   {"description": "FileStorage:/home/driveup/.local/share/caddy"}
2022/10/28 13:39:50.898 INFO    tls     finished cleaning storage units
2022/10/28 13:39:50.898 INFO    autosaved config (load with --resume flag)      {"file": "/home/driveup/.config/caddy/autosave.json"}
2022/10/28 13:39:50.898 INFO    serving initial configuration

5. What I already tried:

I have tried with xcaddy, searching on google for solutions,re view the source code on github everything seems to be ok and i can’t figure out what the problem is.

6. Links to relevant resources:

You’ll need to specify your plugin in your config file. In order to use it with the Caddyfile you’ll need to create a Caddyfile directive. Right now you’ve only set the root dir and enabled a file server.

I’m mobile atm but there are docs on our Extending Caddy page on how to do this very thing.

1 Like

Hi Matt!

Thanks for the reply, It was so helpful because It’s seems I was skipping this part of the document everytime that I read it.

Congratulations, your module registers with Caddy and can be used in Caddy’s config document in whatever places use modules in the same namespace.

I switched from Caddyfile to config.json

{
  "apps": {
    "http": {
      "servers": {
        "server": {
          "listen": [":2019"],
          "routes": [
            {
              "handle": [
                { "handler": "simple_middleware" },
                {
                  "handler": "file_server",
                  "root": "./public"
                }
              ]
            }
          ]
        }
      }
    }
  }
}

And it works!
Again thanks for your help!

2 Likes

No problem!

At my computer now, so it’s easier to link to the relevant page:

Hope you can have fun with it!

2 Likes

This topic was automatically closed after 30 days. New replies are no longer allowed.