Caddy源码阅读(二)启动流程与 Event 事件通知

Caddy源码阅读(二)启动流程与 Event 事件通知

Preface

Caddy 是 Go 语言构建的轻量配置化服务器。https://github.com/caddyserver/caddy

Caddy 整个软件可以说是由不同的 插件 堆砌起来的。自己本身仅提供 Plugin 的注册运行逻辑和 Server 的监听服务功能。

学习 caddy 的源码,实际上是学习 如何构建一个 松耦合的 抽象 Plugin 设计,即模块化插拔的做法。

所以我们的源码阅读,围绕 Caddy 为 Plugin 提供的基础设施,和 Plugin 自身逻辑。


下面我们从第一步,启动流程开始阅读。
之后的路径应该是? Caddyfile 的解析,解析出的 配置文件如何消费,配置完成的服务器如何服务。

Overview

Package

这是 caddy 包的结构

main.go

一切的开始? ---?
我们查看 在 caddy 文件夹下的 main.go 函数。

这是 上图 caddy 文件夹下的目录结构。

其中 run.go 我们在上一篇文章阅读完成

main.go 的 Trick

在 caddy 文件夹中的 main 函数启动 caddy 服务器。实际运行的是 run.go 中的文件,这是方便测试使用
main.go的代码

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

启动流程?

启动 caddy 的流程

?caddyfileLoader?加载 caddyfile 配置? =》生成 Input 信息
Context?=》 生成 Server

caddyfile 示例

caddyfile 简单示例:

Instance?是运行操作的 Server 实例,可以看到几个主要的操作都是在他身上?

Server?两种监听模式?TCP?UDP?

我们首先关心的是 Start()?启动服务器。

启动服务器

发送 StartupEvent, 参照下文中?Event?理解

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

读取配置文件:参照我的接下来的文章 Caddy-解析Caddyfile

caddyfileinput, err := caddy.LoadCaddyfile(serverType)

启动:

instance, err := caddy.Start(caddyfileinput)

发送 InstanceStartupEvent

caddy.EmitEvent(caddy.InstanceStartupEvent, instance)

Start()

// Start your engines
instance, err := caddy.Start(caddyfileinput)
if err != nil {
    mustLogFatalf("%v", err)
}

阅读完代码,画一张图帮助理解

这里除了?Instance?之外还有两个新名词

?Controller:它是用来帮助?Directives?设置它自身的,通过读取 Token,这里的 Directives?实际上对应的就是上文所说的 caddyfile 中的配置文件选项。

这一点请参照 Caddy(三)Loader 下的 excuteDirective?理解。

?Token?:是 caddy 自己的 词法分析器 解析 caddyfile 配置文件出的选项的标记。

这一点请Caddy(三)中?Loader?中的 Parser 理解

我们来看顺序,第一遍从顶向下看。

第一个是 Input,这是 caddyfile 的变量结构,他可以通过 Start()方法新建实例 Instance

Instance 通过从 caddyfile 读取到信息的 Input 生成 Context

携带信息的 Context 承担 新建 Server 的任务

Context 读取 caddyfile 解析出的 ServerBlock 配置服务器

ServerBlock 包含 不同的 Tokens 他们会转换为 Directive

Directive 会被 Controller 消费,用于配置插件 安装到服务器上

值得注意的是 Controller? 更改的是 Instance?
对于 http 服务器来说还会增加 http 服务的中间件

如果不理解,首先记住 caddy 是?配置的?模块化的服务器,

通过 caddyfile 配置 -> caddyfile
读取它 -> Loader
解析配置目标-> token & directives
进行配置 -> controller & setup
启动 -> instance & Start()

记住这个流程就能理解了。

Event 事件通知启动插件

引入

我们看到,在 caddy 的 run.go 中有一行代码是

caddy.EmitEvent(caddy.StartupEvent, nil)

这就是 caddy 中的 事件通知系统,通知的是所有的 plugin。

变量

caddy/plugin.go 包中

// eventHooks is a map of hook name to Hook. All hooks plugins
// must have a name.
eventHooks = &sync.Map{}

是一个保存所有 plugin hook 的 sync.Map{}?

这个标准包的 Map 是并发安全的, 通常我们使用 Load() 或者 LoadOrStore() 方法存读信息,Range() 方法遍历,如果你需要,可以引入你的 Go 程序中。

Logic

看内在实现

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

很简单,上文提过,eventHooks.Range 是遍历信息,会遍历所有保存的 EventHook 函数并运行。

那么 Plugin 想使用接收某一个事件通知做相应操作的时候,只需把自己的 EventHook 函数注册到这个 map 中

// eventHooks is a map of hook name to Hook. All hooks plugins
// must have a name.
eventHooks = &sync.Map{}

使用?RegisterEventHook?注册
?type EventHook func(eventType EventName, eventInfo interface{}) error

// RegisterEventHook plugs in hook. All the hooks should register themselves
// and they must have a name.
func RegisterEventHook(name string, hook EventHook) {
    if name == "" {
        panic("event hook must have a name")
    }
    _, dup := eventHooks.LoadOrStore(name, hook)
    if dup {
        panic("hook named " + name + " already registered")
    }
}

那么可以监听哪些事件呢?在 Plugin 中有定义常量

// Define names for the various events
const (
    StartupEvent         EventName = "startup"
    ShutdownEvent                  = "shutdown"
    CertRenewEvent                 = "certrenew"
    InstanceStartupEvent           = "instancestartup"
    InstanceRestartEvent           = "instancerestart"
)

启动,关闭,刷新证书,这里提到的 Instance 是 caddy 中的 Server 实例

结语

我们概览了 caddy 的 run 和 Start 启动流程,接下来我们会继续深入了解 Caddy 每个部分流程。
可以看之前的文章
Caddy源码阅读(一)Run详解
和之后的
Caddy源码阅读(三)Caddyfile 解析 by Loader & Parser
Caddy源码阅读(四)Plugin & Controller 安装插件
Caddy源码阅读(五) Instance & Server
caddy-plugins(一)自定义插件
caddy-plugins(二)caddy-grpc 一个插件实例

原文地址:https://www.cnblogs.com/abser/p/11397334.html

时间: 2024-10-14 00:42:18

Caddy源码阅读(二)启动流程与 Event 事件通知的相关文章

ThinkPHP源码阅读1-------访问流程

ThinkPHP访问流程在手册1.11系统流程里就有介绍,我阅读的ThinkPHP的版本是3.1.3,大家可以看下手册,基本的流程也有,现在就是详细去介绍一下ThinkPHP的访问流程.(调试模式下的,在部署模式下,会把中间的URL解析,文件加载之类的,都封装到一个文件里) 1.入口文件(index.php) 入口文件最常见得就是index.php,而在这里可以定义项目名称,路径,缓存文件存放路径之类的.最后要加载Thinkphp/ThinkPHP.php 指向下一个文件的地方是require

OSCache源码阅读(二)

前文LRU Cache 暨LinkedHashMap源码阅读提到了如何使用LinkedHashMap来实现一个LRU数据结构,今天在看OSCache代码算法部分的时候,就用到了该知识,what was done contributes what is done now. algorithm包是包含下列缓存过期策略的类: 下面重点介绍LRU和FIFO. LRU private Collection list = new LinkedHashSet(); 使用一个LinkedHashSet对象来实现

SpringMVC源码解析-DispatcherServlet启动流程和初始化

在使用springmvc框架,会在web.xml文件配置一个DispatcherServlet,这正是web容器开始初始化,同时会在建立自己的上下文来持有SpringMVC的bean对象. 先从DispatcherServlet入手,从名字来看,它是一个Servlet.它的定义如下: public class DispatcherServlet extends FrameworkServlet { 它是继承FrameworkServlet,来看一下整个的继承关系. 从继承关系来看,Dispatc

SDWebImage源码阅读(二)NSData+ImageContentType

NSData+ImageContentType 是NSData 的分类(Category). 创建分类的步骤: 在Xcode 工程页面,按command + N ,在iOS -> Source 选择Objective-C File ,点击Next ,File Type 选择 Category ,file 框里输入分类名字,Calss 为要添加分类的类,可以是系统的类也可以是自定义的类,当然这里出现的主要是系统的类. 分类的描述: 无论一个类设计的如何完美,都不可避免的会遇到没有预测到的需求,那怎

spring源码阅读(二) Bean加载之自定义标签加载

紧接着上一篇关于spring默认标签加载,这一篇来看下自定义标签的加载 继续从 DefaultBeanDefinitionDocumentReader来看 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for(

Spring源码分析: SpringMVC启动流程与DispatcherServlet请求处理流程

Spring版本: 4.0.X 注:这里的分析只关注整个处理流程的大致过程,省略与流程无关的代码. 应用根上下文(Root ApplicationContext)的启动 我们知道在一个web项目中使用SpringMVC时,需在web.xml中配置一个监听器: <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </list

SpringMVC源码分析和启动流程

https://yq.aliyun.com/articles/707995 在Spring的web容器启动时会去读取web.xml文件,相关启动顺序为:<context-param> --> <listener> --> <filter> --> <servlet>,具体为: 1.解析<context-param>键值对 2.创建一个application对象即ServletContext,servlet上下文,用于全局共享 3

FMDB源码阅读(二)FMDatabase.m

FMDatabase.m 1 @interface FMDatabase () { 2 void* _db; 3 BOOL _isExecutingStatement; 4 NSTimeInterval _startBusyRetryTime; 5 6 NSMutableSet *_openResultSets; 7 NSMutableSet *_openFunctions; 8 9 NSDateFormatter *_dateFormat; 10 } 成员变量. 1 NS_ASSUME_NON

AFNetworking源码阅读(二)AFNetworkReachabilityManager

AFNetworkReachabilityManager.h 1 #if !TARGET_OS_WATCH 2 ... 3 #endif 表示当前只适用于 非WATCH 平台开发. 1 #import <SystemConfiguration/SystemConfiguration.h>