本文將從以下幾部分分析net/http
模塊中關於server
部分的源碼:api
Handler
類型和HandlerFunc
類型是什麼?bash
ServeMux
對HTTP handler
的註冊管理和分發app
Server
啓動流程tcp
原生註冊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
返回一個請求處理器,該處理其對每一個請求都返回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) }
複製代碼
返回一個請求處理器,該處理器對每一個請求都使用狀態碼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)
}
複製代碼
在將請求定向到你經過參數指定的請求處理器以前,將特定的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)
爲類型申請一片內存空間,並返回指向這片內存的指針
對指針變量進行取值*
操做,能夠得到指針變量指向的原變量的值
返回一個採用指定時間限制的請求處理器,若是某一次調用耗時超過了時間限制,該處理器會回覆請求狀態碼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
}
}
複製代碼
ServeMux
是HTTP請求的多路轉接器,它會將每個接收請求的URL與一個註冊模式的列表進行匹配,並調用最匹配的模式的處理器, 第一部分的註冊HTTP handler
的過程其實是在ServeMux
內部的一個哈希表中添加一條記錄
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
中執行註冊, 並無開始分發
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
Server
的部分結構以下:
type Server struct {
// 監聽的地址和端口
Addr string
// 全部請求都要調用的Handler
Handler Handler
// 讀的最大超時時間
ReadTimeout time.Duration
// 寫的最大超時時間
WriteTimeout time.Duration
// 請求頭的最大長度
MaxHeaderBytes int
...
}
複製代碼
實例代碼
http.ListenAndServe(":8001", nil)
複製代碼
源碼中其實是建立一個server實例,當handler
爲nil
時,將使用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