深刻了解net/http

http包提供了HTTP客戶端和服務端的實現。golang

最簡單Web服務

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("hello")
	})
	log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

咱們先分析下面這個函數web

  • func ListenAndServe(addr string, handler Handler) error
    • addr 參數很明顯就是監聽的host和端口
    • handler 參數是一個知足Handler 接口的對象

咱們首先來了解下Handler類型 其定義以下編程

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

也就是說凡是實現了 ServeHTTP(ResponseWriter, *Request)方法的類型都是知足Handler接口的,因此咱們能夠用下面的方式來實現一個web服務併發

(上面的說法並不嚴謹,我會在最後講述使用http.HandlerFunc()進行類型轉換來實現知足Handler接口的方法)tcp

import (
	"fmt"
	"log"
	"net/http"
)

type myHandler struct {
}

func (h myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/":
		fmt.Println("hello")
	default:
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w, "not found: %s\n", r.URL)
	}
}
func main() {
	handler := myHandler{}
	log.Fatal(http.ListenAndServe("localhost:8000", handler))
}

可能有人注意到了咱們在最開始寫的,最簡單的web服務中的http.ListenAndServe("localhost:8000", nil)咱們向第二個參數傳遞的是nil,並不知足http.Handler接口,這個問題咱們留到下面去解答函數

http.ServeMux

ServeMux類型是HTTP請求的多路轉接器。它會將每個接收的請求的URL與一個註冊模式的列表進行匹配,並調用和URL最匹配的模式的處理器。.net

上面的例子中myHandler.ServeHTTP()實際上既實現了路由分配又實現了邏輯處理。在大多數狀況下,咱們更但願不一樣的路由交由不一樣的方法來處理,因此http包中引入了ServeMux類型來幫助咱們實現路由分配code

用法

type myHandler struct {
}

func (h myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "hello world")
}
func main() {
	handler := myHandler{}
	mux := http.NewServeMux()
	mux.Handle("/", handler)
	log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

ServeMux的結構

type ServeMux struct {
    mu sync.RWMutex   //鎖,因爲請求涉及到併發處理,所以這裏須要一個鎖機制
    m  map[string]muxEntry  // 路由規則,一個string對應一個mux實體,這裏的string就是註冊的路由表達式
    hosts bool // 是否在任意的規則中帶有host信息
}
type muxEntry struct {
    explicit bool   // 是否精確匹配
    h        Handler // 這個路由表達式對應哪一個handler
    pattern  string  //匹配字符串
}

能夠注意到muxEntry中的h的類型爲實現Handler接口結構的對象對象

路由解析實現思路

經過ServeMux能夠看到實際的路由規則記錄在ServeMux.m 中,這個屬性是一個map[string]muxEntry 類型,string記錄都是路由,muxEntry裏包含實際的handlerblog

經過http.ListenAndServe("localhost:8000", mux)很容易想到ServeMux實現了也實現了Handler接口(擁有ServerHTTP()方法)

因此實現路由解析的原理爲

  • ListenAndServe() 調用ServeMux中的ServerHTTP()
  • ServeMux中的ServerHTTP()方法會根據路由去map[string]muxEntry中找到對應的muxEntry,而後調用muxEntry中的 h.ServerHTTP()

源碼入下

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        w.Header().Set("Connection", "close")
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    if r.Method != "CONNECT" {
        if p := cleanPath(r.URL.Path); p != r.URL.Path {
            _, pattern = mux.handler(r.Host, p)
            return RedirectHandler(p, StatusMovedPermanently), pattern
        }
    }    
    return mux.handler(r.Host, r.URL.Path)
}

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

    // Host-specific pattern takes precedence over generic ones
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    if h == nil {
        h, pattern = mux.match(path)
    }
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    return
}

添加handler

經過上面路由解析的原理能夠了解:據用戶請求的URL和路由器裏面存儲的map去匹配,當匹配到以後返回存儲的handler,調用這個handler的ServeHTTP接口就能夠執行到相應的函數了。

而將URL和handler 添加到map中則須要使用func (mux *ServeMux) Handle(pattern string, handler Handler)

看上面用法也能夠很容易理解 mux.Handle("/", handler) 函數將URL:"/" 添加到了map的鍵,將對應的handler 添加到了muxEntry結構中

http.HandlerFunc()

還記得上面的說法:凡是實現了ServeHTTP(ResponseWriter, *Request) 方法的類型都是知足Handler接口的。但實際開發中咱們會發現大部分handler 並無實現ServeHTTP() 的函數,而是以下的一種方式

type myHandler struct {
}

func (h myHandler) index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "hello world")
}
func (h myHandler) News(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "news one")
}
func main() {
	h := myHandler{}
	mux := http.NewServeMux()
	mux.Handle("/", http.HandlerFunc(h.index))
	mux.Handle("/news", http.HandlerFunc(h.News))
	log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

http.HandlerFunc(h.index)在這裏並非函數調用,而是強制的類型轉換

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

即咱們調用了HandlerFunc(f),強制類型轉換f成爲HandlerFunc類型,這樣f就擁有了ServeHTTP方法。

mux.HandleFunc()

由於像上面http.HandlerFunc()方式是很是廣泛的,因此下面兩個是等價的

mux.HandleFunc("/", h.index)
mux.Handle("/news", http.HandlerFunc(h.News))

http.HandleFunc()

注意和上面的http.HandlerFunc()進行區分,前者是實現了Handler接口的類型,可以使用強制類型轉換http.HandlerFunc(f) 讓f擁有ServeHTTP方法。

後者是爲默認的ServeMux:DefaultServeMux註冊路由和handler

http.ListenAndServe("localhost:8000", nil)中第二參數爲nil時,http.Serve()便使用DefaultServeMux做爲處理的handler

Go代碼的執行流程

經過對http包的分析以後,如今讓咱們來梳理一下整個的代碼執行過程。 首先調用Http.HandleFunc 按順序作了幾件事:

  1. 調用了DefaultServeMux的HandleFunc
  2. 調用了DefaultServeMux的Handle
  3. 往DefaultServeMux的map[string]muxEntry中增長對應的handler和路由規則

其次調用http.ListenAndServe(":9090", nil) 按順序作了幾件事情:

  1. 實例化Server

  2. 調用Server的ListenAndServe()

  3. 調用net.Listen("tcp", addr)監聽端口

  4. 啓動一個for循環,在循環體中Accept請求

  5. 對每一個請求實例化一個Conn,而且開啓一個goroutine爲這個請求進行服務go c.serve()

  6. 讀取每一個請求的內容w, err := c.readRequest()

  7. 判斷handler是否爲空,若是沒有設置handler(這個例子就沒有設置handler),handler就設置爲DefaultServeMux

  8. 調用handler的ServeHttp

  9. 在這個例子中,下面就進入到DefaultServeMux.ServeHttp

  10. 根據request選擇handler,而且進入到這個handler的ServeHTTP

    mux.handler(r).ServeHTTP(w, r)

  11. 選擇handler:

  • 判斷是否有路由能知足這個request(循環遍歷ServerMux的muxEntry)
  • 若是有路由知足,調用這個路由handler的ServeHttp
  • 若是沒有路由知足,調用NotFoundHandler的ServeHttp

參考資料

相關文章
相關標籤/搜索