如何為 caddy 添寫自定義插件
- 2019 年 10 月 3 日
- 筆記
如何為 caddy 添寫自定義插件
項目地址:https://github.com/yhyddr/quicksilver/tree/master/gosample/caddy-plugin
前言
Caddy附帶一個HTTP服務器,但是你可以實現其他服務器類型並將它們插入Caddy中。其他類型的服務器可以是SSH、SFTP、TCP、內部使用的其他東西等等。
對於Caddy來說,服務器的概念是任何可以Listen()
和Serve()
的東西。這意味着什麼、如何運作都取決於你。你可以自由地發揮你的創造力去使用它。
那麼怎樣去擴展 Caddy 呢?
不同的服務器類型,可以根據自己的需要定製不同的插件。我們在這裡,通過添加最簡單的不做任何事的插件,來熟悉如何擴展 Caddy 服務器。
Plugin for HTTP
我們會一步一步構建出一個 HTTP Plugin 的框架,到時候你只需要填充自己處理邏輯即可!那還等什麼,讓我們開始吧。
構建一個 HTTP Plugin ,代碼部分僅需要兩步,注意事項也有兩個。
創建一個 Go Package
首先為 caddy 創建一個 插件的 Go Package ,你可以新建一個文件夾達到這個效果。比如
├── caddy-plugin │ ├── gizmo.go │ └── setup.go
這裡分為了兩個 Go 文件,接下來詳細講每一個 Go 文件的作用。
代碼?:註冊 caddy plugin
首先我們看到 setup.go
setup.go
創建 setup.go 文件並寫入以下信息
import "github.com/mholt/caddy" func init() { caddy.RegisterPlugin("gizmo", caddy.Plugin{ ServerType: "http", Action: setup, }) }
這裡是 建立了一個新插件,caddy 包來做到插件的註冊。
- 注意到 「gizmo」 這是 插件的名字,同時也是指令的名字,請為你的插件取一個獨一無二的名字吧。(注意:名字需要是單詞小寫哦。)
- 因為是針對 HTTP 服務器的插件,所以 ServerType 字段值是 「http」
- 另一個設置的字段是 setup ,實際上,我們接下來會填充這個函數的邏輯。它的作用就是將我們插件的處理邏輯安裝到 Caddy 中。
setup
現在我們來實現 setup 函數
假如我們希望在Caddyfile中有一行這樣的行:
gizmo foobar
我們可以得到剛才所說的 c.Next()
第一個參數(「foobar」)的值,如下所示:
for c.Next() { // skip the directive name if !c.NextArg() { // expect at least one value return c.ArgErr() // otherwise it's an error } value := c.Val() // use the value }
我們首先注意到, c.Next() 是真正我們讀取 caddyfile 邏輯的地方,caddyfile 就是配置服務器的配置文件的名字。我們注意到,這裡的操作實際上是使用 caddy.Controller 來實現的。它的存在 讓編寫插件的開發者只需要關注如何使用它來執行你的命令,這是一項優秀的設計,有興趣可以看我的源碼閱讀部分關於 Plugin 的具體實現。
在 Caddy 解析了Caddyfile之後,它將迭代每個指令名(按照服務器類型規定的順序),並在每次遇到指令名時調用指令的setup函數。setup函數的職責是解析指令的標識並配置自己。
您可以通過遍歷c.Next()
來解析為指令提供的標識,只要有更多的標識需要解析,那麼c.Next()
就會返回true
。由於一個指令可能出現多次,你必須遍歷c.Next()
以獲得所有出現的指令並使用第一個標識(即指令名)。
有關caddyfile包,請參閱godoc以了解如何更充分地使用分發器,並查看任何其他現有插件。
代碼 ?:Handler 實現
gizmo.go:
查看httpserver包的godoc。最重要的兩種類型是httpserver.Handler和httpserver.Middleware。
Handler
是一個處理HTTP請求的函數。Middleware
是一種連接Handler
的方式。
Caddy將負責為你設置HTTP服務器的所有簿記(bookkeeping)工作,但是你需要實現這兩種類型。
Struct
httpserver.Handler
是一個幾乎和http.Handler
完全一樣的接口,除了ServeHTTP
方法返回(int, error)
。
這個方法簽名遵循Go語言博客中關於與中間件相關的錯誤處理的建議。int
是HTTP狀態碼,error
應該被處理和/或記錄。有關這些返回值的詳細信息,請參閱godoc。
Handler
通常是一個結構體,至少包含一個Next
字段,用來鏈接下一個Handler
:
type gizmoHandler struct { next httpserver.Handler }
除了這些之外,可以添加一些自己使用的參數,考慮 grpc 的 plugin 實現,解釋放在代碼塊中的注釋中
type server struct { backendAddr string // 監聽地址 next httpserver.Handler // 作為中間件必須有的字段 backendIsInsecure bool // 是否啟用 Insecure() 選項,是 grpc 的一項配置 backendTLS *tls.Config // 關於 TLS 的使用的證書文件 wrappedGrpc *grpcweb.WrappedGrpcServer // 通過 grpcweb 的 協議實現 HTTP 請求等 }
這就是參考的一個 字段的使用。可以根據自己的需要,調整在 caddyfile 中讀取的指令應該如何配置。
httpserver.Handler
為了實現httpserver.Handler
接口,我們需要編寫一個名為ServeHTTP
的方法。這個方法是實際的處理程序函數,除非它自己處理完畢請求,否則它應該調用鏈中的下一個Handler
:即使用 g.next.ServeHTTP(w, r)
func (g gizmoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { return g.next.ServeHTTP(w, r) }
這裡只是框架,具體邏輯可以自行填充,可以參照已有的 Plugin 實現。
第二步,註冊 Middleware
然後我們可以進行第二步,將這個 handler 註冊到整個 caddy 的 http 調用鏈上。
我們需要回到 剛才的 setup.go 文件中,
回到設置函數。你剛剛解析了標識並使用所有適當的配置設置了中間件處理程序:
func setup(c *caddy.Controller) error { g := gizmoHandler{} // 用來實現 HTTPHandler 的 next 的結構,用來構建 中間件。也可以加入一些自己的字段 for c.Next() { // 獲取配置文件,並處理 } // 現在開始註冊中間件 httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { g.next = next return g }) return nil }
這樣,代碼部分就全部完成了。
下面我們查看需要注意的事項。實際上是關乎於怎樣將寫好的插件集成在 caddy 中。
排序
要做的事情是告訴服務器類型在進程的什麼地方執行你的指令。這一點很重要,因為其他指令可能會設置你所依賴的更原始的配置,因此執行指令的順序不能是隨意的。
每個服務器類型都有一個字符串列表,其中每個項都是一個指令的名稱。例如,查看HTTP服務器支持的指令列表。將指令添加到適當的位置。
插入你的插件
最後,不要忘記導入你的插件包!Caddy必須導入插件來註冊並執行它。這通常是在run.go的import
部分的尾部完成的:
_ "your/plugin/package/here"
請注意:包名前的_
是必需的。
總結
就是這樣!可以用你的插件來構建caddy,然後用你的新指令寫一個Caddyfile來查看它的運行情況。
雖然還沒完善的她只是一個框架,還不能做任何事情,但是她很簡單,很美不是嗎?她能幫你做任何事情。因為記住 caddy 的服務器是設置的非常抽象的。她就想 net 包中 conn 一樣完美的 接口設計,能夠兼容和擴展任何 需要 listen() 和 serve() 的東西,只要你的創造力足夠。
現在,發揮你的想像力,填充這個框架吧,可以參考我的簡單項目地址。
項目地址:https://github.com/yhyddr/quicksilver/tree/master/gosample/caddy-plugin
同時記得多多尋找別人的插件實現方式,你會找到讓你耳目一新的實現。https://www.yuque.com/fengyfei/idznuk/sumapn
參考
我剛編輯過哦
https://github.com/caddyserver/caddy/wiki/Writing-a-Plugin:-Directives
https://github.com/caddyserver/caddy/wiki/Writing-a-Plugin:-Server-Type
https://github.com/caddyserver/caddy/wiki/Writing-a-Plugin:-HTTP-Middleware