Buat Plugin

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() dan Validate() opsional tapi direkomendasikan.
  • Daftarkan plugin di func init() menggunakan caddy.RegisterModule() — Caddy akan otomatis mengenali modul saat init dipanggil.
  • Untuk HTTP middleware, implementasikan caddyhttp.MiddlewareHandler interface dengan method ServeHTTP(w, r, next) — selalu panggil next.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-NAMA untuk repository, http.handlers.NAMA untuk modul HTTP, dns.providers.NAMA untuk 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.

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact