Preview

前文提到了 Caddy 的启动流程 和 Run 函数,现在我们顺着启动流程的第一步,读取 Caddyfile 的 源码阅读开始。
image.png

Loader

概念

可以理解为读取器。

在 caddy 中 由 Loader 执行 装载 配置文件的职能。看一下它的工作流程。

这个图来源于 plugin.go 文件

caddy-loader.svg

可以看到这里通过 Loader 解耦了 caddyfile 文件的读取,所以把它放在了 plugin.go 文件中,作为一个插件注册在 caddy app 中。

  • 可以多定义 caddyfileloader 在图中由三个长方形的表示为数组
  • 自定义的 caddyfileloader 会实现 右半部分的 Loader 接口
  • caddyfileloader 会有对应读取的 ServerType 的 name
  • 最后生产出的是 Input

下面我们看它的实现

保存

plugin.go 中有一个全局变量保存

  1. // caddyfileLoaders is the list of all Caddyfile loaders
  2. // in registration order.
  3. caddyfileLoaders []caddyfileLoader

设计

我们看这个接口

  1. type Loader interface {
  2. Load(serverType string) (Input, error)
  3. }

这是用来 load caddyfile 的 你可以为自己的所有独有的 server 自定义读取 caddyfile 的方式

Usage

我们自定义的时候,只需要实现一个 LoaderFunc 即可。因为它使用了 类似 http.HandlerFunc 的模式,帮你自动实现了上文所说的 Loader 接口

  1. // LoaderFunc is a convenience type similar to http.HandlerFunc
  2. // that allows you to use a plain function as a Load() method.
  3. type LoaderFunc func(serverType string) (Input, error)
  4. // Load loads a Caddyfile.
  5. func (lf LoaderFunc) Load(serverType string) (Input, error) {
  6. return lf(serverType)
  7. }

扩展

两个方法,

  1. 可以注册新的 Loader
  2. 或者设置为默认的 Loader ```go // 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} }

  1. <a name="VMa94"></a>
  2. ### Logic
  3. ```go
  4. // loadCaddyfileInput iterates the registered Caddyfile loaders
  5. // and, if needed, calls the default loader, to load a Caddyfile.
  6. // It is an error if any of the loaders return an error or if
  7. // more than one loader returns a Caddyfile.
  8. func loadCaddyfileInput(serverType string) (Input, error) {
  9. var loadedBy string
  10. var caddyfileToUse Input
  11. for _, l := range caddyfileLoaders {
  12. cdyfile, err := l.loader.Load(serverType)
  13. if err != nil {
  14. return nil, fmt.Errorf("loading Caddyfile via %s: %v", l.name, err)
  15. }
  16. if cdyfile != nil {
  17. if caddyfileToUse != nil {
  18. return nil, fmt.Errorf("Caddyfile loaded multiple times; first by %s, then by %s", loadedBy, l.name)
  19. }
  20. loaderUsed = l
  21. caddyfileToUse = cdyfile
  22. loadedBy = l.name
  23. }
  24. }
  25. if caddyfileToUse == nil && defaultCaddyfileLoader.loader != nil {
  26. cdyfile, err := defaultCaddyfileLoader.loader.Load(serverType)
  27. if err != nil {
  28. return nil, err
  29. }
  30. if cdyfile != nil {
  31. loaderUsed = defaultCaddyfileLoader
  32. caddyfileToUse = cdyfile
  33. }
  34. }
  35. return caddyfileToUse, nil
  36. }

很轻松的看到,装载 caddyfile 的逻辑 是尝试调用 所有的 Loader 并且重复装载会报错。
如果尝试过之后装载失败,则使用 默认的 defaultCaddyfileLoader
这里可以看到最终流程是 caddyfile -> caddy.Input 那么这个 Input 是什么呢?

实际上 Input 就是 caddyfile 在代码中的映射。可以理解为,caddyfile 转化为了 Input 给 caddy 读取。

谁来读取它呢?
那么干活的主角登场啦!

Parser

文件结构

Caddy源码阅读(三)Caddyfile 解析 by Loader & Parser - 图5

我们想看到各个流程中的 Token 是如何被分析出来的,需要知道,这里的 Token 代表着 caddyfile 中的每行选项配置

lexer.go

一个实用工具,它可以从 Reader获取一个token接一个token的值。通过 next()函数
token 是一个单词,token由空格分隔。如果一个单词包含空格,可以用引号括起来。
lexer 用来做 caddyfile 的语法分析 被其他程序调用

词法分析

next()函数就是 lexer 用来提供功能的函数

  1. // next loads the next token into the lexer.
  2. // A token is delimited by whitespace, unless
  3. // the token starts with a quotes character (")
  4. // in which case the token goes until the closing
  5. // quotes (the enclosing quotes are not included).
  6. // Inside quoted strings, quotes may be escaped
  7. // with a preceding \ character. No other chars
  8. // may be escaped. The rest of the line is skipped
  9. // if a "#" character is read in. Returns true if
  10. // a token was loaded; false otherwise.
  11. func (l *lexer) next() bool {
  12. var val []rune
  13. var comment, quoted, escaped bool
  14. makeToken := func() bool {
  15. l.token.Text = string(val)
  16. return true
  17. }
  18. for {
  19. ch, _, err := l.reader.ReadRune()
  20. if err != nil {
  21. if len(val) > 0 {
  22. return makeToken()
  23. }
  24. if err == io.EOF {
  25. return false
  26. }
  27. panic(err)
  28. }
  29. if quoted {
  30. if !escaped {
  31. if ch == '\\' {
  32. escaped = true
  33. continue
  34. } else if ch == '"' {
  35. quoted = false
  36. return makeToken()
  37. }
  38. }
  39. if ch == '\n' {
  40. l.line++
  41. }
  42. if escaped {
  43. // only escape quotes
  44. if ch != '"' {
  45. val = append(val, '\\')
  46. }
  47. }
  48. val = append(val, ch)
  49. escaped = false
  50. continue
  51. }
  52. if unicode.IsSpace(ch) {
  53. if ch == '\r' {
  54. continue
  55. }
  56. if ch == '\n' {
  57. l.line++
  58. comment = false
  59. }
  60. if len(val) > 0 {
  61. return makeToken()
  62. }
  63. continue
  64. }
  65. if ch == '#' {
  66. comment = true
  67. }
  68. if comment {
  69. continue
  70. }
  71. if len(val) == 0 {
  72. l.token = Token{Line: l.line}
  73. if ch == '"' {
  74. quoted = true
  75. continue
  76. }
  77. }
  78. val = append(val, ch)
  79. }
  80. }

他会根据 caddyfile 定义的写法,进行多种判断来实现分词

理解了 next 函数,就很容易知道如何分析一块选项的 token 了,不过都是 next() 的包装函数罢了。
这就是 lexer.go 中的解读,接下来我们看 parse.go

Parser.go

serverBlock

实际上, ServerBlock 存储的是 一组 token 信息,

  1. //ServerBlock associates any number of keys
  2. //(usually addresses of some sort) with tokens
  3. //(grouped by directive name).
  4. type ServerBlock struct {
  5. Keys []string
  6. Tokens map[string][]Token
  7. }

它包含的 Token 正是 Parser 在 Caddyfile 中得来的。

context 还负责生成 caddy 管理的 Server,用来提供供 caddy Start 的信息
注意:这里的 Server 是 TCPServer 和 UDPServer,用来 Listen 等操作的。

parse

在 parser.go 中,由 Parse 函数进行生成 ServerBlock 的操作。

  1. // Parse parses the input just enough to group tokens, in
  2. // order, by server block. No further parsing is performed.
  3. // Server blocks are returned in the order in which they appear.
  4. // Directives that do not appear in validDirectives will cause
  5. // an error. If you do not want to check for valid directives,
  6. // pass in nil instead.
  7. func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) {
  8. p := parser{Dispenser: NewDispenser(filename, input), validDirectives: validDirectives}
  9. return p.parseAll()
  10. }

这里使用的 Dispenser ,是令牌分发器,我们下面马上讨论

allTokens

在 praser.go 中使用 allTokens 进行 lexer 分词生成 token

  1. // allTokens lexes the entire input, but does not parse it.
  2. // It returns all the tokens from the input, unstructured
  3. // and in order.
  4. func allTokens(input io.Reader) ([]Token, error) {
  5. l := new(lexer)
  6. err := l.load(input)
  7. if err != nil {
  8. return nil, err
  9. }
  10. var tokens []Token
  11. for l.next() {
  12. tokens = append(tokens, l.token)
  13. }
  14. return tokens, nil
  15. }

Dispenser

allTokens 会在 新建 Dispenser 的时候调用

  1. // NewDispenser returns a Dispenser, ready to use for parsing the given input.
  2. func NewDispenser(filename string, input io.Reader) Dispenser {
  3. tokens, _ := allTokens(input) // ignoring error because nothing to do with it
  4. return Dispenser{
  5. filename: filename,
  6. tokens: tokens,
  7. cursor: -1,
  8. }
  9. }

如此,整个解析流程就串起来了, lexer 负责词法分析,Parse 用于将一组 tokens 分类(因为很多的插件的设置稍微复杂一些),Dispenser 负责分析去的 tokens 令牌消费

我们看一下纵览

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

Dispenser 的关键是 把不同的 tokens 转换成 Directives 命令来执行

dispenser 中由 cursor 来进行 Token 数组中的迭代

关键在于移动 cursor 索引的函数

需要注意到的是 Dispenser 中的函数实际上都是 获取 tokens 的函数,意思是,在 Dispenser 中不会做任何配置,而是在相应的 controller.go ,caddy.go 中使用
请看下集,Plugin 的逻辑和相应的配置如何更改