前文提到了 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 的逻辑和相应的配置如何更改