前文提到了 Caddy 的啓動流程 和 Run 函數,如今咱們順着啓動流程的第一步,讀取 Caddyfile 的 源碼閱讀開始。
git
能夠理解爲讀取器。github
在 caddy 中 由 Loader
執行 裝載 配置文件的職能。看一下它的工做流程。數組
這個圖來源於 plugin.go 文件app
能夠看到這裏經過 Loader
解耦了 caddyfile 文件的讀取,因此把它放在了 plugin.go 文件中,做爲一個插件註冊在 caddy app 中。less
在 plugin.go
中有一個全局變量保存svg
// caddyfileLoaders is the list of all Caddyfile loaders
// in registration order.
caddyfileLoaders []caddyfileLoader
複製代碼
咱們看這個接口函數
type Loader interface {
Load(serverType string) (Input, error)
}
複製代碼
這是用來 load caddyfile 的 你能夠爲本身的全部獨有的 server 自定義讀取 caddyfile 的方式工具
咱們自定義的時候,只須要實現一個 LoaderFunc 便可。由於它使用了 相似 http.HandlerFunc 的模式,幫你自動實現了上文所說的 Loader 接口ui
// LoaderFunc is a convenience type similar to http.HandlerFunc
// that allows you to use a plain function as a Load() method.
type LoaderFunc func(serverType string) (Input, error) // Load loads a Caddyfile. func (lf LoaderFunc) Load(serverType string) (Input, error) {
return lf(serverType)
}
複製代碼
兩個方法,
// RegisterCaddyfileLoader registers loader named name.
func RegisterCaddyfileLoader(name string, loader Loader) {
caddyfileLoaders = append(caddyfileLoaders, caddyfileLoader{name: name, loader: loader})
}
// SetDefaultCaddyfileLoader registers loader by name
// as the default Caddyfile loader if no others produce
// a Caddyfile. If another Caddyfile loader has already
// been set as the default, this replaces it.
//
// Do not call RegisterCaddyfileLoader on the same
// loader; that would be redundant.
func SetDefaultCaddyfileLoader(name string, loader Loader) {
defaultCaddyfileLoader = caddyfileLoader{name: name, loader: loader}
}
複製代碼
// loadCaddyfileInput iterates the registered Caddyfile loaders
// and, if needed, calls the default loader, to load a Caddyfile.
// It is an error if any of the loaders return an error or if
// more than one loader returns a Caddyfile.
func loadCaddyfileInput(serverType string) (Input, error) {
var loadedBy string
var caddyfileToUse Input
for _, l := range caddyfileLoaders {
cdyfile, err := l.loader.Load(serverType)
if err != nil {
return nil, fmt.Errorf("loading Caddyfile via %s: %v", l.name, err)
}
if cdyfile != nil {
if caddyfileToUse != nil {
return nil, fmt.Errorf("Caddyfile loaded multiple times; first by %s, then by %s", loadedBy, l.name)
}
loaderUsed = l
caddyfileToUse = cdyfile
loadedBy = l.name
}
}
if caddyfileToUse == nil && defaultCaddyfileLoader.loader != nil {
cdyfile, err := defaultCaddyfileLoader.loader.Load(serverType)
if err != nil {
return nil, err
}
if cdyfile != nil {
loaderUsed = defaultCaddyfileLoader
caddyfileToUse = cdyfile
}
}
return caddyfileToUse, nil
}
複製代碼
很輕鬆的看到,裝載 caddyfile 的邏輯 是嘗試調用 全部的 Loader 而且重複裝載會報錯。
若是嘗試過以後裝載失敗,則使用 默認的 defaultCaddyfileLoader
這裏能夠看到最終流程是 caddyfile
-> caddy.Input
那麼這個 Input
是什麼呢?
實際上 Input
就是 caddyfile 在代碼中的映射。能夠理解爲,caddyfile 轉化爲了 Input
給 caddy 讀取。
咱們想看到各個流程中的 Token
是如何被分析出來的,須要知道,這裏的 Token
表明着 caddyfile 中的每行選項配置
一個實用工具,它能夠從 Reader
獲取一個token
接一個token
的值。經過 next()
函數token
是一個單詞,token
由空格分隔。若是一個單詞包含空格,能夠用引號括起來。
即 lexer
用來作 caddyfile
的語法分析 被其餘程序調用
next()
函數就是 lexer 用來提供功能的函數
// next loads the next token into the lexer.
// A token is delimited by whitespace, unless
// the token starts with a quotes character (")
// in which case the token goes until the closing
// quotes (the enclosing quotes are not included).
// Inside quoted strings, quotes may be escaped
// with a preceding \ character. No other chars
// may be escaped. The rest of the line is skipped
// if a "#" character is read in. Returns true if
// a token was loaded; false otherwise.
func (l *lexer) next() bool {
var val []rune
var comment, quoted, escaped bool
makeToken := func() bool {
l.token.Text = string(val)
return true
}
for {
ch, _, err := l.reader.ReadRune()
if err != nil {
if len(val) > 0 {
return makeToken()
}
if err == io.EOF {
return false
}
panic(err)
}
if quoted {
if !escaped {
if ch == '\\' {
escaped = true
continue
} else if ch == '"' {
quoted = false
return makeToken()
}
}
if ch == '\n' {
l.line++
}
if escaped {
// only escape quotes
if ch != '"' {
val = append(val, '\\')
}
}
val = append(val, ch)
escaped = false
continue
}
if unicode.IsSpace(ch) {
if ch == '\r' {
continue
}
if ch == '\n' {
l.line++
comment = false
}
if len(val) > 0 {
return makeToken()
}
continue
}
if ch == '#' {
comment = true
}
if comment {
continue
}
if len(val) == 0 {
l.token = Token{Line: l.line}
if ch == '"' {
quoted = true
continue
}
}
val = append(val, ch)
}
}
複製代碼
他會根據 caddyfile 定義的寫法,進行多種判斷來實現分詞
理解了 next
函數,就很容易知道如何分析一塊選項的 token
了,不過都是 next()
的包裝函數罷了。
這就是 lexer.go 中的解讀,接下來咱們看 parse.go
實際上, ServerBlock 存儲的是 一組 token 信息,
//ServerBlock associates any number of keys
//(usually addresses of some sort) with tokens
//(grouped by directive name).
type ServerBlock struct {
Keys []string
Tokens map[string][]Token
}
複製代碼
它包含的 Token 正是 Parser 在 Caddyfile 中得來的。
context 還負責生成 caddy 管理的 Server,用來提供供 caddy Start 的信息
注意:這裏的 Server 是 TCPServer 和 UDPServer,用來 Listen 等操做的。
在 parser.go 中,由 Parse 函數進行生成 ServerBlock 的操做。
// Parse parses the input just enough to group tokens, in
// order, by server block. No further parsing is performed.
// Server blocks are returned in the order in which they appear.
// Directives that do not appear in validDirectives will cause
// an error. If you do not want to check for valid directives,
// pass in nil instead.
func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) {
p := parser{Dispenser: NewDispenser(filename, input), validDirectives: validDirectives}
return p.parseAll()
}
複製代碼
這裏使用的 Dispenser ,是令牌分發器,咱們下面立刻討論
在 praser.go 中使用 allTokens 進行 lexer 分詞生成 token
// allTokens lexes the entire input, but does not parse it.
// It returns all the tokens from the input, unstructured
// and in order.
func allTokens(input io.Reader) ([]Token, error) {
l := new(lexer)
err := l.load(input)
if err != nil {
return nil, err
}
var tokens []Token
for l.next() {
tokens = append(tokens, l.token)
}
return tokens, nil
}
複製代碼
allTokens
會在 新建 Dispenser
的時候調用
// NewDispenser returns a Dispenser, ready to use for parsing the given input.
func NewDispenser(filename string, input io.Reader) Dispenser {
tokens, _ := allTokens(input) // ignoring error because nothing to do with it
return Dispenser{
filename: filename,
tokens: tokens,
cursor: -1,
}
}
複製代碼
如此,整個解析流程就串起來了, lexer
負責詞法分析,Parse
用於將一組 tokens
分類(由於不少的插件的設置稍微複雜一些),Dispenser
負責分析去的 tokens
令牌消費
Dispenser
的關鍵是 把不一樣的 tokens
轉換成 Directives
命令來執行
dispenser
中由 cursor
來進行 Token
數組中的迭代
關鍵在於移動 cursor
索引的函數
須要注意到的是 Dispenser
中的函數實際上都是 獲取 tokens
的函數,意思是,在 Dispenser
中不會作任何配置,而是在相應的 controller.go ,caddy.go 中使用請看下集,Plugin 的邏輯和相應的配置如何更改