飞雪无情

动手撸个Caddy(十四)| 插件开发之自定义全局配置

点击上方蓝色“飞雪无情”关注我,设个星标,第一时间看文章

在开发Caddy插件的时候,很多现成的配置可以为我们所用,这样我们插件中的某些配置就可以遵循整个Caddyfile的全局统一配置,用户体验会更好,比如使用统一的debug来控制自己开发插件的日志输出等等。

什么是全局配置

Caddyfile的全局配置和整个Caddy有关,并不局限于某个指令、请求等。在Caddy中有一些配置使用了默认的配置,但是Caddy同时也提供了全局的配置,可以让我们修改某些配置的默认值。

比如在Caddyfile中,debug默认是关闭的,我们可以通过全局配置来启用它。

{
  debug
}

在整个Caddyfile的最顶部,使用一个单独的 {} 块,这个块就是自定义全局配置的地方,比如以上示例代码,我们开启了 debug 模式,这将会把所有的日志级别都设置为 debug 。
还有一个我们曾经用过的 order 其实也是一个全局配置,用它来自定义指令的顺序:

{
  order hello_world last
  debug
}

Caddyfile提供的全局配置有很多, order 、 debug 只是其中的两个,比如还有 http_port 用来设置http的端口,默认是80。

这里给出所有的配置列表:

{
    # General Options
    debug
    http_port  <port>
    https_port <port>
    order <dir1> first|last|[before|after <dir2>]
    storage <module_name> {
        <options...>
    }
    storage_clean_interval <duration>
    admin   off|<addr> {
        origins <origins...>
        enforce_origin
    }
    log [name] {
        output  <writer_module> ...
        format  <encoder_module> ...
        level   <level>
        include <namespaces...>
        exclude <namespaces...>
    }
    grace_period <duration>
    # TLS Options
    auto_https off|disable_redirects|ignore_loaded_certs
    email <yours>
    default_sni <name>
    local_certs
    skip_install_trust
    acme_ca <directory_url>
    acme_ca_root <pem_file>
    acme_eab <key_id> <mac_key>
    acme_dns <provider> ...
    on_demand_tls {
        ask      <endpoint>
        interval <duration>
        burst    <n>
    }
    key_type ed25519|p256|p384|rsa2048|rsa4096
    cert_issuer <name> ...
    ocsp_stapling off
    preferred_chains [smallest] {
        root_common_name <common_names...>
        any_common_name  <common_names...>
    }
    # Server Options
    servers [<listener_address>] {
        listener_wrappers {
            <listener_wrappers...>
        }
        timeouts {
            read_body   <duration>
            read_header <duration>
            write       <duration>
            idle        <duration>
        }
        max_header_size <size>
        protocol {
            allow_h2c
            experimental_http3
            strict_sni_host
        }
    }
}

以上就是Caddyfile内置支持的全局配置,关于他们的说明可以从字面意思上推测出来,当然你也可以查看这个 https://caddyserver.com/docs/caddyfile/options 文档,了解更详细的说明 。

使用全局配置

以上这些全局配置,我们也可以在我们自己定义的插件中使用的,现在我就为你讲解如何使用他们,下面就以 debug 这个配置为例。

type HelloWorld struct {
   Text  string `json:"text,omitempty"`
   Debug bool   `json:"debug,omitempty"`
}

首先增加一个 Debug 字段用于存储是否需要Debug。
这个字段并不是我们控制,而是要用过全局配置来控制,需要我们需要读取全局配置中 debug 的值,用于控制是否需要Debug。

func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
   hw := new(HelloWorld)
   if debug, ok := h.Option("debug").(bool); ok {
      hw.Debug = debug
   }
   err := hw.UnmarshalCaddyfile(h.Dispenser)
   return hw, err
}

在解析Caddyfile文件的时候,通过 h.Option 方法即可获取指定全局配置的值,这里我们获取的是 debug ,然后赋予 HelloWorld 的 Debug 字段。
好了,现在我们的插件也可以通过全局配置 debug 开关Debug模式了。

自定义全局配置

除了可以使用Caddy自带的全局配置外,我们还可以自定义自己的全局配置,Caddy的这种能力为我们提供了更强大的扩展性。

下面我还以这个debug模式为例,使用我们自定义的全局配置,看如何实现。需要注意的是这个纯属演示自定义全局配置的能力,实践中还是要使用内置的debug这个全局配置。

要先自定义自己的全局配置,首先我们需要注册它:

func init() {
   caddy.RegisterModule(&HelloWorld{})
   httpcaddyfile.RegisterGlobalOption("hello_debug", parseOptHelloDebug)
   httpcaddyfile.RegisterHandlerDirective("hello_world", parseCaddyfile)
}
func parseOptHelloDebug(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
   var debug int
   for d.Next() {
      var debugStr string
      if !d.AllArgs(&debugStr) {
         return 0, d.ArgErr()
      }
      var err error
      debug, err = strconv.Atoi(debugStr)
      if err != nil {
         return 0, d.Errf("converting port '%s' to integer value: %v", debugStr, err)
      }
   }
   return debug, nil
}

是的,就在我们的 init 方法中,使用函数 httpcaddyfile.RegisterGlobalOption 就可以新增一个自定义的全局配置。
parseOptHelloDebug 用来解析出Caddyfile全局配置中 hello_debug 的值,这里为了演示,我定义的hello_debug 的值是 int 类型, 1 表示开启debug模式, 0 表示不开启。

现在解析Caddyfile得到 hello_debug 的值,就可以在刚刚 parseCaddyfile 函数中使用它了,我们稍微修改下即可:

func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
   hw := new(HelloWorld)
   if debug, ok := h.Option("hello_debug").(int); ok {
      hw.Debug = debug == 1
   }
   err := hw.UnmarshalCaddyfile(h.Dispenser)
   return hw, err
}

只需要把原来的 bool 断言换成 int 断言,并且值为 1 才被认为是开启Debug模式。

小结

现在,你已经拿到了 Debug 的值,你可以在你插件的任何地方使用它,来打印你想打印的日志,一般我们会这样:

if (h.Debug){
  // 日志输出
}

Caddy 提供的全局配置以及可以自定义的能力,为Caddy的能力扩展以及插件开发都提供了很好的扩展,我们可以利用这个能力,大大丰富自己插件的可自定义化行为。

但是最终我想提醒的是,如果Caddy已经定义了你需要的全局配置,那么最好和他们保持一致。

本文为原创文章,转载注明出处,欢迎扫码关注公众号flysnow_org或者网站 https://www.flysnow.org/ ,第一时间看后续精彩文章。觉得好的话,请猛击文章右下角「在看」,感谢支持。

——  精彩推荐 ——

接入阿里云Jaeger踩了个大坑,竟然是因为Jaeger的Golang SDK和NodeJS的实现不一样

研究开源项目发现的一个人性化的Go语言库

必须要掌握的几个 Go 语言技巧

动手撸个Caddy(十三)| 插件开发之Caddyfile参数

Caddy实战(十二)| 插件开发之HelloWorld

  Caddy实战(十一)| Caddyfile 设计之美

Caddy实战(十)| 一分钟搭建PHP服务器

Caddy实战(九)| 设置头信息实现跨域

Caddy实战(八)| 利用缓冲提升反向代理的性能

Caddy实战(七)| 反向代理中的健康检查

Caddy实战(六)| 反向代理中的负载均衡

Caddy实战(五)| 配置反向代理

Image

扫码关注

分享、点赞、在看就是最大的支持