Go - http.Server原始碼分析
?
本文將從如下幾部分分析net/http
模組中關於server
部分的原始碼:
-
Handler
型別和HandlerFunc
型別是什麼? -
ServeMux
對HTTP handler
的註冊管理和分發 -
Server
啟動流程
1. Handler
1.1 從註冊HTTP handler入手
原生註冊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 ,*Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern,HandlerFunc(handler))
}
複製程式碼
ServeMux
型別是什麼暫時可以不用理會,會在後文提到。
可以發現,差異體現在HandlerFunc(handler)
這一語句,一個在內部呼叫,一個在外部呼叫。
而這一語句的作用在於將一個普通的函式轉換成為Handler
型別,最終只有實現了Handler
介面的物件可以註冊到HTTP服務端,為特定的路徑及其子樹提供服務,
它起到一個介面卡的作用。
原始碼如下
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
方法的呼叫也代表著請求的處理,
它的呼叫時機將在後文提到
1.2 內建的幾個Handler原始碼以及使用
1.2.1 NotFoundHandler
返回一個請求處理器,該處理其對每個請求都返回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) }
複製程式碼
1.2.2 RedirectHandler
返回一個請求處理器,該處理器對每個請求都使用狀態碼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)
}
複製程式碼
1.2.3 StripPrefix
在將請求定向到你通過引數指定的請求處理器之前,將特定的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)
為型別申請一片記憶體空間,並返回指向這片記憶體的指標 -
對指標變數進行取值
*
操作,可以獲得指標變數指向的原變數的值
1.2.4 TimeoutHandler
返回一個採用指定時間限制的請求處理器,如果某一次呼叫耗時超過了時間限制,該處理器會回覆請求狀態碼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
}
}
複製程式碼
2. 多路轉接器ServeMux
ServeMux
是HTTP請求的多路轉接器,它會將每一個接收請求的URL與一個註冊模式的列表進行匹配,並呼叫最匹配的模式的處理器,
第一部分的註冊HTTP handler
的過程實際上是在ServeMux
內部的一個雜湊表中新增一條記錄
2.1 註冊流程
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
中執行註冊,並沒有開始分發
2.2 分發
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,{ 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
3. Server
Server
的部分結構如下:
type Server struct {
// 監聽的地址和埠
Addr string
// 所有請求都要呼叫的Handler
Handler Handler
// 讀的最大超時時間
ReadTimeout time.Duration
// 寫的最大超時時間
WriteTimeout time.Duration
// 請求頭的最大長度
MaxHeaderBytes int
...
}
複製程式碼
3.1 從啟動HTTP server入手
例項程式碼
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