Golang學習筆記 – 標準庫”net/http”的簡析及自制簡單路由框架

Target_Liu發表於2019-02-16

原文連結:http://targetliu.com/golang-http-router/

還是在繼續學習Go的路上,曾經在使用PHP的時候吃過過度依賴框架的虧。現在學習Go的時候決定先打好基礎,從標準庫學起走。

原始碼分析

我們知道最簡單的建立http伺服器程式碼基本上都是這樣的:

http.HandleFunc(`/`, func(w http.ResponseWriter, r *http.Request){
    fmt.Fprint(w, "Hello world")
})
http.ListenAndServe(":8080", nil)

這樣就成功的建立了一個監聽 8080 埠的http伺服器,當訪問的時候輸出 Hello world

我們順藤摸瓜來看看 HandleFunc 做了些什麼事:

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

這裡繼續通過呼叫 DefaultServeMuxHandleFunc 方法註冊路由,這個 DefaultServeMux 又是何方聖神:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool // whether any patterns contain hostnames
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

DefaultServeMuxnet/http 包提供的一個預設的 ServeMux 型別,ServeMux 實現了 Handler 介面。

追根究底,發現http伺服器收到一條請求後通過 go c.serve(ctx) 開啟goroutine 處理這個請求,在這個過程中呼叫了 Handler 介面函式 ServeHTTP 來做進一步的處理(比如匹配方法、連結等等)。

所以,我們就可以理解 ServeMux 就是 net/http 一個內建的路由功能。

繼續回到 HandleFunc 來:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

ServeMuxHandleFunc 方法將我們傳入的路由具體實現函式轉換成 HandlerFunc 型別並通過 Handle 註冊到路由。這個 HandlerFunc 型別也實現了 Handler 介面:

type HandlerFunc func(ResponseWriter, *Request)

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

最後到了 Handle 這個方法, Handle 方法通過將 pattern 路徑以及實現了 Handler 介面的方法一一對應的儲存到 ServeMuxmap[string]muxEntry 中,方便後續請求的時候呼叫。因此,也可以通過 Handle 直接傳入一個實現了 Handler 介面的方法註冊路由。

至此,net/http 包中預設路由的註冊過程基本上已經走完。

至於請求的時候路由呼叫,記住通過 ServeHTTP 查詢 map 中對應路徑並呼叫相關方法就行了。

自制路由

通過以上的分析,我們可以依樣畫葫蘆,實現自己的路由功能。

package route

import (
    "net/http"
    "strings"
)

//返回一個Router例項
func NewRouter() *Router {
    return new(Router)
}

//路由結構體,包含一個記錄方法、路徑的map
type Router struct {
    Route map[string]map[string]http.HandlerFunc
}

//實現Handler介面,匹配方法以及路徑
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    if h, ok := r.Route[req.Method][req.URL.String()]; ok {
        h(w, req)
    }
}

//根據方法、路徑將方法註冊到路由
func (r *Router) HandleFunc(method, path string, f http.HandlerFunc) {
    method = strings.ToUpper(method)
    if r.Route == nil {
        r.Route = make(map[string]map[string]http.HandlerFunc)
    }
    if r.Route[method] == nil {
        r.Route[method] = make(map[string]http.HandlerFunc)
    }
    r.Route[method][path] = f
}

使用:

r := route.NewRouter()
r.HandleFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello Get!")
})
r.HandleFunc("POST", "/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "hello POST!")
})
http.ListenAndServe(":8080", r)

這個例子只是依樣畫葫蘆的簡單功能實現。

一個完整的路由框架應該包含更復雜的匹配、錯誤檢測等等功能,大家可以試著自己動手試試。

閱讀原始碼和重複造輪子都是學習的方法。

最後,歡迎大家關注我的部落格http://targetliu.com/

相關文章