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 一个插件实例
Caddy源码阅读(二)启动流程与 Event 事件通知的更多相关文章
- Flume-ng源码解析之启动流程
今天我们通过阅读Flume-NG的源码来看看Flume的整个启动流程,废话不多说,翠花,上源码!! 1 主类也是启动类 在这里我贴出Application中跟启动有关的方法,其他你们可以自己看源码,毕 ...
- xxl-job源码阅读二(服务端)
1.源码入口 xxl-job-admin是一个简单的springboot工程,简单翻看源码,可以很快发现XxlJobAdminConfig入口. @Override public void after ...
- Spring源码阅读-spring启动
web.xml web.xml中的spring容器配置 <listener> <listener-class>org.springframework.web.context.C ...
- SpringMVC源码解析-DispatcherServlet启动流程和初始化
在使用springmvc框架,会在web.xml文件配置一个DispatcherServlet,这正是web容器开始初始化,同时会在建立自己的上下文来持有SpringMVC的bean对象. 先从Dis ...
- Python Web Flask源码解读(一)——启动流程
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- spark源码阅读--SparkContext启动过程
##SparkContext启动过程 基于spark 2.1.0 scala 2.11.8 spark源码的体系结构实在是很庞大,从使用spark-submit脚本提交任务,到向yarn申请容器,启 ...
- Spring 源码阅读 二
程序入口: 接着上一篇博客中看完了在AnnotationConfigApplicationContext的构造函数中的register(annotatedClasses);将我们传递进来的主配置类添加 ...
- Elasticsearch源码分析(一)启动流程 ModuleBuilder injector
http://blog.csdn.net/u010994304/article/details/50452890 es启动脚本是bin目录下的elasticsearch. 脚本内容不再赘述,java主 ...
- SpringMVC源码分析和启动流程
https://yq.aliyun.com/articles/707995 在Spring的web容器启动时会去读取web.xml文件,相关启动顺序为:<context-param> -- ...
随机推荐
- 跨站脚本攻击(selfxss)笔记(三)
本篇纯文字发表自己对自插型xss的看法 selfxss,顾名思义,自己输入xss脚本,输出仅自己看到,仅xss到自己. 常见的有:查询框的xss.某个账号中,添加自己的分组类等 查询框的xss: 即在 ...
- jdk安装错误1316,jdk-10.0.1
打开注册表regedit ,定个位到 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\ 把jdk,jre下面的目录删掉.即10.0.1 ,问题解决 参考: https ...
- Mybatis generator生成工具简单介绍
Mybatis generator 其主要的功能就是方便,快捷的创建好Dao,entry,xml 加快了开发速度,使用方面根据其提供的规则配置好就OK 这里还有一个重要的开发场景,开发过程中,对数据 ...
- iOS程序员如何提升核心竞争力,防止自己被裁员?
前言: 核心竞争力最早由普拉哈拉德和加里·哈默尔两位教授提出,通常认为核心竞争力,即企业或个人相较于竞争对手而言所具备的竞争优势与核心能力差异,说白了就是你的优势,而且最好是独一无二的的优势,这就是核 ...
- excel表数据生成定长txt数据
项目作业中需要造数据,从txt文件中获取定长数据,直接从txt中修改,会显得十分麻烦,于是便利用excel自带的vba写了一个小工具.效果如下: A1表示字段名,A2表示长度,A3是数据,也可以增加字 ...
- bean的创建(五)第二部分 寻找bean的工厂方法实例化
instanceWrapper = createBeanInstance(beanName, mbd, args); AbstractAutowireCapableBeanFactory.create ...
- Java连接MySQL8.0以上版本数据库方式
MySQL 8.0 开始数据库相比常用的 5.X 版本发生了比较大的变化,我们在连接数据库的过程中许多地方也要发生一些变化. 总结一下,想要利用 mysql-connector-java 与 MySQ ...
- 【MySQL】ON DUPLICATE KEY UPDATE
之前没用过这个操作,甚至没见过--最近接触到,而且还挺有用. 作用:若 KEY 不重复,则插入记录:否则更新记录. 单条操作: INSERT INTO table(a, b, c) VALUES (1 ...
- spring cloud eureka + feign,api远程调用
网上教程不少,有些就是复制粘贴,不结合实际生产. eureka不再阐述. 一般正常开发会有多个工程,且多个module. 我的习惯是: eureka server.权限.config.gateway ...
- 请使用switch语句和if...else语句,计算2008年8月8日这一天,是该年中的第几天。
请使用switch语句和if...else语句,计算2008年8月8日这一天,是该年中的第几天. #include <stdio.h> int main() { /* 定义需要计算的日期 ...