介紹 golang net/http 原始碼
介紹 golang net/http 原始碼
文章目錄
1、原始碼閱讀
- 閱讀原始碼是學習 golang 繞不開的任務,下文筆者將以 net/http 庫 web 工作原理閱讀為例,原理圖如下:
- 它的處理流程總體分為:註冊路由;開始監聽;處理請求;返回響應,流程圖如下
2、閱讀原始碼的注意事項
- 我們從入口函式 ListenAndServe 開始閱讀分析程式碼:
- 關注函式、方法引數中的 介面 和 函式 引數,是介面一定要了解介面的定義。
- 隨時查閱 API 文件,瞭解相關型別的屬性與方法
- 忽視任何錯誤處理、分支處理。儘管其中有許多有趣的東西,也要放棄
- 其中特別注意閉包、匿名函式、匿名型別這些程式設計技巧
- 特別注意介面斷言語法 var.(type)
- 執行緒要注意上下文物件(context)的構建
3、原始碼分析
① 註冊路由
1)DefaultServeMux & ServeMux
- Mux,即 multiplexer 多路選擇器。在多路資料傳送過程,能根據需要將其中一路選出
- 檢視上文預設路由器的定義 DefaultServeMux,DefaultServeMux 是 ServeMux 的一個例項。
- 在 ServeMux 中,m 是一個 map,儲存路由和 handler 的關係,es 是一個切片 slice,將路由按長度從長到短排序儲存。
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
2)HandleFunc & Handle
-
HandlerFunc 是一個函式型別,它實現了 Handler 介面的 ServeHTTP 方法
-
檢視 HandleFunc 原始碼,它呼叫 DefaultServeMux.HandleFunc() 註冊路由
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
- 將傳入的 handle function 和相應的 pattern 匹配
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
- 檢視上文 Handle() 的原始碼,它對傳入的路徑進行解析,路由匹配,然後設定 ServeMux.m 和 ServeMux.es,存放路由的匹配規則
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
- 至此,pattern 和 handler 的路由註冊完成
② 開啟監聽
1)ListenAndServe
- 因為我們是通過 http.ListenAndServe() 繫結地址埠的,所以從它開始分析
- ListenAndServe() 先例項化 Serve,然後呼叫 Server.ListenAndServe()
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
- 在 Server.ListenAndServe(),使用 TCP 協議建立了一個伺服器,監聽埠
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
// 取地址
addr := srv.Addr
if addr == "" {
addr = ":http"
}
// net.Listen 建立 socket 和繫結埠
ln, err := net.Listen("tcp", addr)
// 錯誤處理
if err != nil {
return err
}
// Server 再 Serve 鏈路上的請求
return srv.Serve(ln)
}
③ 處理請求
1)Serve
- 檢視上文 srv.Serve(ln) 的原始碼
- 它的功能為在 for 迴圈裡持續監聽 request,通過 accept 接受 request,併為每個 request 建立一個 goroutine 來處理。各個 request 之間是相互不影響,併發處理的
func (srv *Server) Serve(l net.Listener) error {
……
// 迴圈監聽
for {
rw, err := l.Accept()
// 錯誤處理
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
// 複製服務執行緒
connCtx := ctx
// 為一個新的連線修改上下文
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
// 建立連線,並初始化
c := srv.newConn(rw)
c.setState(c.rwc, StateNew)
// go 協程處理連線
go c.serve(connCtx)
}
}
2)serve
- 檢視上文 c.serve (connCtx) 的原始碼
- 它用於處理一個連線,用 readRequest 讀取資料,解析 request 中的 Header 和 Body。接著通過 serverHandler 處理 request 和 response
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
// 連線斷開後處理
// ……
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
// 設定 server 維持時間 和 tls handshake 失敗處理
// ···
c.tlsState = new(tls.ConnectionState)
*c.tlsState = tlsConn.ConnectionState()
if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
if fn := c.server.TLSNextProto[proto]; fn != nil {
h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
fn(c.server, tlsConn, h)
}
return
}
}
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
w, err := c.readRequest(ctx)
// http request 錯誤處理
// ……
req := w.req
if req.expectsContinue() {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
w.canWriteContinue.setTrue()
}
} else if req.Header.get("Expect") != "" {
w.sendExpectationFailed()
return
}
c.curReq.Store(w)
if requestBodyRemains(req.Body) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
w.conn.r.startBackgroundRead()
}
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
// ……
w.finishRequest()
// ……
c.setState(c.rwc, StateIdle)
c.curReq.Store((*response)(nil))
// 判斷連線存活
if !w.conn.server.doKeepAlives() {
return
}
if d := c.server.idleTimeout(); d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
if _, err := c.bufr.Peek(4); err != nil {
return
}
}
c.rwc.SetReadDeadline(time.Time{})
}
}
④ 返回響應
1)serverHandler & ServeHTTP
- 檢視上文 serverHandler 的原始碼,它用於實現服務和處理的隔離
- 在 ServerHTTP 中,設定 handler,如果 handler 為 nil,預設使用 DefaultServeMux
- Handler 是一個服務處理回撥介面
- 實現 ServeHTTP(ResponseWriter, *Request) 方法的物件都可以與客戶端會話
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
type HandlerFunc func(ResponseWriter, *Request)
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
2)match
- match 函式實現了,根據路由找到對應的 handler。m 表儲存了 pattern 和 handler 處理器函式的 map[string]muxEntry。優先查詢 m 表,如果找不到,則在 es 表中進行匹配,路徑長的優先匹配。
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
4、總結
- 伺服器的工作流程其實就是:建立 ServerSocket,繫結並 listen,accept 連線,建立 go 執行緒服務連線
- Go 通過 ServeMux 實現了路由 multiplexer 來管理路由,並且設計一個 Handler 介面提供 ServeHTTP 實現 handler ,用於處理 request 並 response。
相關文章
- Golang Tools 介紹Golang
- Go For Web:Golang http 包詳解(原始碼剖析)WebGolangHTTP原始碼
- ASP.NET Core - 選項系統之原始碼介紹ASP.NET原始碼
- Golang net/http 效能優化GolangHTTP優化
- HTTP介紹和HTML簡介HTTPHTML
- HTTP header介紹HTTPHeader
- HTTP Client Hints 介紹HTTPclient
- http代理401錯誤程式碼介紹HTTP
- HTTP狀態碼查詢簡單介紹HTTP
- ReentrantLock介紹及原始碼解析ReentrantLock原始碼
- Spring原始碼分析——spring原始碼之obtainFreshBeanFactory()介紹Spring原始碼AIBean
- 常見的四類HTTP狀態碼介紹HTTP
- 轉載golang中net/http包用法GolangHTTP
- net/http包的使用模式和原始碼解析HTTP模式原始碼
- golang http/transport 程式碼分析GolangHTTP
- Dubbo原始碼學習之-SPI介紹原始碼
- Flutter Dio原始碼分析(一)--Dio介紹Flutter原始碼
- mybatis原理,配置介紹及原始碼分析MyBatis原始碼
- RocketMQ--原始碼編譯和介紹MQ原始碼編譯
- 原始碼管理工具Github介紹原始碼Github
- 原始碼管理工具介紹-github原始碼Github
- .NET框架介紹框架
- Spring原始碼分析——spring原始碼核心方法refresh()介紹Spring原始碼
- http代理使用分類介紹HTTP
- Http Module 的詳細介紹HTTP
- http代理型別格式介紹HTTP型別
- HTTPS 和HTTP的介紹HTTP
- TiKV 原始碼解析(五)fail-rs 介紹原始碼AI
- ArrayList相關方法介紹及原始碼分析原始碼
- 04 原始碼編譯安裝與介紹原始碼編譯
- Nacos使用和註冊部分原始碼介紹原始碼
- 哪裡有介紹jive原始碼的文章?原始碼
- github原始碼管理工具——使用介紹Github原始碼
- 主流原始碼管理工具Github介紹原始碼Github
- golang實現常用集合原理介紹Golang
- golang 介紹以及踩坑之四Golang
- [譯] part 20: golang 併發介紹Golang
- golang ssh包使用方法介紹Golang