成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一文讀懂 Go Http Server 原理

開發 前端
第一個參數Addr是要監聽的地址和端口,第二個參數Handler一般是nil,它是真正的邏輯處理,但我們通常用第一行代碼那樣來注冊處理器,這代碼一看就感覺是把 path 映射到業務邏輯上,我們先大概了解,待會再來看它。

從一個 Demo 入手

俗話說萬事開頭難,但用 Go 實現一個 Http Server 真不難,簡單到什么程度?起一個 Server,并且能響應請求,算上包名、導入的依賴,甚至空行,也就只要 15 行代碼:

package main

import (
"io"
"net/http"
)

func main() {
http.HandleFunc("/hello", hello)
http.ListenAndServe(":81", nil)
}

func hello(response http.ResponseWriter, request *http.Request) {
io.WriteString(response, "hello world")
}

這么簡單,能與之一戰的恐怕只有 Python 了吧,而且 Go 還能編譯成可執行的二進制文件,你說牛啤不牛啤?

Http Server 如何處理連接?

我們從這一行代碼看起

http.ListenAndServe(":81", nil)

從命名來看,這個方法干了兩件事,監聽并且服務,從方法的單一職責上來說,我覺得不ok,一個方法怎么能干兩件事?但這是大佬寫的代碼,就很合理。

第一個參數Addr是要監聽的地址和端口,第二個參數Handler一般是nil,它是真正的邏輯處理,但我們通常用第一行代碼那樣來注冊處理器,這代碼一看就感覺是把 path 映射到業務邏輯上,我們先大概了解,待會再來看它。

http.HandleFunc("/hello", hello)

如果了解過一點網絡編程基礎,就會知道操作系統提供了bind、listen、accept這樣的系統調用,我們只要按順序發起調用,就能組合出一個 Server。

Go 也是利用這些系統調用,把他們都封裝在了ListenAndServe中。

圖片

Listen 往下追究就是系統調用,所以我們重點看 Serve:

圖片

把分支代碼收起來,只看主干,發現是一個 for 循環里面在不停地 Accept,而這個 Accept 在沒有連接時是阻塞的,當有連接時,起一個新的協程來處理。

Http Server 如何處理請求?

一些前置工作

處理請求的一行代碼是,可以看出是每個連接單開了一個協程處理:

go c.serve(connCtx)

這里的 connCtx 代入了當前的 Server 對象:

ctx := context.WithValue(baseCtx, ServerContextKey, srv)
...
connCtx := ctx

而且還提供了修改它的 hook 方法 srv.ConnContext,可以在每次 Accept 時修改原始的 context

if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}

它的定義是:

// ConnContext optionally specifies a function that modifies
// the context used for a new connection c. The provided ctx
// is derived from the base context and has a ServerContextKey
// value.
ConnContext func(ctx context.Context, c net.Conn) context.Context

但是如果按照我開頭給的代碼,你是沒法修改 srv.ConnContext 的,可以改成這樣來自定義:

func main() {
http.HandleFunc("/hello", hello)
server := http.Server{
Addr: ":81",
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, "hello", "roshi")
},
}
server.ListenAndServe()
}

同樣的 c.setState 也提供了 hook,可采取如上的方法設置,在每次連接狀態改變時執行 hook 方法:

c.setState(c.rwc, StateNew, runHooks) // before Serve can return
// ConnState specifies an optional callback function that is
// called when a client connection changes state. See the
// ConnState type and associated constants for details.
ConnState func(net.Conn, ConnState)

開始真正干活

為了能看清楚 Accept 后,serve 方法到底干了什么,我們再簡化一下:

func (c *conn) serve(ctx context.Context) {
...
for {
w, err := c.readRequest(ctx)
...
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}

serve 也是一個大循環,循環里面主要是讀取一個請求,然后將請求交給 Handler 處理。

為什么是一個大循環呢?因為每個 serve 處理的是一個連接,一個連接可以有多次請求。

讀請求就顯得比較枯燥乏味,按照Http協議,讀出URL,header,body等信息。

這里有個細節是在每次讀取了一個請求后,還開了一個協程去讀下一個請求,也算是做了優化吧。

for {
w, err := c.readRequest(ctx)
...

if requestBodyRemains(req.Body) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
w.conn.r.startBackgroundRead() }
...
}

請求如何路由?

當讀取到一個請求后,便進入這一行代碼:

serverHandler{c.server}.ServeHTTP(w, w.req)

ServeHTTP 找到我們注冊的 Handler 去處理,如果請求的URI 是 *或請求 Method 是 OPTIONS,則使用globalOptionsHandler,也就是說這類請求不需要我們手動處理,直接就返回了。

對于我們注冊的 Handler 也需要去尋找路由,這個路由的規則還是比較簡單,主要由如下三條:

  • 如果注冊了帶 host 的路由,則按 host + path 去尋找,如果沒注冊帶 host 的路由,則按 path 尋找
  • 路由規則匹配以完全匹配優先,如果注冊的路由規則最后一個字符是/,則除了完全匹配外,還會以前綴查找

舉幾個例子來理解一下:

  • 帶 host 的匹配規則

注冊路由為

http.HandleFunc("/hello", hello)
http.HandleFunc("127.0.0.1/hello", hello2)

此時如果執行

curl 'http://127.0.0.1:81/hello'

則會匹配到 hello2,但如果執行

curl 'http://localhost:81/hello'

就匹配的是 hello

  • 前綴匹配

如果注冊路由為

http.HandleFunc("/hello", hello)
http.HandleFunc("127.0.0.1/hello/", hello2)

注意第二個最后還有個/,此時如果執行

curl 'http://127.0.0.1:81/hello/roshi'

也能匹配到 hello2,怎么樣,是不是理解了?

找到路由之后就直接調用我們開頭注冊的方法,如果我們往 Response 中寫入數據,就能返回給客戶端,這樣一個請求就處理完成了。

總結

最后我們回憶下 Go Http Server 的要點:

  • 用 Go 起一個 Http Server 非常簡單
  • Go Http Server 本質是一個大循環,每當有一個新連接時,會起一個新的協程來處理
  • 每個連接的處理也是一個大循環,這個循環里做了讀取請求、尋找路由、執行邏輯三件大事


圖片

責任編輯:武曉燕 來源: 捉蟲大師
相關推薦

2017-05-04 20:29:12

HTTP服務器TCP

2020-03-08 21:22:03

HTTP112

2021-12-16 14:45:09

https架構服務端

2022-05-12 10:53:42

keepalivevrrp協議

2023-12-22 19:59:15

2021-08-04 16:06:45

DataOps智領云

2021-10-20 07:18:51

Linux延時隊列

2021-10-15 14:28:30

React 組件渲染

2019-05-14 10:50:11

HTTP協議HttpServlet

2018-09-28 14:06:25

前端緩存后端

2022-09-22 09:00:46

CSS單位

2025-04-03 10:56:47

2022-11-06 21:14:02

數據驅動架構數據

2021-02-26 05:24:35

Java垃圾回收

2025-04-30 09:12:35

2021-04-30 19:53:53

HugePages大內存頁物理

2024-04-10 10:34:34

Cache系統GPU

2023-11-27 17:35:48

ComponentWeb外層

2023-05-20 17:58:31

低代碼軟件

2022-07-05 06:30:54

云網絡網絡云原生
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 人人天天操 | 国产精品久久久久久久久大全 | 免费毛片网站 | 91精品国产综合久久精品图片 | 中文字幕亚洲一区二区三区 | 麻豆av片| 国产精品一区二区福利视频 | 欧美www在线 | 国产精品视频一二三区 | 国产一区二区三区视频免费观看 | 欧美在线视频网 | 日韩精品久久久久 | 一级久久久久久 | 黄色大片免费网站 | 成人在线一区二区 | 久久久噜噜噜久久中文字幕色伊伊 | 在线观看日韩av | 精精久久 | 国产精品无码专区在线观看 | 国产日韩一区 | 久久久成人网 | 精品一区二区三区在线观看国产 | 超碰在线免费 | 久久久综合久久 | 午夜丰满少妇一级毛片 | 最新国产视频 | 欧美精品国产一区二区 | 精品一区在线免费观看 | 精区3d动漫一品二品精区 | 日本成人午夜影院 | 日韩网站在线观看 | 一区二区三区精品 | 国产精品视频一区二区三区不卡 | 欧美日韩亚洲一区 | 国产午夜精品一区二区三区四区 | 伊人免费在线观看 | 国产成人在线视频播放 | 欧美中文在线 | 成人妇女免费播放久久久 | 日韩精品视频在线播放 | 日韩在线看片 |