本次系列會講解 caddy 整個生命週期涉及到的源碼。git
平時咱們使用 caddy 都是使用 它的 二進制 分發文件,如今來分析 caddy 的 Run 函數。
從最外層邏輯看它都作了些什麼。github
咱們來看看 Caddy Run 中引入了哪些包和操做,對 Caddy 的整體行爲作一個概覽caddy/caddymain/run.go
首先看 init 函數json
func init() { caddy.TrapSignals() 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", certmagic.Default.DefaultServerName, "If a ClientHello ServerName is empty, use this ServerName to choose a TLS certificate") flag.BoolVar(&certmagic.Default.DisableHTTPChallenge, "disable-http-challenge", certmagic.Default.DisableHTTPChallenge, "Disable the ACME HTTP challenge") flag.BoolVar(&certmagic.Default.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", certmagic.Default.DisableTLSALPNChallenge, "Disable the ACME TLS-ALPN challenge") flag.StringVar(&certmagic.Default.Email, "email", "", "Default ACME CA account email address") flag.DurationVar(&certmagic.HTTPTimeout, "catimeout", certmagic.HTTPTimeout, "Default ACME CA HTTP timeout") flag.StringVar(&revoke, "revoke", "", "Hostname for which to revoke the certificate") flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable") flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")") flag.StringVar(&cpu, "cpu", "100%", "CPU cap") flag.BoolVar(&printEnv, "env", false, "Enable to print environment variables") flag.StringVar(&envFile, "envfile", "", "Path to file with environment variables to load in KEY=VALUE format") flag.BoolVar(&fromJSON, "json-to-caddyfile", false, "From JSON stdin to Caddyfile stdout") flag.BoolVar(&toJSON, "caddyfile-to-json", false, "From Caddyfile stdin to JSON stdout") flag.BoolVar(&plugins, "plugins", false, "List installed plugins") flag.StringVar(&logfile, "log", "", "Process log file") flag.IntVar(&logRollMB, "log-roll-mb", 100, "Roll process log when it reaches this many megabytes (0 to disable rolling)") flag.BoolVar(&logRollCompress, "log-roll-compress", true, "Gzip-compress rolled process log files") flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file") flag.BoolVar(&caddy.Quiet, "quiet", false, "Quiet mode (no initialization output)") flag.StringVar(&serverType, "type", "http", "Type of server to run") flag.BoolVar(&version, "version", false, "Show version") flag.BoolVar(&validate, "validate", false, "Parse the Caddyfile but do not start the server") caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader)) caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader)) }
而後咱們來看 Run()
函數 Run() 函數全文可在最下看到promise
conf
用來設置 Caddyfile 的文件路徑,是能夠從 Stdin 即終端輸入配置的。
會在如下兩個在 init() 中的函數調用更改服務器
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader)) caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
而注意到這裏使用的 Loader ,能夠用來自定義 caddyfile 的裝載方式。app
// confLoader loads the Caddyfile using the -conf flag. func confLoader(serverType string) (caddy.Input, error) { if conf == "" { return nil, nil } if conf == "stdin" { return caddy.CaddyfileFromPipe(os.Stdin, serverType) } var contents []byte if strings.Contains(conf, "*") { // Let caddyfile.doImport logic handle the globbed path contents = []byte("import " + conf) } else { var err error contents, err = ioutil.ReadFile(conf) if err != nil { return nil, err } } return caddy.CaddyfileInput{ Contents: contents, Filepath: conf, ServerTypeName: serverType, }, nil }
注意到這裏返回的 caddy.Input 類型,它表明 caddyfile 在程序中的 structure函數
log
設置 日誌 輸出在什麼地方,log-roll-mb
log-roll-compress
是設置 log 文件大小限制,達到限制的時候會放棄舊的日誌。還有 文件的壓縮選項ui
// Set up process log before anything bad happens switch logfile { case "stdout": log.SetOutput(os.Stdout) case "stderr": log.SetOutput(os.Stderr) case "": log.SetOutput(ioutil.Discard) default: if logRollMB > 0 { log.SetOutput(&lumberjack.Logger{ Filename: logfile, MaxSize: logRollMB, MaxAge: 14, MaxBackups: 10, Compress: logRollCompress, }) } else { err := os.MkdirAll(filepath.Dir(logfile), 0755) if err != nil { mustLogFatalf("%v", err) } f, err := os.OpenFile(logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { mustLogFatalf("%v", err) } // don't close file; log should be writeable for duration of process log.SetOutput(f) } }
咱們看前 8 個 flag 選項,都是設定 TLS 配置的。使用的是 CertMagic ,同時做者也把它放出來能夠被其餘的 Go 語言程序集成,若是想要爲本身的 Go 程序添加 TLS 加密傳輸,就使用它吧。他還支持自動續期,自己是使用的 ACME 客戶端集成。
自己大部分不須要設置,會使用默認設置幫助你啓用 HTTPS 。
介紹一下 revoke 選項,這裏會調用 CertMagic 的 Certificate revocation (please, only if private key is compromised)來撤銷證書this
// Check for one-time actions if revoke != "" { err := caddytls.Revoke(revoke) if err != nil { mustLogFatalf("%v", err) } fmt.Printf("Revoked certificate for %s\n", revoke) os.Exit(0) }
而後是 disabled-metrics
選項,用來關閉一些不須要的 遙測指標
注意到這裏的 initTelemetry() 函數,其中會使用 這裏輸入的選項進行遙測的關閉。加密
// initialize telemetry client if EnableTelemetry { err := initTelemetry() if err != nil { mustLogFatalf("[ERROR] Initializing telemetry: %v", err) } } else if disabledMetrics != "" { mustLogFatalf("[ERROR] Cannot disable specific metrics because telemetry is disabled") }
而後會在 Run() 的最後部分進行相應遙測模塊的 設置
// Begin telemetry (these are no-ops if telemetry disabled) telemetry.Set("caddy_version", module.Version) telemetry.Set("num_listeners", len(instance.Servers())) telemetry.Set("server_type", serverType) telemetry.Set("os", runtime.GOOS) telemetry.Set("arch", runtime.GOARCH) telemetry.Set("cpu", struct { BrandName string `json:"brand_name,omitempty"` NumLogical int `json:"num_logical,omitempty"` AESNI bool `json:"aes_ni,omitempty"` }{ BrandName: cpuid.CPU.BrandName, NumLogical: runtime.NumCPU(), AESNI: cpuid.CPU.AesNi(), }) if containerized := detectContainer(); containerized { telemetry.Set("container", containerized) } telemetry.StartEmitting()
而後是環境變量的設置 env
、envfile
,分別是打印出環境信息和能夠設置環境變量文件的位置
// load all additional envs as soon as possible if err := LoadEnvFromFile(envFile); err != nil { mustLogFatalf("%v", err) } if printEnv { for _, v := range os.Environ() { fmt.Println(v) } }
cpu
設定 cpu 使用限制
// Set CPU cap err := setCPU(cpu) if err != nil { mustLogFatalf("%v", err) }
這是用來啓用插件的。
// Executes Startup events caddy.EmitEvent(caddy.StartupEvent, nil)
type
設定 caddy 啓動的服務器類型,這是由於 caddy 能啓動的服務器不少,只要知足了 Server 的接口就可使用 caddy。
這裏調用的 LoadCaddyfile 能夠認爲是 Caddy 配置最重要的部分。
// Get Caddyfile input caddyfileinput, err := caddy.LoadCaddyfile(serverType) if err != nil { mustLogFatalf("%v", err) }
pidfile
設置寫入 pid 文件的路徑, quiet
設置 caddy 不輸出初始化信息的啓動 。
version
用來顯示 caddy 版本,plugins
用來列出已經安裝的 插件
if version { if module.Sum != "" { // a build with a known version will also have a checksum fmt.Printf("Caddy %s (%s)\n", module.Version, module.Sum) } else { fmt.Println(module.Version) } os.Exit(0) } if plugins { fmt.Println(caddy.DescribePlugins()) os.Exit(0) }
validate
是驗證 caddyfile 的可行性,他會檢測 caddyfile 可是不會啓動一個新的 Server。注意到在這裏調用了 os.Exit(0) 退出。
if validate { err := caddy.ValidateAndExecuteDirectives(caddyfileinput, nil, true) if err != nil { mustLogFatalf("%v", err) } msg := "Caddyfile is valid" fmt.Println(msg) log.Printf("[INFO] %s", msg) os.Exit(0) }
json-to-caddyfile
和 caddyfile-to-json
從 JSON 文件中讀取 caddyfile 的配置文件,或者將 caddyfile 文件輸出爲 JSON 格式。
// Check if we just need to do a Caddyfile Convert and exit checkJSONCaddyfile()
根據 flag 選項進行 Caddyfile 和 JSON 直接的轉換。
func checkJSONCaddyfile() { if fromJSON { jsonBytes, err := ioutil.ReadAll(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "Read stdin failed: %v", err) os.Exit(1) } caddyfileBytes, err := caddyfile.FromJSON(jsonBytes) if err != nil { fmt.Fprintf(os.Stderr, "Converting from JSON failed: %v", err) os.Exit(2) } fmt.Println(string(caddyfileBytes)) os.Exit(0) } if toJSON { caddyfileBytes, err := ioutil.ReadAll(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "Read stdin failed: %v", err) os.Exit(1) } jsonBytes, err := caddyfile.ToJSON(caddyfileBytes) if err != nil { fmt.Fprintf(os.Stderr, "Converting to JSON failed: %v", err) os.Exit(2) } fmt.Println(string(jsonBytes)) os.Exit(0) } }
啓動服務器
// Start your engines instance, err := caddy.Start(caddyfileinput) if err != nil { mustLogFatalf("%v", err) }
看完 caddy 的 run 函數,瞭解了程序的啓動配置了哪些東西,相應的會有一些問題。帶着這些問題繼續看源碼,可以深刻了解 caddy 內的邏輯。
Run() 函數源碼
// Run is Caddy's main() function. func Run() { flag.Parse() module := getBuildModule() cleanModVersion := strings.TrimPrefix(module.Version, "v") caddy.AppName = appName caddy.AppVersion = module.Version certmagic.UserAgent = appName + "/" + cleanModVersion // Set up process log before anything bad happens switch logfile { case "stdout": log.SetOutput(os.Stdout) case "stderr": log.SetOutput(os.Stderr) case "": log.SetOutput(ioutil.Discard) default: if logRollMB > 0 { log.SetOutput(&lumberjack.Logger{ Filename: logfile, MaxSize: logRollMB, MaxAge: 14, MaxBackups: 10, Compress: logRollCompress, }) } else { err := os.MkdirAll(filepath.Dir(logfile), 0755) if err != nil { mustLogFatalf("%v", err) } f, err := os.OpenFile(logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { mustLogFatalf("%v", err) } // don't close file; log should be writeable for duration of process log.SetOutput(f) } } // load all additional envs as soon as possible if err := LoadEnvFromFile(envFile); err != nil { mustLogFatalf("%v", err) } if printEnv { for _, v := range os.Environ() { fmt.Println(v) } } // initialize telemetry client if EnableTelemetry { err := initTelemetry() if err != nil { mustLogFatalf("[ERROR] Initializing telemetry: %v", err) } } else if disabledMetrics != "" { mustLogFatalf("[ERROR] Cannot disable specific metrics because telemetry is disabled") } // Check for one-time actions if revoke != "" { err := caddytls.Revoke(revoke) if err != nil { mustLogFatalf("%v", err) } fmt.Printf("Revoked certificate for %s\n", revoke) os.Exit(0) } if version { if module.Sum != "" { // a build with a known version will also have a checksum fmt.Printf("Caddy %s (%s)\n", module.Version, module.Sum) } else { fmt.Println(module.Version) } os.Exit(0) } if plugins { fmt.Println(caddy.DescribePlugins()) os.Exit(0) } // Check if we just need to do a Caddyfile Convert and exit checkJSONCaddyfile() // Set CPU cap err := setCPU(cpu) if err != nil { mustLogFatalf("%v", err) } // Executes Startup events caddy.EmitEvent(caddy.StartupEvent, nil) // Get Caddyfile input caddyfileinput, err := caddy.LoadCaddyfile(serverType) if err != nil { mustLogFatalf("%v", err) } if validate { err := caddy.ValidateAndExecuteDirectives(caddyfileinput, nil, true) if err != nil { mustLogFatalf("%v", err) } msg := "Caddyfile is valid" fmt.Println(msg) log.Printf("[INFO] %s", msg) os.Exit(0) } // Log Caddy version before start log.Printf("[INFO] Caddy version: %s", module.Version) // Start your engines instance, err := caddy.Start(caddyfileinput) if err != nil { mustLogFatalf("%v", err) } // Begin telemetry (these are no-ops if telemetry disabled) telemetry.Set("caddy_version", module.Version) telemetry.Set("num_listeners", len(instance.Servers())) telemetry.Set("server_type", serverType) telemetry.Set("os", runtime.GOOS) telemetry.Set("arch", runtime.GOARCH) telemetry.Set("cpu", struct { BrandName string `json:"brand_name,omitempty"` NumLogical int `json:"num_logical,omitempty"` AESNI bool `json:"aes_ni,omitempty"` }{ BrandName: cpuid.CPU.BrandName, NumLogical: runtime.NumCPU(), AESNI: cpuid.CPU.AesNi(), }) if containerized := detectContainer(); containerized { telemetry.Set("container", containerized) } telemetry.StartEmitting() // Twiddle your thumbs instance.Wait() }
caddy(二)啓動流程
Caddy源碼閱讀(三)Caddyfile 解析 by Loader & Parser
Caddy源碼閱讀(四)Plugin & Controller 安裝插件
Caddy源碼閱讀(五) Instance & Server
caddy-plugins(一)自定義插件
caddy-plugins(二)caddy-grpc 一個插件實例