golang的net/http包提供了啟動一個http服務(wù)簡單的方法,我們直接從調(diào)用流程追蹤代碼查看http包是怎樣提供http服務(wù)的,下面是一個簡單的deamo:
package main
import (
"fmt"
"log"
"net/http"
)
func loginHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "login success")
}
func startServer(addr string) {
http.HandleFunc("/login", loginHandler)
log.Fatal(http.ListenAndServe(addr, nil))
}
func main() {
startServer(":10080")
}
因為http是建立在tcp傳輸協(xié)議之上的,所以啟動http服務(wù)器本質(zhì)上是啟動一個tcp服務(wù)器,只是需要對http應(yīng)用層的協(xié)議進行解析;對http request的處理是根據(jù)request路徑來選擇處理方式的,上面代碼也就做了這兩件事:
- 注冊http request的處理函數(shù),這里是注冊了/login的處理函數(shù)loginHandler
- 啟動一個server接收http request,并根據(jù)request path找到注冊的處理函數(shù)
接下來跟代碼,目的是搞清楚http.HandleFunc()注冊的處理函數(shù)保存到哪里了?server接收到http request后又是怎樣找到對應(yīng)的處理函數(shù)的。
一. Handler/HandlerFunc
首先介紹下Handler類型,因為后面分析代碼需要它,看看它的聲明:
1. Handler
// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
只要實現(xiàn)了ServeHTTP(w http.ResponseWriter, r *http.Request)就是Handler類型,ServeHTTP用來對回復(fù)http請求。
3. HandlerFunc
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
HandlerFunc本身是函數(shù)類型,實現(xiàn)了ServeHTTP()方法,同時ServeHTTP()內(nèi)部調(diào)用的又是函數(shù)本身,HandlerFunc其實就是函數(shù)適配器。
二. DefaultServeMux
接下來看看是如何注冊處理函數(shù)的
http.HandleFunc("/login", loginHandler)
調(diào)用的是DefaultServeMux的HandleFunc()函數(shù)
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
對handler進行判空再處理,注意這里把傳入的handler參數(shù)強制轉(zhuǎn)換成HandlerFunc類型,HandlerFunc又實現(xiàn)ServeHTTP()方法,所以本質(zhì)是把一個處理函數(shù)最終轉(zhuǎn)換成Handler類型。
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
從函數(shù)原型可以知道DefaultServeMux是ServeMux類型的實例,我們看下ServeMux的定義:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
知道了DefaultServeMux是ServeMux類型的實例,接下來看看DefaultServeMux是如何保存這些pattern和處理函數(shù)的。
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
原來是把pattern和Handler保存到DefaultServeMux的成員m中了,我們可以猜測匹配過程是通過pattern在m中找到對應(yīng)Handler去處理http request,那es成員有什么用呢?這就要說道ServeMux的pattern匹配規(guī)則了
- 如果pattern不以
/結(jié)尾,如:/index,則只會匹配/index的請求- 如果pattern以
/結(jié)尾,如:/index/,則該pattern能匹配/index/,/index/info,但是優(yōu)先匹配長的路徑- 所以pattern
/能匹配所有不能被其他pattern匹配的路徑- 如果pattern不以
/結(jié)尾,如:/login,如果匹配不到/login,則會去匹配/login/
es保存的就是以/結(jié)尾的pattern,并且按字典從大到小排序,所以匹配時遍歷es就能優(yōu)先遍歷較長的路徑。
到這里注冊處理函數(shù)就結(jié)束了,主要是把處理函數(shù)強制轉(zhuǎn)成Handler類型,并把pattern和Handler保存到DefaultServeMux中,我們可以預(yù)料在某個時刻http server會通過DefaultServeMux找到對用的Handler
三. ListenAndServe
log.Fatal(http.ListenAndServe(addr, nil))
創(chuàng)建一個Server,監(jiān)聽addr中的地址和端口,注意這里的handler參數(shù)傳的是nil,看來是默認(rèn)會使用DefaultServeMux中的Handler,只是要搞清楚它們是怎么關(guān)聯(lián)上的。
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
接下來就是傳統(tǒng)的server建立了,開始監(jiān)聽,接收連接,處理請求
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
Serve函數(shù)比較長,主要是調(diào)用accept()創(chuàng)建連接,并啟動一個goroutine去處理新連接的事件
func (srv *Server) Serve(l net.Listener) error {
......
......
......
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(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", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
....
....
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
這里accpet處理挺有意思,accept失敗的時,如果不是致命錯誤,則依次休眠5ms,10ms,20ms....1s..1s..1s再進行accept()操作,想到的一個場景是文件句柄打開太多了導(dǎo)致accept()創(chuàng)建fd失敗,sleep一段時間后系統(tǒng)回收一定的文件句柄,再次調(diào)用accpet()就能成功。
接下來看看goroutine是怎么處理http請求的,還是去除不相關(guān)代碼
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
....
....
// 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 {
w, err := c.readRequest(ctx)
....
....
....
c.curReq.Store(w)
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
serverHandler{c.server}.ServeHTTP(w, w.req)
....
....
....
}
}
在for{}循環(huán)中讀取http請求,再把解析出來的數(shù)據(jù)保存到response對象w中,response類型實現(xiàn)了ResponseWriter的方法,最終調(diào)用到serverHandler{c.server}.ServeHTTP(w, w.req),繼續(xù)看看serverHandler是怎么實現(xiàn)ServeHTTP方法的
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)
}
還記得我們啟動server時傳的參數(shù)嗎?http.ListenAndServe(addr, nil) handler參數(shù)傳的就是nil,所以用DefaultServeMux作為handler,因為DefaultServeMux也實現(xiàn)了ServeHTTP方法,所以最終會調(diào)用到DefaultServeMux的ServeHTTP()方法中
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
最終會調(diào)用到DefaultServeMux的match()方法,并返回真正處理http請求的Handler
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
可以看到首先從m中完全匹配pattern,如果匹配不到再到es中去匹配。
實現(xiàn)Handler結(jié)構(gòu)的類型有:

- HandlerFunc類型是個函數(shù)適配器,把處理函數(shù)轉(zhuǎn)成Handler類型
- ServeMux是DefaultServeMux的類型,用來通過path找到對應(yīng)的handler
- serverHandler類型只有一個Server成員,http服務(wù)器就是通過Server中handler成員去處理http請求的,如果handler成員為nil,則通過DefaultServeMux去處理
下面是http.HandleFunc(path, handler)的流程圖:

下面是http.ListenAndServe(addr, nil)的流程圖:

除了可以用默認(rèn)的DefaultServeMux作為http復(fù)用器之外,還能自定義實現(xiàn)一個,只要實現(xiàn)ServeHTTP()方法就行,因為本質(zhì)上是傳入一個Handler實例到http.ListenAndServe(string, Handler)
package main
import (
"fmt"
"net/http"
)
type MyServeMux struct{}
func loginHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "loginHandler\n")
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "indexHandler\n")
}
func defaultHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "defaultHandler\n")
}
func (m *MyServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/login":
loginHandler(w, r)
case "/index":
indexHandler(w, r)
default:
defaultHandler(w, r)
}
}
func startServer(addr string) {
http.ListenAndServe(addr, &MyServeMux{})
}
func main() {
startServer(":12345")
}
用curl測試結(jié)果如下:
$$ curl -X GET http://localhost:12345/login
loginHandler
$$:~$ curl -X GET http://localhost:12345/index
indexHandler
$$:~$ curl -X GET http://localhost:12345/
defaultHandler