這個系列會寫三到四篇文章容燕,第一篇是 go sdk 里 net/http/server.go 的閱讀筆記,之后會寫一下如何利用 server.go 的接口自定義一個簡易通用的 HTTP server 框架译蒂。
example
先從一個簡單的例子開始吧:
package main
import (
"net/http"
"fmt"
"log"
)
//開啟web服務(wù)
func test() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil) // 注意這里第二個參數(shù)為 nil
if err != nil {
log.Fatal("ListenAndServer:", err)
}
}
func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Guest!")
}
func main() {
test()
}
運行代碼,此時瀏覽器訪問localhost:9090
就會看到輸出 “Hello Guest!”盛杰,其實訪問localhost:9090/
+任意字符串禽捆,都能得到結(jié)果。這段代碼先用http.HandleFunc
注冊了一個處理函數(shù)洲炊,然后調(diào)用http.ListenAndServe
監(jiān)聽端口感局,當(dāng)有請求到來時,會根據(jù)訪問路徑找到并執(zhí)行對應(yīng)的處理函數(shù)暂衡。
我們通常還能看到另一種寫法:
package main
import (
"net/http"
"fmt"
"log"
)
//開啟web服務(wù)
func test() {
http.Handle("/", &handler{})
err := http.ListenAndServe(":9090", nil) //
if err != nil {
log.Fatal("ListenAndServer:", err)
}
}
func sayHello(w http.ResponseWriter, r *http.Request) {...}
func main() {
test()
}
type handler struct{}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
sayHello(w, r)
}
這段代碼效果一樣询微。區(qū)別就是http.HandleFunc
和http.Handle
需要的第二個參數(shù),前者要一個func (w http.ResponseWriter, r *http.Request)
函數(shù)狂巢,后者要一個實現(xiàn)了該函數(shù)的結(jié)構(gòu)體撑毛。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
可以看到,兩個函數(shù)都會調(diào)用mux.handle
func (mux *ServeMux) Handle(pattern string, handler Handler)
第二個參數(shù)是Handler唧领,是一個接口:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
現(xiàn)在回到上面的HandleFunc
,注意這個:HandlerFunc(handler)
,這里很容易讓人誤以為HandlerFunc是一個函數(shù)并且包裝了傳入的handler藻雌,再返回一個Handler
類型雌续。而實際上這里是類型轉(zhuǎn)換,來看HandlerFunc的定義:
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
雖然HandlerFunc
的類型是一個函數(shù)胯杭,但它是一種類型驯杜,因為是以type
來定義而不是func
,并且實現(xiàn)了ServeHTTP(w ResponseWriter, r *Request)
做个,在這個函數(shù)里鸽心,它又調(diào)用了自身。這個細節(jié)是十分重要的居暖,因為這一步關(guān)乎到當(dāng)路由規(guī)則匹配時顽频,相應(yīng)的響應(yīng)方法是否會被調(diào)用的問題!這里的類型轉(zhuǎn)換用法使一個函數(shù)自身實現(xiàn)了一個接口膝但,就不用每次都要先寫一個本身無用結(jié)構(gòu)體冲九,再用結(jié)構(gòu)體實現(xiàn)接口。請仔細體會這種技巧跟束!
。丑孩。冀宴。有點扯偏了,這里記住 Handler 這個接口是 go 語言 HTTP 服務(wù)最最最重要的接口温学,官方庫和第三方庫都按照這個接口來擴展略贮。
Server
來看一下 Server 這個結(jié)構(gòu)體吧, 這里我只列出了幾個核心的域:
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
TLSConfig *tls.Config // optional TLS config, used by ServeTLS and ListenAndServeTLS
listeners map[net.Listener]struct{}
onShutdown []func()
}
handler
這里主要關(guān)注 Handler,這個 Handler 就是剛剛的那個接口仗岖,可以在創(chuàng)建 Server 時傳入逃延,也可以在調(diào)用 Server.ListenAndServe 時傳入:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
這個 handler 是在建立連接后收到客戶端請求時用到:
func (c *conn) serve(ctx context.Context) { // conn 指當(dāng)前連接
...
for {
w, err := c.readRequest(ctx)
...
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}
type serverHandler struct {
srv *Server
}
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)
}
從 serverHandler 的 ServeHTTP 函數(shù)可以看到,當(dāng) server.handler==nil 時轧拄,使用內(nèi)部全局變量揽祥,也就是前面提到過的 DefaultServeMux。也就是說檩电,我們在收到請求時通過這個 handler 來執(zhí)行自己的邏輯代碼拄丰,所以這個 handler 必須包含路由功能,并且能夠執(zhí)行路由對應(yīng)的處理函數(shù)俐末。同時我們用的第三方 HTTP server 框架(echo料按、beego…)也是通過自定義 handler 來實現(xiàn)功能擴展。這也是 Handler 這個接口是最最重要的接口的原因卓箫。
關(guān)于 DefaultServeMux 和 自定義的 handler载矿,會在之后詳細討論。接下來回到 Server 本身烹卒。
Server.Serve
在主函數(shù)中可以調(diào)用 http.ListenAndServe 或者 http.Serve 來開始 HTTP 服務(wù)闷盔, 原理都一樣:
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
仔細看下 srv.Serve 的實現(xiàn):
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep on accept failure
if err := srv.setupHTTP2_Serve(); err != nil {// 如果設(shè)置了 http2魂挂,就使用 http2 服務(wù),
return err
}
baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, e := l.Accept() // 這里會等待新的連接的建立馁筐,會阻塞在這里涂召。
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
這里要詳細解釋一下的就是 Accept 返回的 error 了。有以下幾種可能:
- Accept 的時候 Server 由于某種原因停止了
- 收到系統(tǒng)信號產(chǎn)生中斷敏沉,當(dāng)然如果 返回的是 EINTR 表示可以重新調(diào)用
- 之前斷掉的連接在短時間被重用了果正,此時該連接處于 TIME_WAIT 狀態(tài),新連接暫時不可用盟迟∏镉荆可參考這里
對于暫時性的錯誤,可以稍等一會兒攒菠,所以會出現(xiàn) sleep迫皱。如果成功拿到 conn,先標(biāo)記連接狀態(tài)辖众,然后創(chuàng)建新 goroutine 開始對連接服務(wù)卓起。
conn.serve
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
c.setState(c.rwc, StateClosed)
}
}()
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
// 處理 https
}
// HTTP/1.x from here on.
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for { // 同一個連接有多個請求,循環(huán)處理
w, err := c.readRequest(ctx) // 讀取請求凹炸,會阻塞
if c.r.remain != c.server.initialReadLimitSize() {
// If we read any bytes off the wire, we're active.
c.setState(c.rwc, StateActive)
}
if err != nil {
// handle error
return
}
// Expect 100 Continue support
req := w.req
if req.expectsContinue() {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
}
} else if req.Header.get("Expect") != "" {
w.sendExpectationFailed()
return
}
c.curReq.Store(w)
if requestBodyRemains(req.Body) { // 支持管線化戏阅,處理當(dāng)前請求時可能還在接收請求
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
if w.conn.bufr.Buffered() > 0 {
w.conn.r.closeNotifyFromPipelinedRequest()
}
w.conn.r.startBackgroundRead()
}
serverHandler{c.server}.ServeHTTP(w, w.req) // 這里就是之前提到的,自定義處理的入口
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest() // 把數(shù)據(jù) flush 到網(wǎng)絡(luò)層啤它,此次請求在應(yīng)用層結(jié)束
if !w.shouldReuseConnection() {
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait() // 發(fā)送 TCP FIN 奕筐,關(guān)閉連接
}
return
}
...
if d := c.server.idleTimeout(); d != 0 { // 設(shè)置空閑超時,超時后關(guān)閉連接
c.rwc.SetReadDeadline(time.Now().Add(d))
if _, err := c.bufr.Peek(4); err != nil {
return
}
}
c.rwc.SetReadDeadline(time.Time{})
}
}
這里代碼比較復(fù)雜变骡,包含了比較完整的 HTTP离赫、HTTPs、HTTP2 協(xié)議的實現(xiàn)塌碌,建議了解了協(xié)議的內(nèi)容再來看具體實現(xiàn)渊胸。代碼協(xié)議的細節(jié)部分代碼就不詳細談了,我們需要理解的是 創(chuàng)建 listener誊爹,從 Accept 拿到連接蹬刷,等待并讀取到 request,用 handler 處理 request 并把結(jié)果或錯誤信息寫到 response 的過程频丘。
需要注意的是办成,我們所討論的是 go 語言官方庫的 HTTP 的實現(xiàn),這里的發(fā)送和接收數(shù)據(jù)都是指的發(fā)給下層傳輸層和從傳輸層接收搂漠,也就是調(diào)用 socket 接口迂卢,一定要分清楚各個層次。