一文讀懂 Go Http Server 原理
從一個 Demo 入手
俗話說萬事開頭難,但用 Go 實現(xiàn)一個 Http Server 真不難,簡單到什么程度?起一個 Server,并且能響應(yīng)請求,算上包名、導(dǎo)入的依賴,甚至空行,也就只要 15 行代碼:
這么簡單,能與之一戰(zhàn)的恐怕只有 Python 了吧,而且 Go 還能編譯成可執(zhí)行的二進制文件,你說牛啤不牛啤?
Http Server 如何處理連接?
我們從這一行代碼看起
從命名來看,這個方法干了兩件事,監(jiān)聽并且服務(wù),從方法的單一職責(zé)上來說,我覺得不ok,一個方法怎么能干兩件事?但這是大佬寫的代碼,就很合理。
第一個參數(shù)Addr是要監(jiān)聽的地址和端口,第二個參數(shù)Handler一般是nil,它是真正的邏輯處理,但我們通常用第一行代碼那樣來注冊處理器,這代碼一看就感覺是把 path 映射到業(yè)務(wù)邏輯上,我們先大概了解,待會再來看它。
如果了解過一點網(wǎng)絡(luò)編程基礎(chǔ),就會知道操作系統(tǒng)提供了bind、listen、accept這樣的系統(tǒng)調(diào)用,我們只要按順序發(fā)起調(diào)用,就能組合出一個 Server。
Go 也是利用這些系統(tǒng)調(diào)用,把他們都封裝在了ListenAndServe中。
Listen 往下追究就是系統(tǒng)調(diào)用,所以我們重點看 Serve:
把分支代碼收起來,只看主干,發(fā)現(xiàn)是一個 for 循環(huán)里面在不停地 Accept,而這個 Accept 在沒有連接時是阻塞的,當(dāng)有連接時,起一個新的協(xié)程來處理。
Http Server 如何處理請求?
一些前置工作
處理請求的一行代碼是,可以看出是每個連接單開了一個協(xié)程處理:
這里的 connCtx 代入了當(dāng)前的 Server 對象:
而且還提供了修改它的 hook 方法 srv.ConnContext,可以在每次 Accept 時修改原始的 context
它的定義是:
但是如果按照我開頭給的代碼,你是沒法修改 srv.ConnContext 的,可以改成這樣來自定義:
同樣的 c.setState 也提供了 hook,可采取如上的方法設(shè)置,在每次連接狀態(tài)改變時執(zhí)行 hook 方法:
開始真正干活
為了能看清楚 Accept 后,serve 方法到底干了什么,我們再簡化一下:
serve 也是一個大循環(huán),循環(huán)里面主要是讀取一個請求,然后將請求交給 Handler 處理。
為什么是一個大循環(huán)呢?因為每個 serve 處理的是一個連接,一個連接可以有多次請求。
讀請求就顯得比較枯燥乏味,按照Http協(xié)議,讀出URL,header,body等信息。
這里有個細節(jié)是在每次讀取了一個請求后,還開了一個協(xié)程去讀下一個請求,也算是做了優(yōu)化吧。
請求如何路由?
當(dāng)讀取到一個請求后,便進入這一行代碼:
ServeHTTP 找到我們注冊的 Handler 去處理,如果請求的URI 是 *或請求 Method 是 OPTIONS,則使用globalOptionsHandler,也就是說這類請求不需要我們手動處理,直接就返回了。
對于我們注冊的 Handler 也需要去尋找路由,這個路由的規(guī)則還是比較簡單,主要由如下三條:
- 如果注冊了帶 host 的路由,則按 host + path 去尋找,如果沒注冊帶 host 的路由,則按 path 尋找
- 路由規(guī)則匹配以完全匹配優(yōu)先,如果注冊的路由規(guī)則最后一個字符是/,則除了完全匹配外,還會以前綴查找
舉幾個例子來理解一下:
- 帶 host 的匹配規(guī)則
注冊路由為
此時如果執(zhí)行
則會匹配到 hello2,但如果執(zhí)行
就匹配的是 hello
- 前綴匹配
如果注冊路由為
注意第二個最后還有個/,此時如果執(zhí)行
也能匹配到 hello2,怎么樣,是不是理解了?
找到路由之后就直接調(diào)用我們開頭注冊的方法,如果我們往 Response 中寫入數(shù)據(jù),就能返回給客戶端,這樣一個請求就處理完成了。
總結(jié)
最后我們回憶下 Go Http Server 的要點:
- 用 Go 起一個 Http Server 非常簡單
- Go Http Server 本質(zhì)是一個大循環(huán),每當(dāng)有一個新連接時,會起一個新的協(xié)程來處理
- 每個連接的處理也是一個大循環(huán),這個循環(huán)里做了讀取請求、尋找路由、執(zhí)行邏輯三件大事