Go 集成
除了当独立进程,Caddy 也可以作为 Go 库嵌入到自己的程序里。
引入依赖
章节「引入依赖」go get github.com/caddyserver/caddy/v2最小示例
章节「最小示例」package main
import ( "log"
"github.com/caddyserver/caddy/v2" _ "github.com/caddyserver/caddy/v2/modules/standard")
func main() { err := caddy.Run() if err != nil { log.Fatal(err) }}那个下划线导入 modules/standard 会注册所有标准模块——不导入的话,配置里用到的指令会报 unknown directive。
加载配置
章节「加载配置」package main
import ( "log"
"github.com/caddyserver/caddy/v2" _ "github.com/caddyserver/caddy/v2/modules/standard")
func main() { err := caddy.Load("Caddyfile", true) if err != nil { log.Fatal(err) }
// 阻塞 select {}}第二个参数 true 表示强制只加载这个文件。
从字符串加载
章节「从字符串加载」config := `example.com { reverse_proxy localhost:3000}`
if err := caddy.LoadString(config, true); err != nil { log.Fatal(err)}自定义模块
章节「自定义模块」Caddy 的模块系统基于 Go 的 Module 接口。最小实现:
package mymodule
import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/core" "github.com/caddyserver/caddy/v2/modules/caddyhttp")
func init() { caddy.RegisterModule(GreetingHandler{}) httpcaddyfile.RegisterHandlerDirective("greet", parseCaddyfile)}
type GreetingHandler struct { Message string `json:"message,omitempty"`}
func (GreetingHandler) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ ID: "http.handlers.greet", New: func() caddy.Module { return new(GreetingHandler) }, }}
func (h *GreetingHandler) Provision(ctx caddy.Context) error { if h.Message == "" { h.Message = "Hello!" } return nil}
func (h *GreetingHandler) ServeHTTP( w http.ResponseWriter, r *http.Request, next caddyhttp.Handler,) error { w.Header().Set("Content-Type", "text/plain") w.Write([]byte(h.Message)) return nil}
func (h GreetingHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { d.Next() // 消费指令名 if !d.NextArg() { return d.ArgErr() } h.Message = d.Val() return nil}
var _ caddyhttp.MiddlewareHandler = (*GreetingHandler)(nil)注册后就能在 Caddyfile 里用:
example.com { greet "Welcome aboard"}启用 Go 模块
章节「启用 Go 模块」Caddyfile 里也能写 Go 代码块:
example.com { respond "hi from Go"}或者显式:
{ order respond before reverse_proxy}
example.com { respond { body "Hello from a handler" status 200 }}动态改配置
章节「动态改配置」import "context"
handler, err := caddy.NewAdminHandler(context.Background(), caddy.String("caddyfile"))if err != nil { log.Fatal(err)}err = handler.LoadConfig(`example.com { respond "changed" }`, true)什么时候该用 Go 集成
章节「什么时候该用 Go 集成」| 场景 | 建议 |
|---|---|
| 独立部署 Web 服务器 | 用二进制,别嵌入 |
| 需要自定义处理逻辑 | 写模块 |
| 单测里起一个真实服务器 | 嵌入,很方便 |
| 桌面应用内嵌服务器 | 嵌入 |
| 想要热更新但不想管进程 | 子进程方式 |
构建带自定义模块的二进制
章节「构建带自定义模块的二进制」//go:generate xcaddy build --with ./mymodule或者命令行:
xcaddy build \ --with github.com/caddy-dns/cloudflare \ --with github.com/mholt/caddy-ratelimit \ --with ./mymodule产出的二进制是完全静态的,可以直接扔到任何 Linux 上跑。
单元测试
章节「单元测试」func TestGreetingHandler(t *testing.T) { h := &GreetingHandler{Message: "hi"} if h.Message != "hi" { t.Fatal("unexpected message") }}对模块本身做单测,用 caddytest 集成测试框架:
import "github.com/caddyserver/caddy/v2/caddytest"
func TestIntegration(t *testing.T) { tester := caddytest.NewTester(t) tester.InitServer(` localhost:9080 { greet "test" } `) tester.AssertGetResponse("http://localhost:9080/", 200, "test")}