Caddy源碼閱讀(三)Caddyfile 解析 by Loader & Parser

Caddy源碼閱讀(三)Caddyfile 解析 by Loader & Parser

Preview

前文提到了 Caddy 的啓動流程 和 Run 函數,如今咱們順着啓動流程的第一步,讀取 Caddyfile 的 源碼閱讀開始。
git

image.png

Loader

概念

能夠理解爲讀取器。github

在 caddy 中 由 Loader 執行 裝載 配置文件的職能。看一下它的工做流程。數組

這個圖來源於 plugin.go 文件app

caddy-loader.svg

能夠看到這裏經過 Loader 解耦了 caddyfile 文件的讀取,因此把它放在了 plugin.go 文件中,做爲一個插件註冊在 caddy app 中。less

  • 能夠多定義 caddyfileloader 在圖中由三個長方形的表示爲數組
  • 自定義的 caddyfileloader 會實現 右半部分的 Loader 接口
  • caddyfileloader 會有對應讀取的 ServerType 的 name
  • 最後生產出的是 Input

下面咱們看它的實現 ide

保存

在 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 的方式工具

Usage

咱們自定義的時候,只須要實現一個  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)
}
複製代碼

擴展

兩個方法,

  1. 能夠註冊新的 Loader
  2. 或者設置爲默認的 Loader
// 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}
}
複製代碼

Logic

// 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 讀取。

誰來讀取它呢?
那麼幹活的主角登場啦!

Parser

文件結構

咱們想看到各個流程中的 Token 是如何被分析出來的,須要知道,這裏的 Token 表明着 caddyfile 中的每行選項配置

lexer.go

一個實用工具,它能夠從 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

Parser.go

serverBlock

實際上, 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 等操做的。

parse

在 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 ,是令牌分發器,咱們下面立刻討論

allTokens

在 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
}
複製代碼

Dispenser

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 令牌消費

咱們看一下縱覽

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

Dispenser 的關鍵是 把不一樣的 tokens 轉換成 Directives 命令來執行

dispenser 中由 cursor 來進行 Token 數組中的迭代

關鍵在於移動 cursor 索引的函數

須要注意到的是 Dispenser 中的函數實際上都是 獲取 tokens 的函數,意思是,在 Dispenser 中不會作任何配置,而是在相應的 controller.go ,caddy.go 中使用請看下集,Plugin 的邏輯和相應的配置如何更改

相關文章
相關標籤/搜索