原文連結: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)
}
這裡繼續通過呼叫 DefaultServeMux
的 HandleFunc
方法註冊路由,這個 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
DefaultServeMux
是 net/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))
}
ServeMux
的 HandleFunc
方法將我們傳入的路由具體實現函式轉換成 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
介面的方法一一對應的儲存到 ServeMux
的 map[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/