Go - http.Server源碼分析

?

本文將從以下幾部分分析net/http模塊中關於server部分的源碼:api

  • Handler類型和HandlerFunc類型是什麼?bash

  • ServeMuxHTTP handler的註冊管理和分發app

  • Server啓動流程tcp

1. Handler

1.1 從註冊HTTP handler入手

原生註冊HTTP handler有以下兩種寫法,它們有什麼區別呢?函數

func handler(w http.ResponseWriter, r *http.Request) {}

http.HandleFunc("/some-pattern", handler)
http.Handle("/some-pattern", http.HandlerFunc(handler))
複製代碼

兩個方法對應的源碼以下:學習

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

func Handle(pattern string, handler Handler) { 
    DefaultServeMux.Handle(pattern, handler) 
}

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}
複製代碼

ServeMux類型是什麼暫時能夠不用理會,會在後文提到。ui

能夠發現,差別體如今HandlerFunc(handler)這一語句,一個在內部調用,一個在外部調用。 而這一語句的做用在於將一個普通的函數轉換成爲Handler類型,最終只有實現了Handler接口的對象能夠註冊到HTTP服務端,爲特定的路徑及其子樹提供服務, 它起到一個適配器的做用。url

源碼以下spa

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}
複製代碼

假設f是一個有着正確簽名的函數, 那麼HandlerFunc(f)就表明一個HTTP handler,除此以外,ServeHTTP方法的調用也表明着請求的處理, 它的調用時機將在後文提到3d

1.2 內置的幾個Handler源碼以及使用

1.2.1 NotFoundHandler

返回一個請求處理器,該處理其對每一個請求都返回404 page not found

http.Handle("/some-pattern", http.NotFoundHandler())
複製代碼

源碼

func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }

func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
複製代碼

1.2.2 RedirectHandler

返回一個請求處理器,該處理器對每一個請求都使用狀態碼code重定向到網址url

http.Handle("/some-pattern", http.RedirectHandler("/", 301))
複製代碼

源碼,有一說一,其中的Redirect函數看起來沒什麼分析價值,跳過

type redirectHandler struct {
	url  string
	code int
}

func RedirectHandler(url string, code int) Handler {
	return &redirectHandler{url, code}
}

func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
	Redirect(w, r, rh.url, rh.code)
}
複製代碼

1.2.3 StripPrefix

在將請求定向到你經過參數指定的請求處理器以前,將特定的prefix從URL中過濾出去

http.Handle("/api/some-pattern", http.StripPrefix("/api", handler))
複製代碼

源碼

func StripPrefix(prefix string, h Handler) Handler {
	if prefix == "" {
		return h
	}
	return HandlerFunc(func(w ResponseWriter, r *Request) {
		if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
			// 淺拷貝request
			r2 := new(Request)
			*r2 = *r
			// r.URL是一個引用,須要再拷貝一次
			r2.URL = new(url.URL)
			*r2.URL = *r.URL
			// 重置請求路徑
			r2.URL.Path = p
			h.ServeHTTP(w, r2)
		} else {
			NotFound(w, r)
		}
	})
}
複製代碼

理解該部分只須要了解兩個點:

  • new(T)爲類型申請一片內存空間,並返回指向這片內存的指針

  • 對指針變量進行取值*操做,能夠得到指針變量指向的原變量的值

1.2.4 TimeoutHandler

返回一個採用指定時間限制的請求處理器,若是某一次調用耗時超過了時間限制,該處理器會回覆請求狀態碼503 Service Unavailable,並將msg做爲回覆的主體。

func handler(w http.ResponseWriter, r *http.Request) {
	time.Sleep(2 * time.Second)
}

http.Handle("/some-pattern", http.TimeoutHandler(http.HandlerFunc(handler), 1 * time.Second, "Timeout"))
複製代碼

定義

type timeoutHandler struct {
	handler Handler
	body    string
	dt      time.Duration
}

func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {
	return &timeoutHandler{
		handler: h,
		body:    msg,
		dt:      dt,
	}
}
複製代碼

當觸發請求處理器時,ServeHTTP方法會執行下面的操做(爲了保證可讀性簡化非關鍵代碼)

func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
    // 初始化一個可被取消的上下文
    ctx, cancelCtx := context.WithTimeout(r.Context(), h.dt)
    defer cancelCtx()
    // 設置r.ctx
    r = r.WithContext(ctx)
    done := make(chan struct{})
    tw := &timeoutWriter{
        w: w,
        h: make(Header),
    }
    // 起一個goroutine來執行本來的邏輯
    go func() {
        h.handler.ServeHTTP(tw, r)
        close(done)
    }()
    // 等待一個通訊
    select {
    // 若是沒有超時正常返回
    case <-done:
        tw.mu.Lock()
        defer tw.mu.Unlock()
        dst := w.Header()
        for k, vv := range tw.h {
            dst[k] = vv
        }
        if !tw.wroteHeader {
            tw.code = StatusOK
        }
        w.WriteHeader(tw.code)
        w.Write(tw.wbuf.Bytes())
    // 若是超時
    case <-ctx.Done():
        tw.mu.Lock()
        defer tw.mu.Unlock()
        w.WriteHeader(StatusServiceUnavailable)
        io.WriteString(w, h.errorBody())
        tw.timedOut = true
    }
}
複製代碼

2. 多路轉接器ServeMux

ServeMux是HTTP請求的多路轉接器,它會將每個接收請求的URL與一個註冊模式的列表進行匹配,並調用最匹配的模式的處理器, 第一部分的註冊HTTP handler的過程其實是在ServeMux內部的一個哈希表中添加一條記錄

2.1 註冊流程

ServeMux結構體以下

type ServeMux struct {
	// 讀寫鎖
	mu    sync.RWMutex
	// 管理全部註冊路由哈希表
	m     map[string]muxEntry
	// 按pattern長度降序排列的匹配列表, 記錄值均以/結尾
	es    []muxEntry
	// 是否存在hosts, 即不以'/'開頭的pattern
	hosts bool
}

type muxEntry struct {
	h       Handler
	pattern string
}

var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux
複製代碼

上文提到了,每次註冊一個HTTP handler最終調用的都是DefaultServeMux.Handle(pattern, handler)方法, 這個方法作的事情很簡單,就是維護內部哈希表m,省略部分錯誤處理代碼後源碼以下:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()
	
	// 若是註冊一個已註冊的處理器,將panic
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}
	// 註冊
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	// 以斜槓結尾的pattern將存入es切片並按pattern長度降序排列
	if pattern[len(pattern)-1] == '/' {
		mux.es = appendSorted(mux.es, e)
	}
	// 不以"/"開頭的模式將視做存在hosts
	if pattern[0] != '/' {
		mux.hosts = true
	}
}
複製代碼

註冊流程結束,因此說http.Handle(pattern, handler)只是單純的在DefaultServeMux的哈希表m中執行註冊, 並無開始分發

2.2 分發

ServeMux結構體也實現了Handler接口,所以它纔是真正的分發者!

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		// 對於http協議小於1.1的處理
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	// 尋找到最接近的HTTP handler
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}
複製代碼

mux.Handler(r)方法始終會返回一個不爲空的HTTP handler,其中包含較多的對特殊狀況的處理,從源碼學習的角度來講,陷入這些分支是不正確的,只應該 考慮最主要的狀況進行分析,所以,Handler方法簡化爲:

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
	// 當請求地址爲/tree,但只註冊了/tree/未註冊/tree時,301重定向
	// 此處redirectToPathSlash並無分析價值,檢測一下二者是否在mux.m哈希表中便可
	if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
		return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
	}
    
	return mux.handler(host, r.URL.Path)
}
複製代碼

最後的handler纔是Handler方法實現的核心,源碼以下:

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// 指定主機的模式優於通常的模式
	if mux.hosts {
		h, pattern = mux.match(host + path)
	}
	if h == nil {
		h, pattern = mux.match(path)
	}
	// 若是沒有匹配到任何Handler,將返回404 handler
	if h == nil {
		h, pattern = NotFoundHandler(), ""
	}
	return
}
複製代碼

假設此時DefaultServeMux註冊了兩個模式: /a/, /a/b/,此時DefaultServeMux的結構爲

{
    m: {
        "/a/": { h: HandlerA, pattern: "/a/" },
        "/a/b/": { h: HandlerB, pattern: "/a/b" },
    },
    es: [{ h: HandlerB, pattern: "/a/b" }, { h: HandlerA, pattern: "/a/" }]
}
複製代碼

當請求路徑爲/a/b/c,將進入第二個if語句,在match方法中進行匹配:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	// 直接匹配成功的狀況
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}
	// 尋找最接近的最長匹配,mux.es切片中包含了全部子樹,並降序排列,所以遍歷一次便可找出最接近的模式
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}
複製代碼

最終路徑/a/b/c將返回handlerB

3. Server

Server的部分結構以下:

type Server struct {
    // 監聽的地址和端口
    Addr string
    // 全部請求都要調用的Handler
    Handler Handler
    // 讀的最大超時時間
    ReadTimeout time.Duration
    // 寫的最大超時時間
    WriteTimeout time.Duration
    // 請求頭的最大長度
    MaxHeaderBytes int
    ...
}
複製代碼

3.1 從啓動HTTP server入手

實例代碼

http.ListenAndServe(":8001", nil)
複製代碼

源碼中其實是建立一個server實例,當handlernil時,將使用DefaultServerMux做爲默認的handler, 也就是第二節中提到的"多路轉接器",這也是最多見的作法

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
複製代碼

server.ListenAndServe方法中,將調用net.Listen("tcp", addr)監聽端口, 不過關於TCP的內容不在分析範圍內,直接進入srv.Serve方法

func (srv *Server) ListenAndServe() error {
	addr := srv.Addr
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
複製代碼

srv.Serve方法中則會在一個for循環中,完成以下的工做:

  • 調用l.Accept()得到一個新的鏈接,進行後續操做

  • TCP conn轉換爲服務端的HTTP conn

  • 啓動一個goroutine來處理這個HTTP conn

func (srv *Server) Serve(l net.Listener) error {
	l = &onceCloseListener{Listener: l}
	defer l.Close()

	// 得到根context
	baseCtx := context.Background()
	// 返回一個在根context的基礎上添加鍵爲ServerContextKey,值爲當前Server引用的context
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		// 接收一個請求
		rw, e := l.Accept()
		// 將tcp conn轉換爲http conn
		c := srv.newConn(rw)
		// 啓動一個goroutine處理這個請求
		go c.serve(ctx)
	}
}
複製代碼

c.serve(ctx)則會進行最後的處理, 此部分比較複雜,其實只須要關心serverHandler{c.server}.ServeHTTP(w, w.req)這一行

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	// HTTP/1.x from here on.
	c.r = &connReader{conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
	
	ctx, cancelCtx := context.WithCancel(ctx)
	defer cancelCtx()

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

		serverHandler{c.server}.ServeHTTP(w, w.req)
		......
	}
}
複製代碼

最終也就是調用DefaultServeMux做爲總體的HTTP handler

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)
}
複製代碼

OVER

3.2 總體流程

相關文章
相關標籤/搜索