1. 程式人生 > >Caddy原始碼閱讀(二)啟動流程與 Event 事件通知

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

 載入 caddyfile 配置  =》生成 Input 資訊
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 一個外掛實