Caddy源碼閱讀(二)啓動流程與 Event 事件通知

Caddy源碼閱讀(二)啓動流程與 Event 事件通知

Preface

Caddy 是 Go 語言構建的輕量配置化服務器。https://github.com/caddyserver/caddy

Caddy 整個軟件可以說是由不同的 插件 堆砌起來的。自己本身僅提供 Plugin 的註冊運行邏輯和 Server 的監聽服務功能。

學習 caddy 的源碼,實際上是學習 如何構建一個 鬆耦合的 抽象 Plugin 設計,即模塊化插拔的做法。

所以我們的源碼閱讀,圍繞 Caddy 爲 Plugin 提供的基礎設施,和 Plugin 自身邏輯。

caddy-overview (2).png
下面我們從第一步,啓動流程開始閱讀。
之後的路徑應該是  Caddyfile 的解析,解析出的 配置文件如何消費,配置完成的服務器如何服務。

Overview

Package

這是 caddy 包的結構
image.pngimage.png

main.go

一切的開始  --- 
我們查看 在 caddy 文件夾下的 main.go 函數。

這是 上圖 caddy 文件夾下的目錄結構。

其中 run.go 我們在上一篇文章閱讀完成

main.go 的 Trick

在 caddy 文件夾中的 main 函數啓動 caddy 服務器。實際運行的是 run.go 中的文件,這是方便測試使用
main.go 的代碼

image.png

通過改變 run 變量的值來方便測試,可以學習一下。

啓動流程 

啓動 caddy 的流程

屏幕快照 2019-08-04 下午6.34.21.png

 caddyfileLoader 加載 caddyfile 配置  =》生成 Input 信息
Context =》 生成 Server

caddyfile 示例

caddyfile 簡單示例:
image.png

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)
}

閱讀完代碼,畫一張圖幫助理解
屏幕快照 2019-08-04 下午6.35.03.png

這裏除了 Instance 之外還有兩個新名詞

 Controller:它是用來幫助 Directives 設置它自身的,通過讀取 Token,這裏的 Directives 實際上對應的就是上文所說的 caddyfile 中的配置文件選項。

這一點請參照 Caddy(三)Loader 下的 excuteDirective 理解。

 Token :是 caddy 自己的 詞法分析器 解析 caddyfile 配置文件出的選項的標記。

這一點請Caddy(三)中 Loader 中的 Parser 理解

caddy.Start.svg

我們來看順序,第一遍從頂向下看。

第一個是 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-event.svg

引入

我們看到,在 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 一個插件實例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章