开发插件:服务器类型
英文原文:https://github.com/mholt/caddy/wiki/Writing-a-Plugin:-Server-Type
加入Caddy交流论坛和其他开发者一起分享。
Caddy附带一个HTTP服务器,但是你可以实现其他服务器类型并将它们插入Caddy中。其他类型的服务器可以是SSH、SFTP、TCP、内部使用的其他东西等等。
对于Caddy来说,服务器的概念是任何可以Listen()
和Serve()
的东西。这意味着什么、如何运作都取决于你。你可以自由地发挥你的创造力去使用它。
如果你的服务器类型可以使用TLS,那么它应该利用Caddy的神奇TLS特性。我们将在本指南的最后描述如何做到这一点。
在一个高的层面上而言,使用caddy.RegisterServerType()函数实现一个服务器类型插件非常容易。传入服务器类型的名称,以及描述它的caddy.ServerType结构即可。
下面是一个(稍微简化了一些)HTTP服务器的示例:
import "github.com/mholt/caddy"
func init() {
caddy.RegisterServerType("http", caddy.ServerType{
Directives: directives,
DefaultInput: func() caddy.Input {
return caddy.CaddyfileInput{
Contents: []byte(fmt.Sprintf("%s:%s\nroot %s", Host, Port, Root)),
ServerTypeName: "http",
}
},
NewContext: newContext,
})
}
如你所见,服务器类型包括名称(“http”)、指令列表、默认的Caddyfile输入(可选,但推荐)和上下文。现在我们将分别讨论这些。
指令
每个服务器类型都要定义它所识别的指令,以及它们应该以什么顺序执行。这只是一个字符串列表,其中每个字符串都是一个指令的名称:
var directives = []string{
"dir1",
"dir2",
"etc",
}
在大多数服务器类型中,执行顺序非常重要,因此如果不在此列表中,则不会执行指令。
默认Caddyfile输入
这是可选的,但是强烈建议你不要默认使用空白的Caddyfile。这对你的服务器类型意味着什么取决于你自己。但是你可以返回一个caddy.CaddyfileInput
实例以满足此功能。只有在需要时,Caddy才会调用你的函数。
上下文
Caddy试图避免尽可能多的全局状态。当Caddy加载、解析和执行Caddyfile时,它是在一个称为实例的作用域中执行的,这样一组服务器可以单独管理,多个服务器类型可以在同一个进程中启动。
每次Caddy经过这个启动阶段,都会创建一个新的caddy.instance
。实例和Caddy将向你的服务器类型请求一个新的caddy.Context
值,以便在不共享全局状态的情况下执行服务器的指令。阅读caddy.Context
的godoc,获取更多信息。
不应该使用服务器类型存储上下文列表;它们将与实例一起存储,并且可以从控制器访问。如果服务器稍后停止运行,那么保持对上下文的引用将防止它们被垃圾收集,这很可能造成内存泄漏。
最终,您的目标是使MakeServers()
返回一个caddy.Server列表供Caddy使用。你怎么做完全取决于你自己。
例如:HTTP服务器使用其上下文类型保存站点配置的map。这个包还有一个公有函数GetConfig(c Controller)
,它为某个站点获取配置,传入指定的caddy.Controller。配置依次存储中间件列表等——所有设置站点所需的信息。每个指令的操作都调用GetConfig函数,帮助正确地设置站点。在调用MakeServers()
时,只需将这些站点配置合并到服务器实例中并返回它们。(服务器实例实现了caddy.Server。)
服务器
caddy.Server
的接口同时考虑了TCP和非TCP服务器。它有四个方法:两个针对TCP,两个针对UDP或其他:
type Server interface {
TCPServer // Required if using TCP
UDPServer // Required if using UDP or other
}
type TCPServer interface {
Listen() (net.Listener, error)
Serve(net.Listener) error
}
type UDPServer interface {
ListenPacket() (net.PacketConn, error)
ServePacket(net.PacketConn) error
}
如果你的服务器只使用TCP,*Packet()
方法可能是空操作(即返回nil)。而非TCP服务器则与此相反。服务器还可以同时使用TCP和UDP,并实现所有四种方法。
一旦这些接口被实现,并且正确地从Caddyfile指令应用了配置,你的服务器类型就可以运行了。
自动TLS
可以使用TLS的服务器类型应该在将TLS添加到Caddy下载页面之前自动启用TLS。导入caddytls包以使用Caddy的魔术 TLS特性。乍一看,这似乎有点令人困惑,但一旦它起作用,你会喜欢它,并意识到它是值得的。
- 你需要一种方式来为你的服务器实例存储Caddy TLS配置。通常,这只意味着向服务器的配置结构中添加一个字段。
- 相同的服务器配置结构类型应该实现caddytls.ConfigHolder接口。这只是一些getter方法。
- 在包的init()中调用RegisterConfigGetter(),这样caddytls包就知道在为服务器类型解析Caddyfile时如何请求配置。(如果给定控制器中还不存在配置,你的“配置getter”必须创建一个新的
caddytls.Config
。 - 将
tls
指令添加到服务器类型的指令列表中。通常它位于列表的最前面。
当你实例化实际的服务器值并需要tls.Config
时,可以调用caddytls.MakeTLSConfig(tlsConfigs),其中tlsConfigs
是[]caddytls.Config
。这个函数将一个Caddy的TLS配置列表转换为一个标准库的tls.Config
。然后,你可以在对tls.NewListener()的调用中使用它。
最后,你通常希望在分析Caddyfile时启用TLS。例如,HTTP服务器在执行tls
指令后立即配置HTTPS。你应该使用包的init()
函数注册一个解析回调函数,该回调函数将遍历配置并配置TLS:
// 将"http"替换成你自己的服务器类型
caddy.RegisterParsingCallback("http", "tls", activateHTTPS)
这段代码在tls
指令完成设置后执行activateHTTPS()
函数。你的服务器类型应该有一个类似的函数,以一种合理的方式启用TLS。为了给你一个概念,HTTP服务器的activateHTTPS
函数做了以下工作:
- 打印一条信息到标准输出,“激活隐私功能…”(Activating privacy features…)(如果操作出现;例如,
caddy.Started() == false
),因为这个过程可能需要几秒钟。 - 在所有应该完全管理的配置上将
Managed
字段设置为true
。 - 为每个配置调用 ObtainCert() (此方法仅在配置正确并将其
Managed
字段设置为true时才获得证书)。 - 通过将TLS配置的
Enabled
字段设置为true
并调用caddytls.CacheManagedCertificate()来配置服务器结构以使用新获得的证书,而caddyls .cachemanagedcertificate()实际上是将证书加载到内存中以供使用。 - 调用[caddytls.SetDefaultTLSParams()]以确保所有必要的字段都有一个值。
- 调用caddytls.RenewManagedCertificates(true),以确保在必要时已将所有已加载到内存中的证书更新。
还有很多很多,但是你还可以看看HTTP服务器是如何工作的(<—这是一个永久链接,所以最新的代码可能更好)。
为了保持完美的前向保密,你应该在实例化服务器值时调用caddytls.RotateSessionTicketKeys(),传入TLS配置。请确保在服务器停止时关闭它返回的通道。
TLS的所有其他内容:续订、OCSP和其他维护都会为你进行,因为所有服务器类型都是相同的。所有这些步骤只是将你的服务器类型与Caddy的TLS包连接起来,这样它就知道如何完成它的工作。
彻底测试caddytls包的集成。一旦运行良好,让我们将你的服务器类型添加到下载页面!