Golang Http Server源碼閱讀


這篇文章出現的理由是業務上需要創建一個Web Server。創建web是所有語言出現必須實現的功能之一了。在nginx+fastcgi+php廣為使用的今天,這里我們不妨使用Go來進行web服務器的搭建。

前言

使用Go搭建Web服務器的包有很多,大致有下面幾種方法,直接使用net包,使用net.http包,使用第三方包(比如gorilla)。使用net包就需要從tcp層開始封裝,耗費人力物力極大,果斷舍棄。直接使用封裝好的net.http和第三方包才是上策。這里我們就選擇了使用官方提供的net.http包來搭建web服務。另外附帶一句,gorilla的第三方包現在使用還是非常廣的,文檔也是比較全的,有興趣的同學可以考慮使用一下。

 

建議看這篇文章前先看一下net/http文檔 http://golang.org/pkg/net/http/

 

net.http包里面有很多文件,都是和http協議相關的,比如設置cookie,header等。其中最重要的一個文件就是server.go了,這里我們閱讀的就是這個文件。

幾個重要概念

ResponseWriter: 生成Response的接口

Handler: 處理請求和生成返回的接口

ServeMux: 路由,后面會說到ServeMux也是一種Handler

Conn : 網絡連接

 

具體分析

(具體的說明直接以注釋形式放在代碼中)

幾個接口:

Handler

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)  // 具體的邏輯函數
}

實現了handler接口的對象就意味着往server端添加了處理請求的邏輯。

下面是三個接口(ResponseWriter, Flusher, Hijacker):

ResponseWriter, Flusher, Hijacker

// ResponseWriter的作用是被Handler調用來組裝返回的Response的
type ResponseWriter interface {
	// 這個方法返回Response返回的Header供讀寫
	Header() Header

	// 這個方法寫Response的Body
	Write([]byte) (int, error)
	
	// 這個方法根據HTTP State Code來寫Response的Header
	WriteHeader(int)
}

// Flusher的作用是被Handler調用來將寫緩存中的數據推給客戶端
type Flusher interface {
	// 這個方法將寫緩存中數據推送給客戶端
	Flush()
}

// Hijacker的作用是被Handler調用來關閉連接的
type Hijacker interface {
	// 這個方法讓調用者主動管理連接
	Hijack() (net.Conn, *bufio.ReadWriter, error)

}
 

response

實現這三個接口的結構是response(這個結構是http包私有的,在文檔中並沒有顯示,需要去看源碼)

// response包含了所有server端的http返回信息
type response struct {
	conn          *conn         // 保存此次HTTP連接的信息
	req           *Request // 對應請求信息
	chunking      bool     // 是否使用chunk
	wroteHeader   bool     // header是否已經執行過寫操作
	wroteContinue bool     // 100 Continue response was written
	header        Header   // 返回的http的Header
	written       int64    // Body的字節數
	contentLength int64    // Content長度
	status        int      // HTTP狀態
	needSniff     bool     // 是否需要使用sniff。(當沒有設置Content-Type的時候,開啟sniff能根據HTTP body來確定Content-Type)
	
	closeAfterReply bool     //是否保持長鏈接。如果客戶端發送的請求中connection有keep-alive,這個字段就設置為false。

	requestBodyLimitHit bool //是否requestBody太大了(當requestBody太大的時候,response是會返回411狀態的,並把連接關閉) 

}

 

在response中是可以看到

func (w *response) Header() Header
func (w *response) WriteHeader(code int)
func (w *response) Write(data []byte) (n int, err error)
func (w *response) Flush()
func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error)

這么幾個方法。所以說response實現了ResponseWriter,Flusher,Hijacker這三個接口

 

HandlerFunc

handlerFunc是經常使用到的一個type

// 這里將HandlerFunc定義為一個函數類型,因此以后當調用a = HandlerFunc(f)之后, 調用a的ServeHttp實際上就是調用f的對應方法
type HandlerFunc func(ResponseWriter, *Request)

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

 

這里需要多回味一下了,這個HandlerFunc定義和ServeHTTP合起來是說明了什么?說明HandlerFunc的所有實例是實現了ServeHttp方法的。另,實現了ServeHttp方法就是什么?實現了接口Handler!

 

所以你以后會看到很多這樣的句子:

func AdminHandler(w ResponseWriter, r *Request) {
    ...
}
handler := HandlerFunc(AdminHandler)
handler.ServeHttp(w,r)

 

請不要訝異,你明明沒有寫ServeHttp,怎么能調用呢? 實際上調用ServeHttp就是調用AdminHandler。

好吧,理解這個也花了我較長時間,附帶上一個play.google寫的一個小例子

http://play.golang.org/p/nSt_wcjc2u

有興趣繼續研究的同學可以繼續試驗下去

 

如果你理解了HandlerFunc,你對下面兩個句子一定不會訝異了

func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }

func NotFoundHandler() Handler { return HandlerFunc(NotFound) }

 

下面接着看Server.go

ServerMux結構

它就是http包中的路由規則器。你可以在ServerMux中注冊你的路由規則,當有請求到來的時候,根據這些路由規則來判斷將請求分發到哪個處理器(Handler)。

它的結構如下:

type ServeMux struct {
	mu sync.RWMutex   //鎖,由於請求設計到並發處理,因此這里需要一個鎖機制
	m  map[string]muxEntry  // 路由規則,一個string對應一個mux實體,這里的string就是我注冊的路由表達式
}
 

下面看一下muxEntry

type muxEntry struct {
	explicit bool   // 是否精確匹配
	h        Handler // 這個路由表達式對應哪個handler
}
 

看到這兩個結構就應該對請求是如何路由的有思路了:

當一個請求request進來的時候,server會依次根據ServeMux.m中的string(路由表達式)來一個一個匹配,如果找到了可以匹配的muxEntry,就取出muxEntry.h,這是個handler,調用handler中的ServeHTTP(ResponseWriter, *Request)來組裝Response,並返回。

 

ServeMux定義的方法有:

func (mux *ServeMux) match(path string) Handler   //根據path獲取Handler
func (mux *ServeMux) handler(r *Request) Handler  //根據Request獲取Handler,內部實現調用match
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) //!!這個說明,ServeHttp也實現了Handler接口,它實際上也是一個Handler!內部實現調用handler
func (mux *ServeMux) Handle(pattern string, handler Handler) //注冊handler方法

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))  //注冊handler方法(直接使用func注冊)

 

在godoc文檔中經常見到的DefaultServeMux是http默認使用的ServeMux

var DefaultServeMux = NewServeMux()

如果我們沒有自定義ServeMux,系統默認使用這個ServeMux。

 

換句話說,http包外層(非ServeMux)中提供的幾個方法:

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

實際上就是調用ServeMux結構內部對應的方法。

 

Server

下面還剩下一個Server結構

type Server struct {
	Addr           string        // 監聽的地址和端口
	Handler        Handler       // 所有請求需要調用的Handler(實際上這里說是ServeMux更確切)如果為空則設置為DefaultServeMux
	ReadTimeout    time.Duration // 讀的最大Timeout時間
	WriteTimeout   time.Duration // 寫的最大Timeout時間
	MaxHeaderBytes int           // 請求頭的最大長度
	TLSConfig      *tls.Config   // 配置TLS
}

Server提供的方法有:

func (srv *Server) Serve(l net.Listener) error   //對某個端口進行監聽,里面就是調用for進行accept的處理了
func (srv *Server) ListenAndServe() error  //開啟http server服務,內部調用Serve
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error //開啟https server服務,內部調用Serve

 

當然Http包也直接提供了方法供外部使用,實際上內部就是實例化一個Server,然后調用ListenAndServe方法

func ListenAndServe(addr string, handler Handler) error   //開啟Http服務
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error //開啟HTTPs服務

 

具體例子分析

下面根據上面的分析,我們對一個例子我們進行閱讀。這個例子搭建了一個最簡易的Server服務。當調用http://XXXX:12345/hello的時候頁面會返回“hello world”

func HelloServer(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "hello, world!\n")
}

func main() {
	http.HandleFunc("/hello", HelloServer)
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}

}

 

首先調用Http.HandleFunc

按順序做了幾件事:

1 調用了DefaultServerMux的HandleFunc

2 調用了DefaultServerMux的Handle

3 往DefaultServeMux的map[string]muxEntry中增加對應的handler和路由規則

 

其次調用http.ListenAndServe(":12345", 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 判斷header是否為空,如果沒有設置handler(這個例子就沒有設置handler),handler就設置為DefaultServeMux

8 調用handler的ServeHttp

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

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

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

11 選擇handler:

    A 判斷是否有路由能滿足這個request(循環遍歷ServerMux的muxEntry)

    B 如果有路由滿足,調用這個路由handler的ServeHttp

    C 如果沒有路由滿足,調用NotFoundHandler的ServeHttp

后記

對於net.http包中server的理解是非常重要的。理清serverMux, responseWriter, Handler, HandlerFunc等常用結構和函數是使用go web的重要一步。個人感覺由於go中文檔較少,像這樣有點復雜的包,看godoc的效果就遠不如直接看代碼來的快和清晰了。實際上在理解了http包后,才會對godoc中出現的句子有所理解。后續還會寫一些文章關於使用net.http構建web server的。請期待之。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM