精华内容
下载资源
问答
  • Caddy

    2019-10-25 00:37:34
    Caddy
  • Caddy Docker自定义映像 带有自定义模块的Caddy Docker容器 我拍了基本的球童泊坞窗图片; 在自定义模块下方添加了内容,并构建了自定义的caddy docker容器。 我只是遵循以下链接中的“构建自己的基于Caddy的图像”...
  • 安装Caddy v2.2.1 安装PHP 7.4 安装MariaDB 使用systemd将Caddy注册为服务 使用file_server,gzip,zstd,php_fastcgi配置Caddyfile 安装WordPress [可选] 经过测试 Ubuntu 18.04 Ubuntu 20.04 用法 bash <...
  • Although JSON is Caddy's native config language, Caddy can accept input from config adapters which can essentially convert any config format of your choice into JSON: Caddyfile, JSON 5, YAML, TOML, ...
  • 检查以生成跨平台的Caddy二进制文件。 执照 该图像是从构建的。 因此,它受该项目的约束,但是它既不包含也不受。 让我们加密订户协议 Caddy可能会提示您同意 。 这可以使用ACME_AGREE环境变量进行配置。 将其设置...
  • authz "/folder/to/caddy_binary/authz_model.conf" "/folder/to/caddy_binary/authz_policy.csv" } respond "Hello, world!" ... } 或者 { order authz before respond } localhost { authz "/folder/to/...
  • StackHead:Caddy Web服务器模块 用于将Caddy用作Web服务器的StackHead模块。 系统要求 需要以下软件才能使用此模块: Python模块 密码库 加密 安装 通过ansible-galaxy安装它: ansible-galaxy install ...
  • :warning_selector: 该插件不再维护,也不与Caddy 2+兼容。 该插件使用在Caddy上创建服务。 为了工作,它在Caddy可执行文件上实现了两个标志: -name ,与服务名称相对应(默认值: Caddy )。 -service ,可以...
  • caddy-json-schema 用于Caddy v2的JSON模式生成器。 可以将生成的模式与编辑器集成,以实现智能感知并获得更好的配置和插件开发体验。 安装 生成的模式适用于球童二进制文件。 即二进制文件中的所有模块都将包含在...
  • 使用Caddy作为您的主要Web服务器非常简单。 但是,当您需要扩展应用程序时,Caddy限于其静态配置。 为了克服这个问题,我们使用docker-gen每次容器产生或死亡时生成配置。 现在扩展很容易! 配置/选项 caddy-gen...
  • 球童源码头工人 Docker Image生成基于Caddy源代码和自定义插件的Caddy服务器映像
  • Caddy介绍

    千次阅读 2018-11-29 17:57:43
    caddy源码地址:https://github.com/mholt/caddy caddy下载地址:https://caddyserver.com/download       这是一个Web Server的时代,apache2与nginx共舞,在追求极致性能的路上,没有最高,只有更高。但这...

    caddy源码地址:https://github.com/mholt/caddy

    caddy下载地址:https://caddyserver.com/download

     

     

     

    这是一个Web Server的时代,apache2nginx共舞,在追求极致性能的路上,没有最高,只有更高。但这又是一个追求个性化的时代,有些Web Server并没有去挤“Performance提升”这一独木桥,而是有着自己的定位,Caddy就是这样一个开源Web Server。

    Caddy的作者Matt Holt在caddy官网以及FAQ中对caddy的目标阐释如下: 其他Web Server为Web而设计,Caddy为human设计。功能定位上,与经常充当最前端反向代理的nginx不同,caddy致力于成为一个易用的静态 文件Web Server。可以看出Caddy主打易用性,使用配置简单。并且得益于Go的跨平台特性,caddy很容易的支持了三大主流平台:Windows、 Linux、Mac。在Caddy开发者文档中,我们可以看到caddy还可以在Android(linux arm)上运行。

    关注caddy,是因为caddy填补了go在通用web server这块的空白(也许有其他,但我还不知道),同时Web server in go也“响应”了近期Golang去C化的趋势(Go 1.5中C is gone!),即便caddy作者提到caddy的目标并非如nginx那样。但未来谁知道呢?一旦Go性能足够高时,一旦caddy足够稳定时,自然而 然的就会有人将其用在某些应用的生产环境中替代nginx或apache2了。一套全Go的系统,在部署、运维方面也是有优势的。

    一、安装和运行caddy

    和诸多go应用一样,我们可以直接从caddy的github.com releases页中找到最新发布版(目前是0.11.1)的二进制包。这里使用的是caddy_darwin_amd64.zip。

    下载解压后,进入目录,直接执行./caddy即可将caddy运行起来。

    $caddy
    0.0.0.0:2015

    在浏览器里访问localhost:2015,页面上没有预期显示的类似"caddy works!”之类的默认Welcome页面,而是“404 Not Found"。虽然这说明caddy已经work了,但没有一个default welcome page毕竟对于caddy beginer来说并不友好。这里已经向作者提了一个sugguestion issue

    二、caddy原理

    Go的net/http标准库已经提供了http server的实现,大多数场合这个http server都能满足你的需要,无论是功能还是性能。Caddy实质上也是一个Go web app,它也import net/http,嵌入*http.Server,并通过handler的ServeHTTP方法为每个请求提供服务。caddy使用 http.FileServer作为处理 静态文件的基础。caddy的诱人之处在于其middleware,将诸多middleware串成一个middleware chain以提供了灵活的web服务。另外caddy中的middleware还可以独立于caddy之外使用。

    caddy从当前目录的Caddyfile(默认)文件中读取配置,当然你也可以通过-conf指定配置文件路径。Caddyfile的配置格式 的确非常easy,这也符合caddy的目标。

    Caddyfile总是以站点的Addr开始的。

    单一站点的Caddyfile样例如下:

    //Caddyfile
    localhost:2015
    gzip
    log ./2015.log

    Caddy也支持配置多个站点,类似virtualhost的 配置(80端口多路复用):

    //Caddyfile
    foo.com:80 {
        log ./foo.log
        gzip
    }

    bar.com:80 {
        log ./bar.log
        gzip
    }

    为了实现风格上的统一,单一站点也最好配置为如下这种格式(代码内部称之为    Server Block):

    localhost:2015 {
        gzip
        log ./2015.log
    }

    这样Caddyfile的配置文件模板样式类似于下面这样:

    host1:port {
        middleware1
        middleware2 {
            … …
        }
        … …
    }

    host2:port {
        middleware1
        middleware2 {
            … …
        }
        … …
    }
    … …

    关于middleware,在caddy文档中有较为详细的说明和例子。对于caddy这样一个年轻的开源项目而言,其文档还算是相对较全的,虽 然现在还不能和nginx、 apache比。

    caddy中的middleware就是一个实现了middleware.Handler接口的struct,例如gzip这个 middleware:

    // middleware.go
    type Middleware func(Handler) Handler
    type Handler interface {
            ServeHTTP(http.ResponseWriter, *http.Request) (int, error)
    }

    // gzip/gzip.go
    type Gzip struct {
        Next middleware.Handler
    }

    func (g Gzip) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
        if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
            return g.Next.ServeHTTP(w, r)
        }
        …. …
        gz := gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w}

        // Any response in forward middleware will now be compressed
        status, err := g.Next.ServeHTTP(gz, r)
        … …
    }

    middleware.Handler的函数原型与http.Handler的不同,不能直接作为http.Server的Handler使用。caddy使用了下面这个idiomatic go pattern:

    type appHandler func(http.ResponseWriter, *http.Request) (int, error)

    func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        if status, err := fn(w, r); err != nil {
            http.Error(w, err.Error(), status)
        }
    }
    当然这个pattern有很多变种,但思路大致类似。一个middleware chain大致就是handler1(handler2(handler3))的调用传递。

    前面说过caddy是基于http.FileServer的静态文件Web Server,FileServer总会作为middleware chain的最后一环,如果没有配置任何middleware,那你的server就是一个静态文件server。

    三、caddy典型应用

    【静态文件Server】

    caddy的最基础应用实际就是一个静态文件Server,底层由http.FileServer承载,当然caddy封装了http.FileServer,做了一些拦截处理,最后将w, r传递给http.ServeContent去处理文件数据。

    第一次执行./caddy,实际上就启动了一个静态文件Server。但这个server不默认支持你navigate directory。如果你知道website root目录(如果没有指定root,则caddy执行的当前路径会作为website的root路径)下的文件名,比如foo.txt,你可以在浏览器 中输入:localhost:2015/foo.txt,caddy会执行正确的服务,浏览器也会显示foo.txt的全文。

    对于静态文件Server,caddy支持在website的root路径下首先查找是否有如下四个文件:

    //caddy/middleware/browse/browse.go
    var IndexPages = []string{
        "index.html",
        "index.htm",
        "default.html",
        "default.htm",
    }

    如果查到有其中一个,则优先返回这个文件内容,这就是静态站点的首页。

    如果要支持目录文件列表浏览,则需要为website配置browse middleware,这样对于无index file的目录,我们可以看到目录文件列表。

    localhost:2015 {
        browse
    }   

    【反向代理】

    caddy支持基本的反向代理功能。反向代理配置通过proxy middleware实现。

    localhost:2015 {
        log ./2015.log

        proxy /foo localhost:9001
        proxy /bar localhost:9002
    }

    当你访问localhost:2015/foo时,实际上访问的是9001端口的服务程序;
    当你访问localhost:2015/bar时,实际上访问的是9002端口的服务程序。

    【负载均衡】

    Caddy支持负载均衡配置,并支持三种负载均衡算法:random(随机)、least_conn(最少连接)以及round_robin(轮询调度)。

    负载均衡同样是通过proxy middleware实现的。

    localhost:2015 {
        log ./2015.log

        proxy / localhost:9001 localhost:9003 {
            policy round_robin
        }
        proxy /bar localhost:9002 localhost:9004 {
            policy least_conn
        }
    }

    【支持fastcgi代理】

    caddy同样支持fastcgi代理,可以将请求通过fastcgi接口发送给后端的实现fastcgi的server。我们以一个"hello world"的php server为例。

    mac os上自带了php-fpm,一个实现了fastcgi的php cgi进程管理器。caddy将请求转发给php-fpm监听的端口,后者会启动php-cgi解释器,解释index.php,并将结果返回给caddy。

    mac os上的php-fpm默认没有随机启动。我们需要简单配置一下:

    $mkdir phptest
    $mkdir -p phptest/etc
    $mkdir -p phptest/log
    $cd phptest
    $sudo cp /private/etc/php-fpm.conf.default ./etc
    $cd ./etc
    $sudo chown tony php-fpm.conf.default
    $mv php-fpm.conf.default php-fpm.conf

    编辑php-fpm.conf,保证下面两项是非注释状态的:

    error_log = log/php-fpm.log
    listen = 127.0.0.1:9000 

    我们通过network socket进行fastcgi通信。

    回到phptest目录下,执行:

    php-fpm -p ~/test/go/caddy/phptest

    执行后,php-fpm就会转入后台执行了。

    接下来我们来配置Caddyfile:

    localhost:2015 {
        fastcgi / 127.0.0.1:9000 php
        log ./2015.log
    }

    这里配置的含义是:将全部请求转发到9000端口,这里的php是一个preset(预配置集合),相当于:

    ext   .php
    split .php
    index index.php

    我们在phptest目录下创建一个index.php文件,内容如下:

    <?php echo "Hello World\n"; ?>

    好了,现在启动caddy,并使用浏览器访问localhost:2015试试。你会看到"Hello World"呈现在浏览器中。

    【git push发布】

    对于一些静态站点,caddy支持git directive,实现在server启动以及运行时定期git pull你的项目库,将最新更新pull到server上。

    caddy文档中给出两个例子:

    第一个是一个php站点,定期pull项目库,实现server更新:

    git git@github.com:user/myphpsite {
        key /home/user/.ssh/id_rsa
    }
    fastcgi / 127.0.0.1:9000 php

    第二个是一个hugo支撑的静态站点,每次pull后,执行hugo命令生成新的静态页面:

    git github.com/user/site {
        path  ../
        then  hugo –destination=/home/user/hugosite/public
    }

    注意:git directive并非middleware,而是一个单独的goroutine实现的。

    四、小结

    caddy的功能不局限于上面的几个例子,上面只是几个最为常见的场景而已。caddy目前还很年轻,应用不多,但知名golang网站 gopheracademy.com(GopherCon组织方)是由Caddy support的。caddy还在积极进化,有兴趣的Gopher可持续关注。

     

    原文地址:https://tonybai.com/2015/06/04/caddy-a-web-server-in-go/

     

    展开全文
  • 使用Docker Compose在Caddy 2.3.0之后部署WordPress 克隆存储库, git clone https://github.com/rajaseg/caddy-WordPress和cd caddy-WordPress转到“安装”步骤以设置WordPress 储存库的内容 该存储库包含在Caddy...
  • CADDY-DOCKER-PROXY CADDY V2! 此插件已更新到Caddy V2。 Master分支和Docker CI映像现在专用于V2。 介绍 该插件使caddy可用作Docker的反向代理。 它是如何工作的? 它扫描Docker元数据以查找指示该服务或容器应...
  • 使用基本配置集安装以包含/etc/caddy/conf.d/* 。 希望使用 Caddy 的角色应将其配置文件放在该目录中。 此角色现在支持从存储库安装的 Caddy v2。 有一个任务文件可用于清理以前的 Caddy v1 安装。 它应该在运行...
  • Caddy群集/ Certmagic TLS群集对Redis的支持 该插件基于。 大多数方面也相似,我几乎复制了加密实现。 我使用的原因是因为它更易于设置。 目前,这将支持Redis作为单个实例或具有副本,但不支持集群。 这个插件利用...
  • 使用 Caddy 服务器 目的 让我们学习如何使用 Caddy Web 服务器并检查其功能。 测试 安装卷曲 $ sudo apt-get install curl $ curl --version curl 7.38.0 (x86_64-pc-linux-gnu) libcurl/7.38.0 OpenSSL/1.0.1f ...
  • 达到指定的速率限制后,可以拒绝请求。 请注意,该模块仍未完成,可能存在错误。 请尝试一下并提交错误报告,谢谢! 特征 多个限速区 ...要使用此模块构建Caddy,请使用 : $ xcaddy build --with git
  • caddy-docker在这里为您提供帮助。 通过使用特殊标签创建容器,caddy-docker将自动为生成适当的配置文件并重新加载应用程序。 而且由于Caddy使用Let's Encrypt获得免费的TLS证书,因此要启动并运行启用TLS的服务,...
  • caddy-mwcache :warning: 工作正在进行中 caddy-mwcache是的缓存插件。 待办事项清单 处理caddyfile指令 将缓存存储到后端 使用缓存的响应 不缓存重定向 缓存头 请勿打扰已登录的活动 提供清除ACL指令 ...
  • 带有插件的Caddy Web服务器 版本:linux-amd64 目录 Filemanager语法: filemanager [url] [scope] { database path no_auth recaptcha_key key recaptcha_secret secret alternative_recaptcha locale [en|...
  • 现在caddy2了不在介绍之前的caddy1语法. 最常用见的就是yii2 、tp 等等这样的伪静态规则 if (!-e $request_filename){ rewrite '^(.*)$' /index.php$1 last; break; } 我们想转成caddy2的规则怎么弄呢?官方...

    现在caddy2了不在介绍之前的caddy1语法.

    最常用见的就是yii2 、tp 等等这样的伪静态规则

    if (!-e $request_filename){
    
    rewrite '^(.*)$' /index.php$1 last;
    
    break;
    
    }

    我们想转成caddy2的规则怎么弄呢?官方文档也有不少介绍。

    伪静态caddy2介绍:

    https://caddyserver.com/docs/caddyfile/directives/rewrite

    需要用到匹配器:

    https://caddyserver.com/docs/caddyfile/matchers

    匹配器有如下:

    file

    header

    header_regexp

    host

    method

    not

    path

    path_regexp

    protocol

    query

    remote_ip

    所有请求重写到foo.html

    rewrite * /foo.html

    规则看起来和nginx还有点差异,怎么转yii2和tp这类伪静态?

    @key0 {
    
    not file
    
    path_regexp key0 '(.*)'
    
    }
    
    rewrite @key0 /index.php{re.key0.1}

    要留意空格,都是一个空格。

    @key0中 not file 是告诉不存在一个文件时候则重写

    伪静态转换工具:https://www.toolnb.com/tools/rewriteTools.html

    支持nginx、apache、iis、caddy

    程序员工具:https://www.toolnb.com/

    展开全文
  • Ghost-over-Caddy:此摘要将通过Caddy Web服务器安装Ghost博客作为反向代理
  • Caddy 源码阅读

    2020-03-01 22:22:35
    Caddy Caddy 是一个go编写的web server。 类似于nginx。 官网上是这么介绍的:Caddy is the HTTP/2 web server with automatic HTTPS. (说实话官网v1版本的介绍并不怎么清楚,反而是v2版本的介绍更明确) Caddy的...

    Caddy

    Caddy 是一个go编写的轻量配置化web server。
    类似于nginx。有丰富的插件,配置也很简单,自定义插件也很容易。更人性化。
    官网上是这么介绍的:Caddy is the HTTP/2 web server with automatic HTTPS.
    (说实话官网v1版本的介绍并不怎么清楚,反而是v2版本的介绍更明确)

    Caddy官方文档: https://caddyserver.com/v1/tutorial
    GitHub地址: https://github.com/caddyserver/caddy

    Caddy的功能

    官方首页盗来的图
    TLS证书的续订 : TLS certificate renewal。
    Caddy可以通过ACME协议(Let’s Encrypt 为了实现自动化证书管理,制订了 ACME 协议)和Let’s Encrypt(一个免费、开放、自动化的数字证书认证机构)进行证书的签发、续订等。这也是官方介绍的automatic HTTPS.
    (这个功能尝试了一次因为网络timeout,就没有继续研究。等有需求的时候,再尝试吧。)

    OCSP装订: OCSP Staplin。
    在线证书状态协议(Online Certificate Status )Protocol),简称 OCSP,是一个用于获取 X.509 数字证书撤销状态的网际协议。 Web 服务端将主动获取 OCSP 查询结果,并随证书一起发送给客户端,以此让客户端跳过自己去寻求验证的过程,提高 TLS 握手效率。

    静态文件服务器: static file serving。
    这个可以用来进行文件管理、文件上传、基于 MarkDown 的博客系统等等。只需简单的配置。附链接:Caddy服务器搭建和实现文件共享
    (这个值得尝试,之后打算用caddy搭建一个MarkDown的博客玩玩)

    反向代理
    这个和nginx的功能一样。什么叫反向代理,什么叫正向代理,请看我的这篇博客:反向代理和正向代理

    Kubernetes入口 : Kubernetes Ingress.
    k8s集群的网络入口。这个是要和traefik抢工作啊。(还是习惯使用treafik,不打算尝试caddy)。

    自定义中间件
    这个可nginx可以写lua插件一样,caddy也支持写插件。是用golang写,得益于caddy代码结构的组织,在caddy源码基础上扩展很容易。(TODO: 下一篇文章写怎么给caddy添加插件)

    自定义服务器(ServerType)
    Caddy本身是一个http服务器(const serverType = "http") ,但是通过扩展ServerType可以变成 SSH、SFTP、TCP等等,教科书一样的典范是DNS服务器CoreDNS

    Caddy代码目录

    下面是caddy项目源码的目录,去除了相关文档文件、test文件等。

    directives 指令: 比如log、limits、proxy都是指令。

    .
    ├── access.log          // 访问日志
    ├── assets.go           // 工具方法,用来获取环境变量CADDYPATH和用户目录的路径
    ├── caddy
    │   ├── caddymain
    │   │   ├── run.go          // caddy服务启动入口,解析参数、日志输出、读取Caddyfile\设置cpu等等。
    │   ├── main.go           // 程序入口, main函数
    ├── caddy.go            // 定义了caddy服务器相关概念和接口     
    ├── caddyfile           // caddy的配置文件caddyfile的解析和使用
    │   ├── dispenser.go      // 定义了一系列方便使用配置里Token的方法,如Next、NextBlock、NextLine等
    │   ├── json.go           // caddyfile同样支持json,两种形式可以用caddy相互转换
    │   ├── lexer.go          // 词法分析器
    │   ├── parse.go          // 读入配置文件并使用lexer进行解析
    ├── caddyhttp           // caddy的http服务器
    │   ├── caddyhttp.go      // 用来加载插件,import了所有caddy http服务器相关的指令(中间件)
    │   ├── httpserver
    │   │   ├── error.go        // 定义了常见的错误,都实现了error接口
    │   │   ├── https.go        // 处理https相关逻辑
    │   │   ├── logger.go       // 日志相关逻辑
    │   │   ├── plugin.go       // httpserver插件逻辑,定义了一个directives的字符串slice,自定义插件时,这里要改!!
    │   │   ├── server.go       // HTTP server的实现,包裹了一层标准库的http.Server
    │   │   ├── ...
    │   ├── bind
    │   ├── browse
    │   ├── errors
    │   ├── basicauth
    │   ├── ...
    ├── caddytls             // caddy tls 相关逻辑,不影响主要流程先不看。
    ├── commands.go          // 命令行终端命令的处理逻辑,处理终端执行的时候加入的参数。
    ├── controller.go        // 用于从配置caddyfile中的配置来设置directive
    ├── onevent              // 插件,on在触发指定事件时执行命令。举个栗子:在服务器启动时,启动php-fpm。
    ├── plugins.go           // 维护了caddy的所有插件,event hook等
    ├── rlimit_nonposix.go
    ├── rlimit_posix.go      // 启动服务器的时候,如果文件句柄数限制过低就提醒你设置ulimits
    ├── sigtrap.go           // 信号机关,用来处理信号,如中断、挂起等。
    ├── sigtrap_nonposix.go
    ├── sigtrap_posix.go
    ├── telemetry            // 遥测,就是监控。个人觉得使用prometheus的exporter做更好。
    └── upgrade.go           // 热更新
    

    Caddy 启动过程

    main 入口

    caddy/main.go

    package main
    
    import "github.com/caddyserver/caddy/caddy/caddymain"
    
    var run = caddymain.Run // replaced for tests
    
    func main() {
    	run()
    }
    

    main函数很简单,引用了caddmain包,调用了caddy/caddymain/run.go的run函数。下面主要看run函数怎么去启动服务器的:

    run函数

    run函数代码比较长,首先大致看下run.go文件里有哪些东西。包内函数(首字母小写的)先忽略,因为最能提现一个包的主要职责的是它的import、包内变量、init函数、导出函数(首字母大写的函数)、导出结构体(首字母大写的结构体)。

    package caddymain
    
    import (
    	...
    
    	_ "github.com/caddyserver/caddy/caddyhttp" // plug in the HTTP server type
    	// This is where other plugins get plugged in (imported)
    )
    
    const appName = "Caddy"    // 这里定义了app的名字。
    
    // Flags that control program flow or startup
    // 定义了程序启动的一些参数,这些参数从运行时指定(从os.Args中解析)。
    // 这里定义的是程序启动后,就不能更改的。 配置文件中的参数是程序启动后,还可以热更新的。
    var (
    	serverType      string
    	conf            string
    	cpu             string
    	envFile         string
    	...
    )
    
    // EnableTelemetry defines whether telemetry is enabled in Run.
    var EnableTelemetry = true    // 遥测启动开关,不是重点,忽略。
    
    func init() {...} 
    
    // Run is Caddy's main() function.
    func Run() {...}
    

    根据执行顺序来看,main包import了caddymain这个包,且调用了caddymain.Run。那么执行步骤如下:

    1. import caddymain包的相关依赖,这里需要看看import却没有使用的caddyhttp包做了什么操作。
      在这里插入图片描述
      可以看到caddyhttp包都是在import其他的包, 这些被caddyhttp import的包,就是caddy官方自带的相关插件,和httpserver这个server plugin。
      稍后再看plugin的具体实现。
    2. 初始化caddymain包中的包内变量。
    3. 执行caddymain包的init函数。
      不管什么包,init函数的作用都很明确:初始化相关包内变量,读取相关配置、参数等等。caddymain的init函数也不例外。
      func init() {
          caddy.TrapSignals()     // 捕捉信号量,处理
      	// 解析启动参数,flag包是从os.Args中解析的。
          flag.BoolVar(&certmagic.Default.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
      	flag.StringVar(&certmagic.Default.CA, "ca", certmagic.Default.CA, "URL to certificate authority's ACME server directory")
      	flag.StringVar(&certmagic.Default.DefaultServerName, "default-sni", sable")
      	...
          // 注册加载caddyfile的loader.
      	caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
      	caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
      	}
      
    4. 调用Run函数。
      在caddy这个项目中,caddymain.Run才是程序的"main"函数。
      这里忽略了一些无关紧要的逻辑和某些执行一次就退出的命令,如 caddy -plugins 、 caddy -validate只关注caddy服务器启动过程相关的步骤。
    func Run() {
        // 1. 解析命令行参数,不调用这个的话,init里面绑定的变量就都是默认值了。
    	flag.Parse()
        
        // 2. log怎么输出,输出到哪里,日志文件怎么平滑滚动。
    	// Set up process log before anything bad happens
    	switch logfile {
    	case "stdout":
    		log.SetOutput(os.Stdout)
    	case "stderr":
    		log.SetOutput(os.Stderr)
    	case "":
    	    ...
    	}
        
        // 3. 加载环境变量并设置。   
    	// load all additional envs as soon as possible
    	if err := LoadEnvFromFile(envFile); err != nil {
    		mustLogFatalf("%v", err)
    	}
        
        // 4. 初始化遥测相关逻辑
    	// initialize telemetry client
    	if EnableTelemetry {
    		err := initTelemetry()
    		...
    	}
    	...
        // 5. 可以把caddyfile从json和普通模式互相转换
    	// Check if we just need to do a Caddyfile Convert and exit
    	checkJSONCaddyfile()
    	
    	// 6. 设置cpu使用,最小不能小于1
    	// Set CPU cap
    	err := setCPU(cpu)
    	if err != nil {
    		mustLogFatalf("%v", err)
    	}
        
        // 7. 发送Startup事件,然后调用EventHook去处理这个事件。
        //    EventHook处理过程要用goroutine去处理,防止阻塞。
    	// Executes Startup events
    	caddy.EmitEvent(caddy.StartupEvent, nil)
    
        // 8. 去加载caddyfile文件,根据插件定义的loader去加载。
        // 详细内容可以看LoadCaddyfile的函数备注
    	// Get Caddyfile input
    	caddyfileinput, err := caddy.LoadCaddyfile(serverType)
    	if err != nil {
    		mustLogFatalf("%v", err)
    	}
    
        // 9. 启动服务器!!!
    	// Start your engines
    	instance, err := caddy.Start(caddyfileinput)
    	if err != nil {
    		mustLogFatalf("%v", err)
    	}
         
        // 10. 阻塞主进程,防止main goroutine退出
        //     内部就是调用了sync.WaitGroup的Wait方法。
    	// Twiddle your thumbs
    	instance.Wait()
    }
    

    Run函数的逻辑也很清楚: 1.处理参数,读取配置 2. 调用Start方法 3. Wait阻塞main goroutine.

    Start 函数

    Start函数位于 项目根目录下的 caddy.go文件中。
    为什么要这样组织文件结构呢? 为什么不把start函数也放在caddymain包里面呢?
    这是为了方便别的项目来引用caddy。如果放在caddymain,别的项目引入的时候就只需要import "github.com/caddyserver/caddy"从这里可以看出caddy这个项目的定位,不仅仅是一个web server, 还可以是一个lib库。

    // Start starts Caddy with the given Caddyfile.
    //
    // This function blocks until all the servers are listening.
    func Start(cdyfile Input) (*Instance, error) {
        // 1. 初始化一个instance, Run函数末尾调用的instance.Wait()就是调用的这个instance里面的wg。
    	inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})}
    	// 2. 根据这个instance和caddyfile的配置启动服务器
    	err := startWithListenerFds(cdyfile, inst, nil)
    	if err != nil {
    		return inst, err
    	}
    	// 3. 给父进程发送成功启动信号
    	//    这里只要在upgrade的时候才有用,upgrade的时候父进程fork子进程,子进程成功执行完startWithListenerFds后,通过管道发送success给父进程,父进程再kill self。
    	signalSuccessToParent()
    	if pidErr := writePidFile(); pidErr != nil {
    		log.Printf("[ERROR] Could not write pidfile: %v", pidErr)
    	}
    
        // 4. 发送instance start up 事件,调用对此事件感兴趣的hook函数。
    	// Execute instantiation events
    	EmitEvent(InstanceStartupEvent, inst)
    
    	return inst, nil
    }
    

    到这一步整个服务器还没有运作起来,还无法监听端口,处理请求。startWithListenerFds函数里面开始用Caddyfile文件定义的配置启动相关的Services(注意是复数,有多少个Server取决于Caddyfile里面的定义)。

    startWithListenerFds 函数
    func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartTriple) error {
        // 1. 这里把instance保存到了一个包内变量instances的slice里面, 
        // 一个instance代表服务的实例,如http服务器 dns服务器等。
        // 对instances的操作都加上锁了,防止并发问题。
    	instancesMu.Lock()
    	instances = append(instances, inst)
    	instancesMu.Unlock()
    	var err error
    	defer func() {
    	    // 当instance处理失败了,需要从instances 这个slice中移除。
    		if err != nil {
    			instancesMu.Lock()
    			for i, otherInst := range instances {
    				if otherInst == inst {
    					instances = append(instances[:i], instances[i+1:]...)
    					break
    				}
    			}
    			instancesMu.Unlock()
    		}
    	}()
    
        // 2. 这里处理Caddyfile, 验证Caddyfile里面的directives(指令)是否有效可用。
    	if cdyfile == nil {
    		cdyfile = CaddyfileInput{}
    	}
    	err = ValidateAndExecuteDirectives(cdyfile, inst, false)
    	if err != nil {
    		return err
    	}
    
        // 3. 这里是Make Servers,就是产生instance里面所有的server
        // 比如instance代表了一个http服务器, serverA就是其中监听在8080端口的一个http服务,
        // serverB是另一个监听在8020的http服务。 这里同时被make出来。
    	slist, err := inst.context.MakeServers()
    	if err != nil {
    		return err
    	}
    	
    	// 4. 处理start up的相关callback, 先不关注其中的细节。
        ...
        
        // 5. 上面创建了servers 这里统一启动起来
    	err = startServers(slist, inst, restartFds)
    	if err != nil {
    		return err
    	}
    	
        // 6. 处理after start up的相关callback, 先不关注其中的细节。
        ... 
    
    	mu.Lock()
    	started = true
    	mu.Unlock()
    
    	return nil
    }
    

    startWithListenerFds调用了MakeServers() 产生若干个(具体有多少个,看Caddyfile怎么定义)服务的实例,startServers又把这些服务实例启动起来。 而且在启动服务前后,还会执行一些callback函数。
    执行逻辑顺序如下:

    1. MakeServers()
      MakeServers是plugins.go文件内Context接口的一个方法,放在接口里的作用,自然是方便扩展。plugins.go被放在根目录下,说明caddy可以很支持外部自定义其他ServerType的Instance。
      因为golang的接口和实现是松耦合的,很难从接口定义去找到实现它的实例,反过来也是。这里想找到MakeServers的实现,一可以通过逻辑判断,二可以通过IDE的全局搜索功能。
      Caddy项目自带了http这种ServerType的实现,于是去caddyhttp/httpserver里面找,果不其然在caddyhttp/httpserver/plugin.go里面找到了。
    func (h *httpContext) MakeServers() ([]caddy.Server, error) {
        // 这里用到"github.com/mholt/certmagic"这个包来做tls和https相关的事情,
        // 因为没有实际用过certmagic, 这里的代码也跳过。
        // CertMagic - 利用Go程序管理TLS证书的颁发和续订,自动化添加HTTPS
    	...
    
        // 前面讲过每个server实例绑定了一个端口,这里是把配置分组,按照端口不同来分组
    	groups, err := groupSiteConfigsByListenAddr(h.siteConfigs)
    	if err != nil {
    		return nil, err
    	}
    	// 根据每个端口,和定义在这个端口上的相关配置来生成一个Server实例
    	var servers []caddy.Server
    	for addr, group := range groups {
    		s, err := NewServer(addr, group)
    		if err != nil {
    			return nil, err
    		}
    		servers = append(servers, s)
    	}
    
    	// 判断是dev还是prod环境
    	deploymentGuess := "dev"
    	if looksLikeProductionCA && atLeastOneSiteLooksLikeProduction {
    		deploymentGuess = "prod"
    	}
    	telemetry.Set("http_deployment_guess", deploymentGuess)
    	telemetry.Set("http_num_sites", len(h.siteConfigs))
    	return servers, nil
    }
    
    1. run startup callbacks
      (和hook、callback相关的先不分析。)
    2. startServers()
      MakeServer函数被抽象成接口了,为什么startServer没有呢? 这是因为Server本身就是一个接口, startServers的主要逻辑其实就是调用Listen 和 Serve。
    type Server interface {
       TCPServer
       UDPServer
    }
    
    type TCPServer interface {
       Listen() (net.Listener, error)
       Serve(net.Listener) error
    }
    type UDPServer interface {
       ListenPacket() (net.PacketConn, error)
       ServePacket(net.PacketConn) error
    }
    
    func startServers(serverList []Server, inst *Instance, restartFds map[string]restartTriple) error {
    
        // 服务启动或处理过程产生的错误就往这个channel中塞
    	errChan := make(chan error, len(serverList)) 
    	// 用来控制记录错误日志的goroutine在记录完日志后能退出
    	stopChan := make(chan struct{})
    	// 保证控制server异常退出后,所有错误日志能被记录到
    	stopWg := &sync.WaitGroup{}
        
        // 根据传入的server list 遍历处理
        // 每个server绑定相应的tcp listener和udp listener。并将server添加到instance里面
        // TODO: upgrade和reload过程中文件描述符的操作没有看懂, 先省略,后续文章补上
    	for _, s := range serverList {
    		var (
    			ln  net.Listener
    			pc  net.PacketConn
    			err error
    		)
    		...	
    		if ln == nil {
    			ln, err = s.Listen()
    			if err != nil {
    				return fmt.Errorf("Listen: %v", err)
    			}
    		}
    		if pc == nil {
    			pc, err = s.ListenPacket()
    			if err != nil {
    				return fmt.Errorf("ListenPacket: %v", err)
    			}
    		}
    
    		inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc})
    	}
    	
        // 遍历instance的server, 调用server的Serve方法监听。出错的话就把错误塞入errChan这个管道里面
        // 每个server都起了两个goroutine,分别监听tcp和udp.
        // 这里使用WaitGroup来同步goroutine,
        // instance的wg用来防止main goroutine退出。
        // stopWg用来挂起最下面那个goroutine。
        // 这样能保证,只要有一个server还在监听着,就不会导致main goroutine退出,也不会导致记录错误日志的goroutine退出。
    	for _, s := range inst.servers {
    		inst.wg.Add(2)
    		stopWg.Add(2)
    		func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) {
    			go func() {
    				defer func() {
    					inst.wg.Done()
    					stopWg.Done()
    				}()
    				errChan <- s.Serve(ln)
    			}()
    
    			go func() {
    				defer func() {
    					inst.wg.Done()
    					stopWg.Done()
    				}()
    				errChan <- s.ServePacket(pc)
    			}()
    		}(s.server, s.listener, s.packet, inst)
    	}
    
    	// 这个goroutine用来记录从errChan来的错误
    	go func() {
    		for {
    			select {
    			case err := <-errChan:
    				if err != nil {
    					if !strings.Contains(err.Error(), "use of closed network connection") {
    						// this error is normal when closing the listener; see https://github.com/golang/go/issues/4373
    						log.Println(err)
    					}
    				}
    			case <-stopChan:
    				return
    			}
    		}
    	}()
    
        // 这个goroutine用来控制,当所有server都退出后,停止上面那个记录错误日志的goroutine.
    	go func() {
    		stopWg.Wait()
    		stopChan <- struct{}{}
    	}()
    
    	return nil
    }
    
    1. run any AfterStartup callbacks
      (和hook、callback相关的先不分析。)

    以上就是Caddy服务启动的过程。

    TODO: 有些详细的实现还没有具体看完,之后再其他文章里面详细讲解。

    展开全文
  • 球童分机 各种Caddy v2扩展(也称为模块)。 扩展名 执照
  • 这是官方caddy Docker映像源所在的回购。 请参阅以获取文档。 如果您对Docker映像有任何问题或建议,请。 如果您想建议对更新,请参阅 。 执照 查看此映像中包含的软件的。
  • Caddy-Web-Server-Installer, 管理Caddy服务器的脚本 这是一个简单的脚本来设置Caddy服务器并在基于Linux的操作系统上维护快速安装wget https://git.io/vra5C -O - -o/dev/null|bash有关详细说明,请参阅安装 。...
  • 该插件使caddy可以运行和监督后台进程。 这个怎么运作 对于每个主管球童指令,命令都会在后台执行,并在球童停止时终止。 您可以将超级用户插件用作http指令或服务器类型。 主管http指令 您可以使用以下方法直接从...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,611
精华内容 644
关键字:

Caddy