按:
源碼地址:https://github.com/mholt/caddy
對(duì)Golang不是很熟悉单旁,算是初學(xué)疾渣,並沒(méi)有什麼好的方式進(jìn)行深入的學(xué)習(xí)漾肮,故選此項(xiàng)目研究清焕。
假定我們都是初學(xué)Golang,或者是熟悉其他語(yǔ)言蝠猬,但Golang不瞭解切蟋,那麼我們下面就從0開(kāi)始來(lái)看看,寫(xiě)Golang到底是啥樣榆芦!
分析不當(dāng)柄粹,望指正!
0. 項(xiàng)目介紹
Caddy是一款輕量級(jí)通用性很強(qiáng)的web服務(wù)器匆绣,支持在Windows驻右,Mac,Linux崎淳,BSD堪夭,以及Android多種平臺(tái)。她完全能夠替代其他主流的網(wǎng)絡(luò)服務(wù)器拣凹。
1. 項(xiàng)目結(jié)構(gòu)
- 雲(yún)端構(gòu)建
- appveyor
- 依賴(lài)
- golang.org/x/tools/cmd/vet
main_test.go爲(wèi)測(cè)試文件
go test
完成測(cè)試操作森爽。
簡(jiǎn)單看下main_test.go
文件,
package main
import (
"runtime" // runtime包嚣镜,運(yùn)行時(shí)的系統(tǒng)交互操作
"testing" // testing包爬迟,自動(dòng)化測(cè)試
)
func TestSetCPU(t *testing.T) {
// 測(cè)試設(shè)置併發(fā)數(shù)
currentCPU := runtime.GOMAXPROCS(-1) // 設(shè)置最大併發(fā)數(shù),參數(shù)小於1時(shí)保持當(dāng)前設(shè)置
maxCPU := runtime.NumCPU() // 獲取機(jī)器上的邏輯cpu數(shù)
halfCPU := int(0.5 * float32(maxCPU))
if halfCPU < 1 {
halfCPU = 1
}
/*
* 下面這段簡(jiǎn)化爲(wèi)
*/
for i, test := range []struct {
input string
output int
shouldErr bool
}{
{"1", 1, false},
{"-1", currentCPU, true},
{"0", currentCPU, true},
{"100%", maxCPU, false},
{"50%", halfCPU, false},
{"110%", currentCPU, true},
{"-10%", currentCPU, true},
{"invalid input", currentCPU, true},
{"invalid input%", currentCPU, true},
{"9999", maxCPU, false}, // over available CPU
} {
err := setCPU(test.input)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error, but there wasn't any", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but there was one: %v", i, err)
}
if actual, expected := runtime.GOMAXPROCS(-1), test.output; actual != expected {
t.Errorf("Test %d: GOMAXPROCS was %d but expected %d", i, actual, expected)
}
// teardown
runtime.GOMAXPROCS(currentCPU)
}
}
2. 讀源碼
/main.go
1. 包名及導(dǎo)入相關(guān)包
package main
import (
"errors" // 實(shí)現(xiàn)操作錯(cuò)誤的函數(shù)菊匿,類(lèi)似於Python中的Expection,但用法存在很大差異付呕,後面再介紹
"flag" // 解析命令行輸入?yún)?shù)
"fmt" // 格式化工具
"io/ioutil" // IO操作
"log" // 日誌
"os" // 操作系統(tǒng)
"runtime" // 運(yùn)行時(shí)環(huán)境
"strconv" // 字符串於一些基本類(lèi)型的轉(zhuǎn)換
"strings" // 處理UTF-8編碼的字符串
"time" // 時(shí)間
"github.com/mholt/caddy/caddy" // 主包
"github.com/mholt/caddy/caddy/letsencrypt" // Let's Encrypt TLS
)
簡(jiǎn)單下介紹下幾個(gè)包计福,
-
error
:
實(shí)現(xiàn)操作錯(cuò)誤的函數(shù),用法是先定義一個(gè)結(jié)構(gòu)體徽职,然後給這個(gè)結(jié)構(gòu)體實(shí)現(xiàn)一個(gè)Error()
『方法』棒搜。 -
flag
:
flag.String(N1, N2, N3)
,有反回值返回值返回給字面量活箕,N1是變量參數(shù)名,N2是默認(rèn)參數(shù)可款,N3是提示字符串育韩;
flag.VarString(N1, N2, N3)
,N1爲(wèi)存儲(chǔ)的指針
/*
* 定義變量
*/
var (
conf string
cpu string
logfile string
revoke string
version bool
)
/*
* 定義常量
*/
const (
appName = "Caddy"
appVersion = "0.8"
)
初始化函數(shù)闺鲸,這個(gè)函數(shù)再main()
函數(shù)之前執(zhí)行筋讨,
func init() {
caddy.TrapSignals()
/*
* 命令行參數(shù)的初始化,再main函數(shù)中flag.Parse()就能獲取到輸入的值
*/
flag.BoolVar(&letsencrypt.Agreed, "agree", false, "Agree to Let's Encrypt Subscriber Agreement")
flag.StringVar(&letsencrypt.CAUrl, "ca", "https://acme-v01.api.letsencrypt.org/directory", "Certificate authority ACME server")
.
.
.
}
下面看下caddy
包內(nèi)TrapSignals()
函數(shù)摸恍,這個(gè)函數(shù)再/caddy/sigtrap.go
悉罕,
// TrapSignals create signal handlers for all applicable signals for this
// system. If your Go program uses signals, this is a rather invasive
// function; best to implement them yourself in that case. Signals are not
// required for the caddy package to function properly, but this is a
// convenient way to allow the user to control this package of your program.
func TrapSignals() {
trapSignalsCrossPlatform()
/*
* 這個(gè)函數(shù)不在這個(gè)文件中,但同樣是在`caddy`包內(nèi)立镶,
* 在/caddy/sigtrap_posix.go文件中壁袄,
* 所以可以直接調(diào)用,這點(diǎn)類(lèi)似於Java媚媒,而不同於Python以文件來(lái)區(qū)分包
*/
trapSignalsPosix()
}
// trapSignalsCrossPlatform captures SIGINT, which triggers forceful
// shutdown that executes shutdown callbacks first. A second interrupt
// signal will exit the process immediately.
func trapSignalsCrossPlatform() {
go func() {
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt)
for i := 0; true; i++ {
<-shutdown
if i > 0 {
log.Println("[INFO] SIGINT: Force quit")
os.Exit(1)
}
log.Println("[INFO] SIGINT: Shutting down")
go os.Exit(executeShutdownCallbacks("SIGINT"))
}
}()
}
// executeShutdownCallbacks executes the shutdown callbacks as initiated
// by signame. It logs any errors and returns the recommended exit status.
// This function is idempotent; subsequent invocations always return 0.
func executeShutdownCallbacks(signame string) (exitCode int) {
shutdownCallbacksOnce.Do(func() {
serversMu.Lock()
errs := server.ShutdownCallbacks(servers)
serversMu.Unlock()
if len(errs) > 0 {
for _, err := range errs {
log.Printf("[ERROR] %s shutdown: %v", signame, err)
}
exitCode = 1
}
})
return
}
var shutdownCallbacksOnce sync.Once
下面嘗試分析下嗜逻,這個(gè)函數(shù)或者說(shuō)這些函數(shù)是來(lái)幹什麼的?
初看下註釋?zhuān)蟾诺囊馑际晴哉伲@是一個(gè)信號(hào)槽栈顷,不知這樣理解是否妥當(dāng)。它主要是用來(lái)做程序功能的切換嵌巷,或者說(shuō)響應(yīng)萄凤,簡(jiǎn)單點(diǎn)就是事件的處理或者分發(fā),當(dāng)然在這裏是信號(hào)搪哪。
先看trapSignalsCrossPlatform()
這個(gè)函數(shù)靡努,
跨平臺(tái)信號(hào)捕捉,觸發(fā)強(qiáng)制關(guān)閉回調(diào)的停止信號(hào)噩死,第二次中斷立即退出颤难。
go func(){}()
,Golang的殺手鐧已维,併發(fā)行嗤,關(guān)鍵字go
,來(lái)執(zhí)行這個(gè)匿名函數(shù)垛耳。make()
變量的申明栅屏,只適用於通道
飘千、切片
、字典
這三種引用類(lèi)型(底層就是數(shù)組)栈雳,返回的是值本身护奈,已經(jīng)初始化(非零值);相反地哥纫,new()
用於爲(wèi)值分配內(nèi)存霉旗,返回的是已經(jīng)初始化(非零值)的內(nèi)存指針。