net/http - 學習

Introduction

  1. 什麼是函數登記中心 跳轉
  2. 登記中內心的處理函數是什麼 跳轉
  3. 登記中內心的處理工具是什麼 跳轉
  4. 登記中內心的內部結構是什麼樣的 跳轉
  5. 向登記中心登記處理函數 跳轉
  6. 服務中心,生成服務對象 跳轉
  7. 服務中心,生成監聽器 跳轉
  8. 服務中心,循環收集請求 跳轉
  9. 取出服務中心的處理工具→登記中心 跳轉
  10. 登記中心找出匹配的處理函數,處理請求 跳轉

go語言大部分時候,做爲後端出現. 那麼它的最基本行爲就是關於"http請求"的發送與接收. 用久了不免會想知道這裏面究竟是怎麼工做的, 怎麼它就能接收請求了, 怎麼就能發送請求了.後端

下面這個片斷是一個簡單的小服務器, 咱們從下面這個簡單的小函數開始, 描述一下在這其中裏面到底都發生了什麼.服務器

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

func main() {
    http.HandleFunc("/", handleRequest)
    _ = http.ListenAndServe(":8099", nil)
}
複製代碼

函數登記中心是什麼 - ServerMux

  • Server → 服務器
  • Mux → multi-plexer,多路選擇器

兩者組合在一塊兒,表示一個服務器,這個服務器具備多路選擇的功能, 能根據不一樣的狀況作出不一樣的處理 → http請求中連接的路徑不一樣作出不一樣的反應.併發

我是這樣理解的, 這個選擇器是一個登記中心, 執行http.HandleFunc("<path>", func(ResponseWriter, *Request)) 的時候其實就是往這個登記中內心註冊一下這個函數, 稍後服務中心開始工做的時候會拿着登記冊裏的註冊信息, 根據路徑找處處理函數,來作出反應.app

登記中內心的處理函數是什麼 - HandlerFunc

type HandlerFunc func(ResponseWriter, *Request) 複製代碼

咱們都知道,經過調用第一行的函數,咱們能夠將一個函數註冊進去, 咱們要求這個函數能夠接收請求並寫一個返回值, 作一個能作響應的函數. 只有這樣的函數才能往登記中內心註冊tcp

登記中內心的處理工具是什麼 - Handler

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

處理工具的本質是一個用於處理HTTP請求的工具, 在後面, 咱們會給出咱們在什麼時候調用處理工具來處理HTTP請求,咱們對於這個工具惟一的指望, 就是但願這個"處理工具"可以實現ServeHTTP函數, 作到函數

  • 解析r *Request請求, 找處處理函數
  • 寫入w Response, 作出反饋

ok, 回到上面工具

  • ServerMux, 登記中心實現了這個函數, 因此登記中心是處理工具
  • HandlerFunc, 實現了這個函數, 因此HandlerFunc是處理工具
    • 而且, 處理函數實現它的方法是直接執行這個函數

事實上, 在服務中心接收到請求的時候, 咱們直接找的"處理工具",而不是"登記中心", 只是恰巧, 在這個例子中. 咱們用的是"登記中心"ui

登記中內心的內部結構

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry 
	hosts bool
}

type muxEntry struct {
	h       Handler
	pattern string
}
複製代碼
  • muxEntry 登記冊中的一個基本單元, h是處理函數, pattern是模式, 也就是對應路徑 → 一個單元裏記錄下一條路徑對應的處理函數
  • map[string]muxEntry 這個就是登記冊, 其中的string就是路徑, 咱們會根據路徑找出單元, 從而

向登記中內心註冊函數過程

// Part-1
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

// Part-2
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

// Part-3
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()
	// 作一些驗證工做
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
}
複製代碼
  • Part-1 : 註冊過程原本應該是針對一個登記中心, 可是這裏沒有描述具體針對哪一個登記中心,所以這時候咱們是向默認登記中心:DefaultServeMux登記, 在後面咱們能看到, 在啓動服務中心的時候咱們會使用這個默認的登記中心
  • Part-2 : 登記中心ServerMux裏這樣要求, 任何試圖成爲muxEntry的函數, 必須得是"處理工具"類型, 咱們的函數是"處理函數"類型, 也就是"處理工具"類型, 所以能成功生成muxEntry單元而註冊
  • Part-3 : 這裏咱們的讀寫鎖就發揮做用了, 爲了防止併發寫入而形成不同的結果, 咱們會加鎖, 作一些驗證工做後, 針對這個路徑以及處理函數生成一個登記單元

服務中心開始工做

生成服務中心對象

在已經有了登記中心之後,咱們會說說服務中心是怎麼開始工做的spa

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

咱們拿着主機+端口信息, Handler=nil不指定處理工具的方式, 生成一個服務中心, 讓這個服務中心開始工做code

生成監聽器

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

負責監聽的是監聽器'ln', 服務中心只有有了監聽器才能收集到請求, 咱們設置好監聽地址,請求類型=tcp → 開始監聽

循環收集請求

func (srv *Server) Serve(l net.Listener) error {
	if fn := testHookServerServe; fn != nil {
		fn(srv, l) // call hook with unwrapped listener
	}

	l = &onceCloseListener{Listener: l}
	defer l.Close()

	if err := srv.setupHTTP2_Serve(); err != nil {
		return err
	}

	if !srv.trackListener(&l, true) {
		return ErrServerClosed
	}
	defer srv.trackListener(&l, false)

	var tempDelay time.Duration     // how long to sleep on accept failure
	baseCtx := context.Background() // base is always background, per Issue 16220
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, e := l.Accept()
		if e != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			if ne, ok := e.(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", e, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return e
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(ctx)
	}
}
複製代碼

收到新的請求時, 開啓一個協程, 生成一個Context上下文用於存儲數據, 這個協程負責去讀取請求以及作出反饋, 這個函數能夠理解爲, 只負責接收請求, 每次接收到請求, 就負責找人(協程)處理, 而它本身則迴歸原位繼續等下一個請求

讀取請求內容,Server對象開始調用處理工具

// 簡化後, 這個函數在作什麼
func (c *conn) serve(ctx context.Context) {
	for {
	
	    // PART-1:讀取請求正文,請求裏包含了什麼信息
		w, err := c.readRequest(ctx)
		
		// PART-2:找人手去處理這個請求
		serverHandler{c.server}.ServeHTTP(w, w.req)
		
		//PART-3: 處理完了,關閉請求,善後
		w.finishRequest()
	}
}
複製代碼

詳細的介紹一下,這裏是什麼一個場景, 首先須要明白,如今咱們還站在服務中心的維度上, 咱們面對的仍是一個鏈接對象, 這個函數的主體發起人仍是 c *conn, 是一個鏈接對象

回顧HTTP協議,在HTTP協議中一個很是重要的概念叫作"鏈接", 有了鏈接再延伸一下就有了諸如長鏈接,鏈接等待一系列HTTP屬性, 幫你們回憶一下, 長鏈接是這麼辦的:

  • 從HTTP1.1開始默認全都走Keep-Alive:
    • 客戶端 - Connection:Keep-Alive → 服務器
    • 客戶端 ← Connection:Keep-Alive - 服務器
  • 服務器設置頭 Keep-Alive: 10 設置超時時間
  • 服務器返回 Connection: close 表達這個長鏈接已經結束

外圍這個大的for循環表明一個長鏈接,循環的讀取發來的請求,每次請求能夠分紅三步:

  • 嘗試看看能不能讀取請求裏的內容
    • 可能會遇到請求過大沒法讀取的錯誤/請求讀取錯誤的問題
    • Expect100: 數據很大時專用的請求頭
  • 讀取出了請求正文, 服務中心將請求轉移至處理工具處理, 前往下一步
  • 處理工具return, 處理完成,開始善後工做,步驟包含
    • finishRequest(),包含:w.reqBody.Close()關閉請求
    • 判斷是否要複用這個TCP連接,若是不復用,處理完成後退出
    • 判斷若是這個請求並非長鏈接, 處理完成後退出
    • 設置當前鏈接對象狀態爲"Idle",可繼續接受下一個請求
    • 在超時時間內, 嘗試讀取請求, 若是讀不到, 則斷定超時,退出

找到服務中心的處理工具,登記中心

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{}
    }
    
    // 這個就是服務中心的處理工具, 它要處理HTTP請求
    handler.ServeHTTP(rw, req)
}
複製代碼

從這裏開始, 咱們已經有了請求裏的正文, 咱們接下來開始找Server對象裏的處理工具, 用於處理請求.

在一開始, 在生成Server對象的時候, 咱們只給了監聽地址, 可是把處理工具設置爲nil,所以在下面的代碼中, 咱們要開始使用默認的登記中心, 做爲咱們的處理工具

拿到了處理工具, 咱們開始對着服務中心的處理工具,處理請求. 咱們調用ServeHTTP方法, 按照ServeHTTP方法的定義, 它必須能接收一個請求,而且能寫一個反饋, 能作響應.

回到登記中心, 找到對應的處理函數

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	
	// 第一件事: 找到對應處理函數
	h, _ := mux.Handler(r)
	// 第二件事: 執行處理函數
	h.ServeHTTP(w, r)
}
複製代碼

如今咱們已經回到登記中心了, 咱們要作的第一件事, 是根據請求內容, 找到對應的處理函數.

在上面咱們說過, 登記中心的任何函數都必須實現ServeHTTP方法, 所以咱們的第二件事就是執行函數ServeHTTP, 也就是執行這個執行函數自己.

登記中心: 解析請求尋找對應處理函數的過程

// 第一步, 解析請求內容
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
  ...
	host := stripHostPort(r.Host)
  ...
	return mux.handler(host, r.URL.Path)
}

// 第二步, 嘗試找到匹配的函數
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)
	}
	if h == nil {
		h, pattern = NotFoundHandler(), ""
	}
	return
}


func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	// 看看這個路徑能不能直接匹配上
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	// 若是找不到直接匹配上, 找出最爲接近的
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

複製代碼
相關文章
相關標籤/搜索