Buat Plugin #
Membuat plugin Caddy memerlukan pemahaman dasar Go programming, tapi arsitektur plugin Caddy dirancang dengan baik sehingga plugin sederhana bisa dibuat dalam waktu singkat. Setiap plugin adalah Go module yang mengimplementasikan interface tertentu yang didefinisikan oleh Caddy.
Tipe Plugin yang Bisa Dibuat #
HTTP Handler → Memproses HTTP request/response
Contoh: auth middleware, request transformer, rate limiter
HTTP Middleware → Wrapper yang berjalan sebelum/sesudah handler lain
Contoh: security headers, request logger kustom
Provisioner → Setup yang berjalan saat konfigurasi dimuat
Contoh: koneksi database, load config dari external source
Validator → Validasi konfigurasi sebelum digunakan
Contoh: cek apakah file ada, validasi format
DNS Provider → Provider untuk DNS-01 ACME challenge
Contoh: plugin untuk DNS provider yang belum ada
Storage → Backend penyimpanan sertifikat TLS
Contoh: store sertifikat di Redis, Vault, S3
Struktur Dasar Plugin #
my-caddy-plugin/
├── go.mod
├── go.sum
├── plugin.go ← Logika utama plugin
├── caddyfile.go ← Parser untuk Caddyfile syntax (opsional)
└── README.md
Membuat HTTP Handler Plugin #
Berikut contoh plugin sederhana yang menambahkan custom header ke setiap response:
// plugin.go
package caddyheaderinjector
import (
"net/http"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func init() {
// Daftarkan modul ke registry Caddy
caddy.RegisterModule(HeaderInjector{})
}
// HeaderInjector adalah plugin yang menambahkan header kustom
type HeaderInjector struct {
// Field ini bisa dikonfigurasi dari Caddyfile atau JSON
Headers map[string]string `json:"headers,omitempty"`
}
// CaddyModule mengembalikan informasi modul
// Format: "http.handlers.NAMA"
func (HeaderInjector) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.header_injector",
New: func() caddy.Module { return new(HeaderInjector) },
}
}
// Provision dipanggil saat konfigurasi dimuat
// Gunakan untuk setup: koneksi DB, load file, dll.
func (h *HeaderInjector) Provision(ctx caddy.Context) error {
// Validasi konfigurasi
if len(h.Headers) == 0 {
return fmt.Errorf("no headers configured")
}
return nil
}
// Validate dipanggil setelah Provision untuk validasi tambahan
func (h *HeaderInjector) Validate() error {
return nil
}
// ServeHTTP adalah handler utama — dipanggil untuk setiap request
func (h HeaderInjector) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
next caddyhttp.Handler,
) error {
// Tambahkan headers ke response
for key, value := range h.Headers {
w.Header().Set(key, value)
}
// Teruskan ke handler berikutnya
return next.ServeHTTP(w, r)
}
// Interface check — pastikan semua interface diimplementasikan
var (
_ caddy.Module = (*HeaderInjector)(nil)
_ caddy.Provisioner = (*HeaderInjector)(nil)
_ caddy.Validator = (*HeaderInjector)(nil)
_ caddyhttp.MiddlewareHandler = (*HeaderInjector)(nil)
)
go.mod untuk Plugin #
// go.mod
module github.com/username/caddy-header-injector
go 1.21
require (
github.com/caddyserver/caddy/v2 v2.8.4
)
Menambahkan Caddyfile Support #
// caddyfile.go
package caddyheaderinjector
import (
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func init() {
// Daftarkan directive Caddyfile
httpcaddyfile.RegisterHandlerDirective("header_injector", parseCaddyfile)
}
// parseCaddyfile mem-parse konfigurasi dari Caddyfile
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
var hi HeaderInjector
hi.Headers = make(map[string]string)
// Parse blok konfigurasi
// header_injector {
// X-Custom-Header "value"
// X-Another-Header "another-value"
// }
for h.NextBlock(0) {
key := h.Val()
var value string
if !h.Args(&value) {
return nil, h.ArgErr()
}
hi.Headers[key] = value
}
return &hi, nil
}
Menggunakan Plugin dalam Caddyfile #
example.com {
header_injector {
X-App-Version "1.0.0"
X-Environment "production"
X-Powered-By "My Stack"
}
reverse_proxy backend:3000
}
Testing Plugin #
// plugin_test.go
package caddyheaderinjector
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func TestHeaderInjector(t *testing.T) {
// Setup plugin
hi := &HeaderInjector{
Headers: map[string]string{
"X-Test-Header": "test-value",
},
}
// Setup test request dan recorder
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
// Handler berikutnya yang mengembalikan 200
nextHandler := caddyhttp.HandlerFunc(func(
w http.ResponseWriter,
r *http.Request,
) error {
w.WriteHeader(200)
return nil
})
// Jalankan plugin
err := hi.ServeHTTP(w, req, nextHandler)
// Assertions
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result := w.Result()
if result.Header.Get("X-Test-Header") != "test-value" {
t.Errorf("expected header X-Test-Header: test-value, got: %s",
result.Header.Get("X-Test-Header"))
}
}
# Jalankan tests
go test ./...
# Test dengan race detector
go test -race ./...
Build dan Test dengan xcaddy #
# Test plugin secara langsung dengan xcaddy
xcaddy build \
--with github.com/username/caddy-header-injector=./
# Atau dari local path selama development
xcaddy build \
--with github.com/username/caddy-header-injector=/path/to/plugin
# Verifikasi modul terdaftar
./caddy list-modules | grep header_injector
# http.handlers.header_injector
# Test konfigurasi
cat > test.Caddyfile << 'EOF'
localhost:8080 {
header_injector {
X-Test "hello from plugin"
}
respond "OK" 200
}
EOF
./caddy run --config test.Caddyfile
# Test dari terminal lain
curl -I http://localhost:8080/
# Harus ada: X-Test: hello from plugin
Mempublikasikan Plugin #
# 1. Push ke GitHub dengan nama yang jelas
# Format konvensi: caddy-NAMA atau caddy-KATEGORI-NAMA
# github.com/username/caddy-header-injector
# 2. Tag versi sesuai semantic versioning
git tag v0.1.0
git push origin v0.1.0
# 3. Pastikan go.mod valid
go mod tidy
go mod verify
# 4. Daftarkan ke caddyserver community
# Buat post di https://caddy.community/
# Tag: plugins, module
# 5. Tambahkan ke README:
# xcaddy build --with github.com/username/caddy-header-injector
Plugin Tipe Storage #
Contoh sederhana plugin storage yang menyimpan sertifikat di memory (untuk testing):
// memory_storage.go
package caddymemstorage
import (
"sync"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/certmagic"
)
func init() {
caddy.RegisterModule(MemStorage{})
}
type MemStorage struct {
mu sync.RWMutex
data map[string][]byte
}
func (MemStorage) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "caddy.storage.memory",
New: func() caddy.Module { return &MemStorage{data: make(map[string][]byte)} },
}
}
// Implementasikan semua method certmagic.Storage...
Ringkasan #
- Plugin Caddy adalah Go module yang mengimplementasikan interface tertentu —
CaddyModule()wajib,Provision()danValidate()opsional tapi direkomendasikan.- Daftarkan plugin di
func init()menggunakancaddy.RegisterModule()— Caddy akan otomatis mengenali modul saat init dipanggil.- Untuk HTTP middleware, implementasikan
caddyhttp.MiddlewareHandlerinterface dengan methodServeHTTP(w, r, next)— selalu panggilnext.ServeHTTP(w, r)untuk meneruskan ke handler berikutnya.- Tambahkan Caddyfile support dengan mendaftarkan directive parser di
httpcaddyfile.RegisterHandlerDirective()— ini membuat plugin bisa dikonfigurasi dari Caddyfile, bukan hanya JSON.- Test dengan xcaddy build –with github.com/user/plugin=./local/path untuk iterasi cepat saat development tanpa perlu push ke GitHub dulu.
- Ikuti konvensi nama:
caddy-NAMAuntuk repository,http.handlers.NAMAuntuk modul HTTP,dns.providers.NAMAuntuk DNS providers.
← Sebelumnya: Caddy DNS Berikutnya: Node.js App →
Contoh: Plugin Request ID #
Plugin yang lebih sederhana — generate dan inject request ID:
// requestid/plugin.go
package requestid
import (
"crypto/rand"
"encoding/hex"
"net/http"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func init() {
caddy.RegisterModule(RequestID{})
}
type RequestID struct {
Header string `json:"header,omitempty"`
}
func (RequestID) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.request_id",
New: func() caddy.Module { return &RequestID{} },
}
}
func (r *RequestID) Provision(_ caddy.Context) error {
if r.Header == "" {
r.Header = "X-Request-ID"
}
return nil
}
func (ri RequestID) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
b := make([]byte, 16)
rand.Read(b)
id := hex.EncodeToString(b)
r.Header.Set(ri.Header, id)
w.Header().Set(ri.Header, id)
return next.ServeHTTP(w, r)
}
Plugin ini: (1) generate random ID, (2) set sebagai request header ke backend, (3) tambahkan ke response header — memungkinkan korelasi log antara Caddy dan backend.