Caddy 源码全解析


caddy源码全解析

main.go 的代码

通过改变 run 变量的值来方便测试,可以学习一下。

Event 理解

// Executes Startup events
caddy.EmitEvent(caddy.StartupEvent, nil)

读取配置文件:

caddyfileinput, err := caddy.LoadCaddyfile(serverType)

启动:

instance, err := caddy.Start(caddyfileinput)

发送 InstanceStartupEvent

caddy.EmitEvent(caddy.InstanceStartupEvent, instance

excuteDirective 理解。
 Token :是 caddy 自己的 词法分析器 解析 caddyfile 配置文件出的选项的标记。这一点请参照下文中 Loader 中的 Parser 理解

如果不理解,首先记住 caddy 是配置化的服务器,
通过 caddyfile 配置 ->
那么肯定要读取它啦 ->
然后要解析它配置的到底是那些东西 ->
之后呢,就要让配置的目标做到 caddyfile 中声明的更改。
记住这个流程继续看几遍就能理解了。

GracefulServer 包含 Stopper 的接口实现了优雅退出,这是拦截了 系统 signal 的信号之后执行的结果,意在意外中断的时候保存好需要保存的东西。

它同时包含着 WrapListener 函数。可以看出,他用来做中间件。

// WrapListener wraps a listener with the
	// listener middlewares configured for this
	// server, if any.
	WrapListener(net.Listener) net.Listener

caddy.go 中

首先我们看一下 它的结构了解下它可能有的功能

struct

type Instance struct {
	serverType string
	caddyfileInput Input
	wg *sync.WaitGroup
	context Context
	servers []ServerListener
	OnFirstStartup  []func() error // starting, not as part of a restart
	OnStartup       []func() error // starting, even as part of a restart
	OnRestart       []func() error // before restart commences
	OnRestartFailed []func() error // if restart failed
	OnShutdown      []func() error // stopping, even as part of a restart
	OnFinalShutdown []func() error // stopping, not as part of a restart
	Storage   map[interface{}]interface{}
	StorageMu sync.RWMutex
}

12-factor 中的 第九条 Disposability 相符合。意思是每一次重载实例 Instance 即使是在进程中重载,也不会出现数据相互影响到情况,保持幂等。


虽然 Instance 操作着众多操作,但是我们却不能从它讲起,从农村包围城市,渐渐了解 Instance 能调用的函数,自然 Instance 的功能就清晰了。

EmitEvent

通过传入函数为参数调用回调函数

// 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 //注意这里返回的是 true
	})
}

这里使用的 Range 函数,实际上是把事件信息给每一个上述提过 map 中的 EventHook 提供参数进行回调执行,按顺序调用,但是如果 传入函数返回 false ,迭代遍历执行就会中断。

可以知道,上文 Overview中启动服务器 所说的发送 caddy.StartupEvent 事件就是调用的

caddy.EmitEvent(caddy.StartupEvent, nil)

讲到这,相信已经对大致的流程有了一点框架的概念。

下面我们继续深入了解 在读取 caddyfile 文件的时候发生了什么。

plugin.go 文件

可以看到这里通过 Loader 解耦了 caddyfile 文件的读取,所以把它放在了 plugin.go 文件中,作为一个插件注册在 caddy app 中。
这里可以看到最终流程是 name -> caddy.Input 那么这个 Input 是什么呢?
实际上 Input 就是 caddyfile 在代码中的映射。可以理解为,caddyfile 转化为了 Input 给 caddy 读取。谁来读取它呢?
那么干活的主角登场啦!

Parser

next()

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

理解了 next 函数,就很容易知道如何分析一块选项的 token 了,不过都是 next() 的包装函数罢了。

excuteDirective

func executeDirectives(inst *Instance, filename string,
	directives []string, sblocks []caddyfile.ServerBlock, justValidate bool) error {
	// map of server block ID to map of directive name to whatever.
	storages := make(map[int]map[string]interface{})

	// It is crucial that directives are executed in the proper order.
	// We loop with the directives on the outer loop so we execute
	// a directive for all server blocks before going to the next directive.
	// This is important mainly due to the parsing callbacks (below).
	for _, dir := range directives {
		for i, sb := range sblocks {
			var once sync.Once
			if _, ok := storages[i]; !ok {
				storages[i] = make(map[string]interface{})
			}

			for j, key := range sb.Keys {
				// Execute directive if it is in the server block
				if tokens, ok := sb.Tokens[dir]; ok {
					controller := &Controller{
						instance:  inst,
						Key:       key,
						Dispenser: caddyfile.NewDispenserTokens(filename, tokens),
						OncePerServerBlock: func(f func() error) error {
							var err error
							once.Do(func() {
								err = f()
							})
							return err
						},
						ServerBlockIndex:    i,
						ServerBlockKeyIndex: j,
						ServerBlockKeys:     sb.Keys,
						ServerBlockStorage:  storages[i][dir],
					}

					setup, err := DirectiveAction(inst.serverType, dir)
					if err != nil {
						return err
					}

					err = setup(controller)
					if err != nil {
						return err
					}

					storages[i][dir] = controller.ServerBlockStorage // persist for this server block
				}
			}
		}

		if !justValidate {
			// See if there are any callbacks to execute after this directive
			if allCallbacks, ok := parsingCallbacks[inst.serverType]; ok {
				callbacks := allCallbacks[dir]
				for _, callback := range callbacks {
					if err := callback(inst.context); err != nil {
						return err
					}
				}
			}
		}
	}

	return nil
}

caddyfile 既然被解析完毕,那么就要开始执行配置更改了,这里实际上是 caddy.go 中的 函数,最后在 caddy 的 main.go 中调用来执行更改。

caddy.Listener 定义在 caddy.go 中,用来支持 零停机时间加载。

往上看到 Middleware 调用,我们来看看 errorsHandle 的结构

// ErrorHandler handles HTTP errors (and errors from other middleware).
type ErrorHandler struct {
	Next             httpserver.Handler
	GenericErrorPage string         // default error page filename
	ErrorPages       map[int]string // map of status code to filename
	Log              *httpserver.Logger
	Debug            bool // if true, errors are written out to client rather than to a log
}

可以看到,Next 字段明显是 Chain 调用的下一个 Handler 处理。事实上,每一个 Plugin 或者算是 HTTP 服务中的中间件都有这个字段用于 构建链式调用。

每一个 Plugin 值得注意的两个,
一个是他们会实现 ServeHTTP 接口进行 HTTP 请求处理。

func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	defer h.recovery(w, r)

	status, err := h.Next.ServeHTTP(w, r)

	if err != nil {
		errMsg := fmt.Sprintf("%s [ERROR %d %s] %v", time.Now().Format(timeFormat), status, r.URL.Path, err)
		if h.Debug {
			// Write error to response instead of to log
			w.Header().Set("Content-Type", "text/plain; charset=utf-8")
			w.WriteHeader(status)
			fmt.Fprintln(w, errMsg)
			return 0, err // returning 0 signals that a response has been written
		}
		h.Log.Println(errMsg)
	}

	if status >= 400 {
		h.errorPage(w, r, status)
		return 0, err
	}

	return status, err
}

另一个是安装到 caddy 中的 setup.go 文件,我们看一下 Plugin 安装的全流程。

errorsParse 函数

谢谢阅读,如果有不对的地方欢迎指正。