Golang Http Server源碼閱讀

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

這篇文章出現的理由是業務上需要建立一個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
1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 具體的邏輯函數
}
實現了handler介面的對象就意味著往server端添加了處理請求的邏輯。

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

ResponseWriter, Flusher, Hijacker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ResponseWriter的作用是被Handler調用來組裝返回的Response的
type ResponseWriter interface {
// 這個方法返回Response返回的Header供讀寫
Header() Header

// 這個方法寫Response的BodyWrite([]byte) (int, error)// 這個方法根據HTTP State Code來寫Response的HeaderWriteHeader(int)

}

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

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

}
1

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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中是可以看到

1
2
3
4
5
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

1
2
3
4
5
6
7
// 這裡將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!

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

1
2
3
4
5
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,你對下面兩個句子一定不會訝異了

1
2
3
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)。

它的結構如下:

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

下面看一下muxEntry

1
2
3
4
type muxEntry struct {
explicit bool // 是否精確匹配
h Handler // 這個路由運算式對應哪個handler
}
1

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

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

ServeMux定義的方法有:

1
2
3
4
5
6
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)中提供的幾個方法:

1
2
3
4
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結構

1
2
3
4
5
6
7
8
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提供的方法有:

1
2
3
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方法

1
2
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”

1
2
3
4
5
6
7
8
9
10
11
12
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的ServeHttpC 如果沒有路由滿足,調用NotFoundHandler的ServeHttp

後記

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

轉帖:http://www.cnblogs.com/yjf512/archive/2012/08/22/2650873.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.