如何為 caddy 添寫(xiě)自定義插件

如何為 caddy 添寫(xiě)自定義插件

項(xiàng)目地址:https://github.com/yhyddr/quicksilver/tree/master/gosample/caddy-plugin


<a name="RVxtJ"></a>

前言

Caddy附帶一個(gè)HTTP服務(wù)器,但是你可以實(shí)現(xiàn)其他服務(wù)器類型并將它們插入Caddy中。其他類型的服務(wù)器可以是SSH、SFTP、TCP、內(nèi)部使用的其他東西等等。<br />
<br />對(duì)于Caddy來(lái)說(shuō),服務(wù)器的概念是任何可以Listen()Serve()的東西。這意味著什么、如何運(yùn)作都取決于你。你可以自由地發(fā)揮你的創(chuàng)造力去使用它。<br />
<br />那么怎樣去擴(kuò)展 Caddy 呢?<br />不同的服務(wù)器類型,可以根據(jù)自己的需要定制不同的插件。我們?cè)谶@里,通過(guò)添加最簡(jiǎn)單的不做任何事的插件,來(lái)熟悉如何擴(kuò)展 Caddy 服務(wù)器。<br />

<a name="bMjmJ"></a>

Plugin for HTTP

我們會(huì)一步一步構(gòu)建出一個(gè) HTTP Plugin 的框架,到時(shí)候你只需要填充自己處理邏輯即可!那還等什么,讓我們開(kāi)始吧。<br />
<br />構(gòu)建一個(gè) HTTP Plugin ,代碼部分僅需要兩步,注意事項(xiàng)也有兩個(gè)。<br />

<a name="hyj6K"></a>

創(chuàng)建一個(gè) Go Package

首先為 caddy 創(chuàng)建一個(gè) 插件的 Go Package ,你可以新建一個(gè)文件夾達(dá)到這個(gè)效果。比如

├── caddy-plugin
│   ├── gizmo.go
│   └── setup.go

這里分為了兩個(gè) Go 文件,接下來(lái)詳細(xì)講每一個(gè) Go 文件的作用。

<a name="nmOnZ"></a>

代碼??:注冊(cè) caddy plugin

首先我們看到 setup.go

<a name="HXIC2"></a>

setup.go

創(chuàng)建 setup.go 文件并寫(xiě)入以下信息

import "github.com/mholt/caddy"

func init() {
    caddy.RegisterPlugin("gizmo", caddy.Plugin{
        ServerType: "http",
        Action:     setup,
    })
}

這里是 建立了一個(gè)新插件,caddy 包來(lái)做到插件的注冊(cè)。

  1. 注意到 “gizmo” 這是 插件的名字,同時(shí)也是指令的名字,請(qǐng)為你的插件取一個(gè)獨(dú)一無(wú)二的名字吧。(注意:名字需要是單詞小寫(xiě)哦。)
  2. 因?yàn)槭轻槍?duì) HTTP 服務(wù)器的插件,所以 ServerType 字段值是 “http”
  3. 另一個(gè)設(shè)置的字段是 setup ,實(shí)際上,我們接下來(lái)會(huì)填充這個(gè)函數(shù)的邏輯。它的作用就是將我們插件的處理邏輯安裝到 Caddy 中。

<a name="6sJEa"></a>

setup

現(xiàn)在我們來(lái)實(shí)現(xiàn) setup 函數(shù) <br />
<br />假如我們希望在Caddyfile中有一行這樣的行:

gizmo foobar

<br />我們可以得到剛才所說(shuō)的 c.Next() 第一個(gè)參數(shù)(“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 就是配置服務(wù)器的配置文件的名字。我們注意到,這里的操作實(shí)際上是使用 caddy.Controller 來(lái)實(shí)現(xiàn)的。它的存在 讓編寫(xiě)插件的開(kāi)發(fā)者只需要關(guān)注如何使用它來(lái)執(zhí)行你的命令,這是一項(xiàng)優(yōu)秀的設(shè)計(jì),有興趣可以看我的源碼閱讀部分關(guān)于 Plugin 的具體實(shí)現(xiàn)。

在 Caddy 解析了Caddyfile之后,它將迭代每個(gè)指令名(按照服務(wù)器類型規(guī)定的順序),并在每次遇到指令名時(shí)調(diào)用指令的setup函數(shù)。setup函數(shù)的職責(zé)是解析指令的標(biāo)識(shí)并配置自己。<br />
<br />您可以通過(guò)遍歷c.Next()來(lái)解析為指令提供的標(biāo)識(shí),只要有更多的標(biāo)識(shí)需要解析,那么c.Next()就會(huì)返回true。由于一個(gè)指令可能出現(xiàn)多次,你必須遍歷c.Next()以獲得所有出現(xiàn)的指令并使用第一個(gè)標(biāo)識(shí)(即指令名)。<br />有關(guān)caddyfile包,請(qǐng)參閱godoc以了解如何更充分地使用分發(fā)器,并查看任何其他現(xiàn)有插件。

<a name="fzu7b"></a>

代碼 ??:Handler 實(shí)現(xiàn)

<a name="iyY6W"></a>

gizmo.go:

查看httpserver包的godoc。最重要的兩種類型是httpserver.Handlerhttpserver.Middleware。

  1. Handler是一個(gè)處理HTTP請(qǐng)求的函數(shù)。
  2. Middleware是一種連接Handler的方式。

Caddy將負(fù)責(zé)為你設(shè)置HTTP服務(wù)器的所有簿記(bookkeeping)工作,但是你需要實(shí)現(xiàn)這兩種類型。<br />

<a name="sPXzP"></a>

Struct

httpserver.Handler是一個(gè)幾乎和http.Handler完全一樣的接口,除了ServeHTTP方法返回(int, error)。<br />這個(gè)方法簽名遵循Go語(yǔ)言博客中關(guān)于與中間件相關(guān)的錯(cuò)誤處理的建議。<br />int是HTTP狀態(tài)碼,error應(yīng)該被處理和/或記錄。有關(guān)這些返回值的詳細(xì)信息,請(qǐng)參閱godoc。<br />
<br />Handler通常是一個(gè)結(jié)構(gòu)體,至少包含一個(gè)Next字段,用來(lái)鏈接下一個(gè)Handler

type gizmoHandler struct {
    next httpserver.Handler
}

<br />除了這些之外,可以添加一些自己使用的參數(shù),考慮 grpc 的 plugin 實(shí)現(xiàn),解釋放在代碼塊中的注釋中

type server struct {
    backendAddr       string // 監(jiān)聽(tīng)地址
    next              httpserver.Handler // 作為中間件必須有的字段
    backendIsInsecure bool // 是否啟用 Insecure() 選項(xiàng),是 grpc 的一項(xiàng)配置
    backendTLS        *tls.Config // 關(guān)于 TLS 的使用的證書(shū)文件
    wrappedGrpc       *grpcweb.WrappedGrpcServer // 通過(guò) grpcweb 的 協(xié)議實(shí)現(xiàn) HTTP 請(qǐng)求等
}

這就是參考的一個(gè) 字段的使用??梢愿鶕?jù)自己的需要,調(diào)整在 caddyfile 中讀取的指令應(yīng)該如何配置。

<a name="K5EDA"></a>

httpserver.Handler

為了實(shí)現(xiàn)httpserver.Handler接口,我們需要編寫(xiě)一個(gè)名為ServeHTTP的方法。這個(gè)方法是實(shí)際的處理程序函數(shù),除非它自己處理完畢請(qǐng)求,否則它應(yīng)該調(diào)用鏈中的下一個(gè)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 實(shí)現(xiàn)。<br />

<a name="RiOqc"></a>

第二步,注冊(cè) Middleware

然后我們可以進(jìn)行第二步,將這個(gè) handler 注冊(cè)到整個(gè) caddy 的 http 調(diào)用鏈上。

我們需要回到 剛才的 setup.go 文件中,<br />回到設(shè)置函數(shù)。你剛剛解析了標(biāo)識(shí)并使用所有適當(dāng)?shù)呐渲迷O(shè)置了中間件處理程序:

func setup(c *caddy.Controller) error {
    g := gizmoHandler{} // 用來(lái)實(shí)現(xiàn) HTTPHandler 的 next 的結(jié)構(gòu),用來(lái)構(gòu)建 中間件。也可以加入一些自己的字段

    for c.Next() {
        // 獲取配置文件,并處理
    }
    // 現(xiàn)在開(kāi)始注冊(cè)中間件
    httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
        g.next = next
        return g
    })
    
    return nil
}

這樣,代碼部分就全部完成了。

下面我們查看需要注意的事項(xiàng)。實(shí)際上是關(guān)乎于怎樣將寫(xiě)好的插件集成在 caddy 中。

<a name="WTOo3"></a>

排序

要做的事情是告訴服務(wù)器類型在進(jìn)程的什么地方執(zhí)行你的指令。這一點(diǎn)很重要,因?yàn)槠渌噶羁赡軙?huì)設(shè)置你所依賴的更原始的配置,因此執(zhí)行指令的順序不能是隨意的。<br />
<br />每個(gè)服務(wù)器類型都有一個(gè)字符串列表,其中每個(gè)項(xiàng)都是一個(gè)指令的名稱。例如,查看HTTP服務(wù)器支持的指令列表。將指令添加到適當(dāng)?shù)奈恢谩?lt;br />

<a name="c75IF"></a>

插入你的插件

最后,不要忘記導(dǎo)入你的插件包!Caddy必須導(dǎo)入插件來(lái)注冊(cè)并執(zhí)行它。這通常是在run.goimport部分的尾部完成的:<br />

_ "your/plugin/package/here"

請(qǐng)注意:包名前的_是必需的。

<a name="1aHEv"></a>

總結(jié)

就是這樣!可以用你的插件來(lái)構(gòu)建caddy,然后用你的新指令寫(xiě)一個(gè)Caddyfile來(lái)查看它的運(yùn)行情況。<br />雖然還沒(méi)完善的她只是一個(gè)框架,還不能做任何事情,但是她很簡(jiǎn)單,很美不是嗎?她能幫你做任何事情。因?yàn)橛涀?caddy 的服務(wù)器是設(shè)置的非常抽象的。她就想 net 包中 conn 一樣完美的 接口設(shè)計(jì),能夠兼容和擴(kuò)展任何 需要 listen() 和 serve() 的東西,只要你的創(chuàng)造力足夠。<br />
<br />現(xiàn)在,發(fā)揮你的想象力,填充這個(gè)框架吧,可以參考我的簡(jiǎn)單項(xiàng)目地址。<br />項(xiàng)目地址:https://github.com/yhyddr/quicksilver/tree/master/gosample/caddy-plugin<br />
<br />同時(shí)記得多多尋找別人的插件實(shí)現(xiàn)方式,你會(huì)找到讓你耳目一新的實(shí)現(xiàn)。https://www.yuque.com/fengyfei/idznuk/sumapn<br />

<a name="jCZ1q"></a>

參考

image.png
image.png
<br />我剛編輯過(guò)哦<br />https://github.com/caddyserver/caddy/wiki/Writing-a-Plugin:-Directives<br />https://github.com/caddyserver/caddy/wiki/Writing-a-Plugin:-Server-Type<br />https://github.com/caddyserver/caddy/wiki/Writing-a-Plugin:-HTTP-Middleware

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容