My encounter with Caddy
At some point in my previous position we had to set up live updates (i.e. getting changes without reloading the page) in our SPA.
Instead of relying on WebSockets I have chosen SSE as I was much simpler (as it is unidirectional) while perfectly fitting our use-cases.
Instead of building ours, we relied on Mercure, which itself relies on Caddy which is a "new" HTTP server.
While you don't really need to know it to have something working, we had come concerns which forced me to dig a little deeper, this log is about some snippets I collected.
The first awesome thing with Caddy
is its dynamical configuration.
Which means, once it runs (caddy run
), this command is enough to change the configuration:
$ curl localhost:2019/load \
-H "Content-Type: text/caddyfile" \
--data-binary @Caddyfile
So, with a simple definition:
http://localhost:8080 {
respond "Hello, world!"
}
(http://
here forces HTTP protocol, Caddy
if HTTPS by default)
We get
$ curl http://localhost:8080
Hello, world!
To be transparent, it often ends up in:
while inotifywait -e close_write Caddyfile test.sh; do ./test.sh; done
test.sh
containing the load curl
and the curl
s I want to test.
Caddy
gives you the opportunity to quickly create a reverse proxy:
http://localhost:8081 {
reverse_proxy :8080
}
You can be more specific adding requests matchers:
http://localhost:8081 {
reverse_proxy /api/* :8080
reverse_proxy /assets/* :8082
}
or having named matchers:
http://localhost:8081 {
@api_ro {
method GET
path /api/*
}
reverse_proxy @api_ro :8083
reverse_proxy /api/* :8080
reverse_proxy /assets/* :8082
}
Since I was running everything in kubernetes, I had to have a /healthz
endpoint to ensure container readiness/liveness:
http://localhost:8080 {
respond /healthz 200
}
At some point I also needed specific reverse proxy on another subdomain, involving CORS.
(cors) {
@origin{args.0} header Origin {args.0}
header @origin{args.0} Access-Control-Allow-Headers "content-type, x-requested-with"
header @origin{args.0} Vary Origin
}
localhost:8080 {
log
encode zstd gzip
import cors https://app.acme.crp
import cors https://backoffice.acme.crp
handle /healthz {
respond 200
}
handle /api {
rewrite * /v2/api
reverse_proxy https://backend.product.com {
header_up X-Real-IP {remote}
}
}
As you can see, you can easily factor-out directives.
I also use it extensively to prototype APIs (instead of Postman-like tools), as it allows me to have a mocked API code-first.