Preview

Caddy
在日常软件开发中,Controller 是 MVC 的 逻辑处理部分,在 Caddy 中,它意味这 Plugin 的安装逻辑。

Caddy 自身提供了一个 总的可扩展的房子一样的框架,Controller 把不同的 Plugin 像积木一样搭建成一个服务器程序提供服务。

Context

image.png
前文提到, Caddyfile 通过 Loader ,lexer 等进行读取。

读取出来的 token directives 最终是用来配置 Plugin 的,

在本文中会详细探索 Plugin 如何读取配置安装,Controller 是完成这一行为的执行者。

同时探索 Plugin 的设计是如何解耦 Caddy 的 Plugin 开发者和 Caddy 的 maintainer 的。

Plugin 插件的巧妙设计解耦扩展

变量

caddy/plugin.go中保存着整个 caddy 的插件,当使用插件的时候,导入某个插件包即可。在包里的 init 函数会帮助你注册好 插件

  1. // plugins is a map of server type to map of plugin name to
  2. // Plugin. These are the "general" plugins that may or may
  3. // not be associated with a specific server type. If it's
  4. // applicable to multiple server types or the server type is
  5. // irrelevant, the key is empty string (""). But all plugins
  6. // must have a name.
  7. plugins = make(map[string]map[string]Plugin)

这里使用的 两层 map 第一层是 ServerType 的 name ,第二层是 plugin 的名字,如果第一个为空,说明这个插件所有的 ServerType 都可以使用

注册

使用 caddy.RegisterPlugin() 注册,dup 是 duplicate 的简写

  1. // RegisterPlugin plugs in plugin. All plugins should register
  2. // themselves, even if they do not perform an action associated
  3. // with a directive. It is important for the process to know
  4. // which plugins are available.
  5. //
  6. // The plugin MUST have a name: lower case and one word.
  7. // If this plugin has an action, it must be the name of
  8. // the directive that invokes it. A name is always required
  9. // and must be unique for the server type.
  10. func RegisterPlugin(name string, plugin Plugin) {
  11. if name == "" {
  12. panic("plugin must have a name")
  13. }
  14. if _, ok := plugins[plugin.ServerType]; !ok {
  15. plugins[plugin.ServerType] = make(map[string]Plugin)
  16. }
  17. if _, dup := plugins[plugin.ServerType][name]; dup {
  18. panic("plugin named " + name + " already registered for server type " + plugin.ServerType)
  19. }
  20. plugins[plugin.ServerType][name] = plugin
  21. }

实现

逻辑解耦使得我们实现 Plugin 只需要关注如何将我们的 Plugin 注册到 Caddy Server 中了。

如何实现 一个 Plugin

Overview

这里概览了 Plugin 是如何注册的。

caddy-plugin.svg

可以在这里看到我们之前讲解的很多的熟悉的概念,这是因为我们快要读完 caddy 的架构了,剩下的是具体的 Plugin 的各种扩展实现了。

可以看到,Plugin 是注册在不同的 服务器类型 serverType 下的,是在两重 map 映射的结构中,图中可以看出,然后是 Action ,最近的上文才说明了它,用它来进行 Plugin 的安装。

然后来到 Controller ,实际进行配置的家伙,看到了之前所说的 DispenserToken 配置

还记得吗,他们在刚才的词法分析里才出现过。

你会注意到,在目录中有一个 叫 caddyhttp 的文件夹中的文件夹特别多,
这就是 一系列 http 服务器 的 Plugin 实现
接下来我们看一个 HTTPPlugin 的例子 errors 的实现

caddyHTTP

errors

overviewcaddy-http-plugin-overview.svg

这里我们从下看,caddy.Listener 定义在 caddy.go 中,用来支持 零停机时间加载。

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

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

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

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

  1. func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
  2. defer h.recovery(w, r)
  3. status, err := h.Next.ServeHTTP(w, r)
  4. if err != nil {
  5. errMsg := fmt.Sprintf("%s [ERROR %d %s] %v", time.Now().Format(timeFormat), status, r.URL.Path, err)
  6. if h.Debug {
  7. // Write error to response instead of to log
  8. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  9. w.WriteHeader(status)
  10. fmt.Fprintln(w, errMsg)
  11. return 0, err // returning 0 signals that a response has been written
  12. }
  13. h.Log.Println(errMsg)
  14. }
  15. if status >= 400 {
  16. h.errorPage(w, r, status)
  17. return 0, err
  18. }
  19. return status, err
  20. }

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

Directives

前面提到过很多次 Directives 这里做一个它的整个流程概览。上文中提到,这些注册都是 Controller 执行的。下半部分是 关于 HTTP 的服务配置

重点在 errors.serup() ,可以看到,它创建了 errors.ErrHandler 并注册到了 httpserver 的一对中间件中

  1. // setup configures a new errors middleware instance.
  2. func setup(c *caddy.Controller) error {
  3. handler, err := errorsParse(c)
  4. ···
  5. httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
  6. handler.Next = next
  7. return handler
  8. })
  9. return nil
  10. }

这里还有一个关于 caddy.Controller 到 ErrorHandler 的一个转换 通过 errorsParse 函数
caddy-http-error.svg

DirectiveAction

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

Plugin 通过 Controller 来实现 配置自己的 Action 的

  1. // DirectiveAction gets the action for directive dir of
  2. // server type serverType.
  3. func DirectiveAction(serverType, dir string) (SetupFunc, error) {
  4. if stypePlugins, ok := plugins[serverType]; ok {
  5. if plugin, ok := stypePlugins[dir]; ok {
  6. return plugin.Action, nil
  7. }
  8. }
  9. if genericPlugins, ok := plugins[""]; ok {
  10. if plugin, ok := genericPlugins[dir]; ok {
  11. return plugin.Action, nil
  12. }
  13. }
  14. return nil, fmt.Errorf("no action found for directive '%s' with server type '%s' (missing a plugin?)",
  15. dir, serverType)
  16. }

了解完这些,我们注意到有一个 叫做 Action 的东西,它又是怎么来的?别急,他就在 Plugin 包中。我们知道了。

配置文件是配置各种 plugin 作为插件安装在 caddy 服务器上,而 caddyfile 正是被转化为了 Token

Dispenser 分发读取到的配置,即不同的插件安装。那么 Action 就是 PluginSetupFunc啦,来看看吧。

excuteDirective

excuteDirective 函数就是所以指令消耗的核心。他以正确的顺序执行指令 Directive。 会调用 DirectiveAciton 来通过 Controller 执行。

需要顺序是为了执行回调函数。下面会讲到他 ParsingCallback

  1. func executeDirectives(inst *Instance, filename string,
  2. directives []string, sblocks []caddyfile.ServerBlock, justValidate bool) error {
  3. // map of server block ID to map of directive name to whatever.
  4. storages := make(map[int]map[string]interface{})
  5. // It is crucial that directives are executed in the proper order.
  6. // We loop with the directives on the outer loop so we execute
  7. // a directive for all server blocks before going to the next directive.
  8. // This is important mainly due to the parsing callbacks (below).
  9. for _, dir := range directives {
  10. for i, sb := range sblocks {
  11. var once sync.Once
  12. if _, ok := storages[i]; !ok {
  13. storages[i] = make(map[string]interface{})
  14. }
  15. for j, key := range sb.Keys {
  16. // Execute directive if it is in the server block
  17. if tokens, ok := sb.Tokens[dir]; ok {
  18. controller := &Controller{
  19. instance: inst,
  20. Key: key,
  21. Dispenser: caddyfile.NewDispenserTokens(filename, tokens),
  22. OncePerServerBlock: func(f func() error) error {
  23. var err error
  24. once.Do(func() {
  25. err = f()
  26. })
  27. return err
  28. },
  29. ServerBlockIndex: i,
  30. ServerBlockKeyIndex: j,
  31. ServerBlockKeys: sb.Keys,
  32. ServerBlockStorage: storages[i][dir],
  33. }
  34. setup, err := DirectiveAction(inst.serverType, dir)
  35. if err != nil {
  36. return err
  37. }
  38. err = setup(controller)
  39. if err != nil {
  40. return err
  41. }
  42. storages[i][dir] = controller.ServerBlockStorage // persist for this server block
  43. }
  44. }
  45. }
  46. if !justValidate {
  47. // See if there are any callbacks to execute after this directive
  48. if allCallbacks, ok := parsingCallbacks[inst.serverType]; ok {
  49. callbacks := allCallbacks[dir]
  50. for _, callback := range callbacks {
  51. if err := callback(inst.context); err != nil {
  52. return err
  53. }
  54. }
  55. }
  56. }
  57. }
  58. return nil
  59. }

ParsingCallback

变量

在 directives 执行中还可以加入 回调函数
caddy/plugin.go

  1. // parsingCallbacks maps server type to map of directive
  2. // to list of callback functions. These aren't really
  3. // plugins on their own, but are often registered from
  4. // plugins.
  5. parsingCallbacks = make(map[string]map[string][]ParsingCallback)

实现

只需要使用 RegisterParsingCallback 注册到变量中即可

  1. // ParsingCallback is a function that is called after
  2. // a directive's setup functions have been executed
  3. // for all the server blocks.
  4. type ParsingCallback func(Context) error
  5. // RegisterParsingCallback registers callback to be called after
  6. // executing the directive afterDir for server type serverType.
  7. func RegisterParsingCallback(serverType, afterDir string, callback ParsingCallback) {
  8. if _, ok := parsingCallbacks[serverType]; !ok {
  9. parsingCallbacks[serverType] = make(map[string][]ParsingCallback)
  10. }
  11. parsingCallbacks[serverType][afterDir] = append(parsingCallbacks[serverType][afterDir], callback)
  12. }

可以看到,能传入的参数是 Context ,而 Context 在上文中的 ServerType 中有涉及到

扩展阅读