Caddy原始碼閱讀(二)啟動流程與 Event 事件通知
Caddy原始碼閱讀(二)啟動流程與 Event 事件通知
Preface
Caddy 是 Go 語言構建的輕量配置化伺服器。https://github.com/caddyserver/caddy
Caddy 整個軟體可以說是由不同的 外掛 堆砌起來的。自己本身僅提供 Plugin 的註冊執行邏輯和 Server 的監聽服務功能。
學習 caddy 的原始碼,實際上是學習 如何構建一個 鬆耦合的 抽象 Plugin 設計,即模組化插拔的做法。
所以我們的原始碼閱讀,圍繞 Caddy 為 Plugin 提供的基礎設施,和 Plugin 自身邏輯。
下面我們從第一步,啟動流程開始閱讀。
之後的路徑應該是 Caddyfile 的解析,解析出的 配置檔案如何消費,配置完成的伺服器如何服務。
Overview
Package
這是 caddy 包的結構
main.go
一切的開始 ---
我們檢視 在 caddy 資料夾下的 main.go 函式。
這是 上圖 caddy 資料夾下的目錄結構。
其中 run.go 我們在上一篇文章閱讀完成
main.go 的 Trick
在 caddy 資料夾中的 main 函式啟動 caddy 伺服器。實際執行的是 run.go 中的檔案,這是方便測試使用
看 main.go的程式碼
通過改變 run 變數的值來方便測試,可以學習一下。
啟動流程
啟動 caddy 的流程
caddyfileLoader
Context
=》 生成 Server
caddyfile 示例
caddyfile
簡單示例:
Instance
是執行操作的 Server 例項,可以看到幾個主要的操作都是在他身上
Server
兩種監聽模式 TCP
UDP
我們首先關心的是 Start()
啟動伺服器。
啟動伺服器
傳送 StartupEvent, 參照下文中 Event 理解
// Executes Startup events caddy.EmitEvent(caddy.StartupEvent, nil)
讀取配置檔案:參照我的接下來的文章 Caddy-解析Caddyfile
caddyfileinput, err := caddy.LoadCaddyfile(serverType)
啟動:
instance, err := caddy.Start(caddyfileinput)
傳送 InstanceStartupEvent
caddy.EmitEvent(caddy.InstanceStartupEvent, instance)
Start()
// Start your engines
instance, err := caddy.Start(caddyfileinput)
if err != nil {
mustLogFatalf("%v", err)
}
閱讀完程式碼,畫一張圖幫助理解
這裡除了 Instance
之外還有兩個新名詞
Controller
:它是用來幫助 Directives
設定它自身的,通過讀取 Token
,這裡的 Directives
實際上對應的就是上文所說的 caddyfile 中的配置檔案選項。
這一點請參照 Caddy(三)中
Loader
下的excuteDirective
理解。
Token
:是 caddy 自己的 詞法分析器 解析 caddyfile 配置檔案出的選項的標記。
這一點請Caddy(三)中
Loader
中的 Parser 理解
我們來看順序,第一遍從頂向下看。
第一個是 Input
,這是 caddyfile
的變數結構,他可以通過 Start()
方法新建例項 Instance
Instance
通過從 caddyfile
讀取到資訊的 Input
生成 Context
攜帶資訊的 Context
承擔 新建 Server
的任務
Context
讀取 caddyfile
解析出的 ServerBlock
配置伺服器
ServerBlock
包含 不同的 Tokens
他們會轉換為 Directive
Directive
會被 Controller
消費,用於配置外掛 安裝到伺服器上
值得注意的是 Controller
更改的是 Instance
對於 http
伺服器來說還會增加 http
服務的中介軟體
如果不理解,首先記住 caddy 是 配置的 模組化的伺服器,
通過 caddyfile
配置 -> caddyfile
讀取它 -> Loader
解析配置目標-> token
& directives
進行配置 -> controller
& setup
啟動 -> instance
& Start()
記住這個流程就能理解了。
Event 事件通知啟動外掛
引入
我們看到,在 caddy 的 run.go 中有一行程式碼是
caddy.EmitEvent(caddy.StartupEvent, nil)
這就是 caddy 中的 事件通知系統,通知的是所有的 plugin。
變數
在 caddy/plugin.go 包中
// eventHooks is a map of hook name to Hook. All hooks plugins
// must have a name.
eventHooks = &sync.Map{}
是一個儲存所有 plugin hook 的 sync.Map{}
這個標準包的 Map 是併發安全的, 通常我們使用 Load() 或者 LoadOrStore() 方法存讀資訊,Range() 方法遍歷,如果你需要,可以引入你的 Go 程式中。
Logic
看內在實現
// EmitEvent executes the different hooks passing the EventType as an
// argument. This is a blocking function. Hook developers should
// use 'go' keyword if they don't want to block Caddy.
func EmitEvent(event EventName, info interface{}) {
eventHooks.Range(func(k, v interface{}) bool {
err := v.(EventHook)(event, info)
if err != nil {
log.Printf("error on '%s' hook: %v", k.(string), err)
}
return true
})
}
很簡單,上文提過,eventHooks.Range 是遍歷資訊,會遍歷所有儲存的 EventHook 函式並執行。
那麼 Plugin 想使用接收某一個事件通知做相應操作的時候,只需把自己的 EventHook 函式註冊到這個 map 中
// eventHooks is a map of hook name to Hook. All hooks plugins
// must have a name.
eventHooks = &sync.Map{}
使用 RegisterEventHook
註冊
type EventHook func(eventType EventName, eventInfo interface{}) error
// RegisterEventHook plugs in hook. All the hooks should register themselves
// and they must have a name.
func RegisterEventHook(name string, hook EventHook) {
if name == "" {
panic("event hook must have a name")
}
_, dup := eventHooks.LoadOrStore(name, hook)
if dup {
panic("hook named " + name + " already registered")
}
}
那麼可以監聽哪些事件呢?在 Plugin 中有定義常量
// Define names for the various events
const (
StartupEvent EventName = "startup"
ShutdownEvent = "shutdown"
CertRenewEvent = "certrenew"
InstanceStartupEvent = "instancestartup"
InstanceRestartEvent = "instancerestart"
)
啟動,關閉,重新整理證書,這裡提到的 Instance 是 caddy 中的 Server 例項
結語
我們概覽了 caddy 的 run 和 Start 啟動流程,接下來我們會繼續深入瞭解 Caddy 每個部分流程。
可以看之前的文章
Caddy原始碼閱讀(一)Run詳解
和之後的
Caddy原始碼閱讀(三)Caddyfile 解析 by Loader & Parser
Caddy原始碼閱讀(四)Plugin & Controller 安裝外掛
Caddy原始碼閱讀(五) Instance & Server
caddy-plugins(一)自定義外掛
caddy-plugins(二)caddy-grpc 一個外掛實