介紹 golang net/http 原始碼

qq_1443036119發表於2020-12-21

介紹 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。

相關文章