Go 源碼學習之--net/http
其實自己不是很會看源碼,但是學習優秀的源碼是提升自己代碼能力的一種方式,也可以對自己以后寫代碼有一個很好的影響,所以決定在之后的時間內,要有一個很好的習慣,閱讀優秀的源碼。剛開始自己會覺得看源碼很痛苦,這個和我自己的方法有關系,剛開始自己總是想要知道源碼的每一步操作,以及每個部分都是做什么,導致看着看着就看不下去了,所以也是從這次整理開始,調整自己閱讀源碼的方式,先去源碼框架的主要流程,細枝末節后面等對整體框架有個了解,並且很清晰了,再回頭來細致看,所以閱讀過程中如果有不理解的地方自己先進行跳過,先對主體的框架進行一個很好的學習。
對於golang,實現一個最簡單的http server 非常簡單,代碼如下:
package main
import (
"net/http"
"fmt"
)
func Indexhandler(w http.ResponseWriter,r *http.Request) {
fmt.Fprintln(w,"hello world")
}
func main() {
http.HandleFunc("/",Indexhandler)
http.ListenAndServe("127.0.0.1",nil)
}
通過上面這個簡單的例子,來一點一點學習go的net/http實現的http服務的原理
HTTP
理解HTTP相關的網絡應用,主要關注兩個地方-客戶端(client)和服務端(server)
兩者的交互主要是client的request以及server的response,主要就在於如何接受client的request並向client返回response
接收request的過程中,最重要的莫過於路由(router),即實現一個Multiplexer器。Go中既可以使用內置的mutilplexer --- DefautServeMux,也可以自定義。Multiplexer路由的目的就是為了找到處理器函數(handler),后者將對request進行處理,同時構建response
流程為:
Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet
理解go中的http服務,最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基於ServeMux結構,同時也實現了Handler接口。下面對幾個重要概念說明:
- hander函數: 具有func(w http.ResponseWriter, r *http.Requests)簽名的函數
- handler處理器(函數): 經過HandlerFunc結構包裝的handler函數,它實現了ServeHTTP接口方法的函數。調用handler處理器的ServeHTTP方法時,即調用handler函數本身。
- handler對象:實現了Handler接口ServeHTTP方法的結構。

Golang 的htttp處理流程,如下圖

Handler
Golang沒有繼承,類多態的方式可以通過接口實現。所謂接口則是定義聲明了函數簽名,任何結構只要實現了與接口函數簽名相同的方法,就等同於實現了接口。go的http服務都是基於handler進行處理。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
任何結構體,只要實現了ServeHTTP方法,這個結構就可以稱之為handler對象。ServeMux會使用handler並調用其ServeHTTP方法處理請求並返回響應。
ServeMux
ServeMux的源碼:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}
ServeMux結構中最重要的字段為m,這是一個map,key是一些url模式,value是一個muxEntry結構,后者里定義存儲了具體的url模式和handler。
當然,所謂的ServeMux也實現了ServeHTTP接口,也算是一個handler,不過ServeMux的ServeHTTP方法不是用來處理request和respone,而是用來找到路由注冊的handler
Server
除了ServeMux和Handler,還有一個結構Server需要了解。從http.ListenAndServe的源碼可以看出,它創建了一個server對象,並調用server對象的ListenAndServe方法:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
查看server的結構如下:
type Server struct {
Addr string
Handler Handler
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSConfig *tls.Config
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 nextProtoOnce sync.Once
nextProtoErr error
}
server結構存儲了服務器處理請求常見的字段。其中Handler字段也保留Handler接口。如果Server接口沒有提供Handler結構對象,那么會使用DefautServeMux做multiplexer,后面再做分析。
創建HTTP服務
創建一個http服務,大致需要經歷兩個過程,首先需要注冊路由,即提供url模式和handler函數的映射,其次就是實例化一個server對象,並開啟對客戶端的監聽。
再看gohttp服務的代碼
http.HandleFunc("/", indexHandler)
即是注冊路由。
http.ListenAndServe("127.0.0.1:8000", nil)
或者:
server := &Server{Addr: addr, Handler: handler}
server.ListenAndServe()
注冊路由
net/http包暴露的注冊路由的api很簡單,http.HandleFunc選取了DefaultServeMux作為multiplexer:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
DefaultServeMux是ServeMux的一個實例。當然http包也提供了NewServeMux方法創建一個ServeMux實例,默認則創建一個DefaultServeMux:
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
DefaultServeMux的HandleFunc(pattern, handler)方法實際是定義在ServeMux下的:
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
HandlerFunc是一個函數類型。同時實現了Handler接口的ServeHTTP方法。使用HandlerFunc類型包裝一下路由定義的indexHandler函數,其目的就是為了讓這個函數也實現ServeHTTP方法,即轉變成一個handler處理器(函數)。
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
我們最開始寫的例子中
http.HandleFunc("/",Indexhandler)
這樣 IndexHandler 函數也有了ServeHTTP方法。ServeMux的Handle方法,將會對pattern和handler函數做一個map映射:
// 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 " + pattern)
}
if handler == nil {
panic("http: nil handler")
}
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
// If pattern contains a host name, strip it and use remaining
// path for redirect.
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]
}
url := &url.URL{Path: path}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}
}
Handle函數的主要目的在於把handler和pattern模式綁定到map[string]muxEntry的map上,其中muxEntry保存了更多pattern和handler的信息,還記得前面討論的Server結構嗎?Server的m字段就是map[string]muxEntry這樣一個map。
此時,pattern和handler的路由注冊完成。接下來就是如何開始server的監聽,以接收客戶端的請求。
注冊好路由之后,啟動web服務還需要開啟服務器監聽。http的ListenAndServer方法中可以看到創建了一個Server對象,並調用了Server對象的同名方法:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
// If srv.Addr is blank, ":http" is used.
// ListenAndServe always returns a non-nil error.
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)})
}
Server的ListenAndServe方法中,會初始化監聽地址Addr,同時調用Listen方法設置監聽。最后將監聽的TCP對象傳入Serve方法:
// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// For HTTP/2 support, srv.TLSConfig should be initialized to the
// provided listener's TLS Config before calling Serve. If
// srv.TLSConfig is non-nil and doesn't include the string "h2" in
// Config.NextProtos, HTTP/2 support is not enabled.
//
// Serve always returns a non-nil error. After Shutdown or Close, the
// returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
var tempDelay time.Duration // how long to sleep on accept failure
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
srv.trackListener(l, true)
defer srv.trackListener(l, false)
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)
}
}
監聽開啟之后,一旦客戶端請求到底,go就開啟一個協程處理請求,主要邏輯都在serve方法之中。
serve方法比較長,其主要職能就是,創建一個上下文對象,然后調用Listener的Accept方法用來 獲取連接數據並使用newConn方法創建連接對象。最后使用goroutein協程的方式處理連接請求。因為每一個連接都開起了一個協程,請求的上下文都不同,同時又保證了go的高並發。serve也是一個長長的方法:
// 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())
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 {
if d := c.server.ReadTimeout; d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
}
if d := c.server.WriteTimeout; d != 0 {
c.rwc.SetWriteDeadline(time.Now().Add(d))
}
if err := tlsConn.Handshake(); err != nil {
c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
return
}
c.tlsState = new(tls.ConnectionState)
*c.tlsState = tlsConn.ConnectionState()
if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) {
if fn := c.server.TLSNextProto[proto]; fn != nil {
h := initNPNRequest{tlsConn, serverHandler{c.server}}
fn(c.server, tlsConn, h)
}
return
}
}
// 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)
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 {
const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"
if err == errTooLarge {
// Their HTTP client may or may not be
// able to read this if we're
// responding to them and hanging up
// while they're still writing their
// request. Undefined behavior.
const publicErr = "431 Request Header Fields Too Large"
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
c.closeWriteAndWait()
return
}
if isCommonNetReadError(err) {
return // don't reply
}
publicErr := "400 Bad Request"
if v, ok := err.(badRequestError); ok {
publicErr = publicErr + ": " + string(v)
}
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
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) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
if w.conn.bufr.Buffered() > 0 {
w.conn.r.closeNotifyFromPipelinedRequest()
}
w.conn.r.startBackgroundRead()
}
// 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)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
if !w.shouldReuseConnection() {
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait()
}
return
}
c.setState(c.rwc, StateIdle)
c.curReq.Store((*response)(nil))
if !w.conn.server.doKeepAlives() {
// We're in shutdown mode. We might've replied
// to the user without "Connection: close" and
// they might think they can send another
// request, but such is life with HTTP/1.1.
return
}
if d := c.server.idleTimeout(); d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
if _, err := c.bufr.Peek(4); err != nil {
return
}
}
c.rwc.SetReadDeadline(time.Time{})
}
}
使用defer定義了函數退出時,連接關閉相關的處理。然后就是讀取連接的網絡數據,並處理讀取完畢時候的狀態。接下來就是調用serverHandler{c.server}.ServeHTTP(w, w.req)方法處理請求了。最后就是請求處理完畢的邏輯。serverHandler是一個重要的結構,它近有一個字段,即Server結構,同時它也實現了Handler接口方法ServeHTTP,並在該接口方法中做了一個重要的事情,初始化multiplexer路由多路復用器。如果server對象沒有指定Handler,則使用默認的DefaultServeMux作為路由Multiplexer。並調用初始化Handler的ServeHTTP方法。
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
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)
}
這里DefaultServeMux的ServeHTTP方法其實也是定義在ServeMux結構中的,相關代碼如下:
// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
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.
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// CONNECT requests are not canonicalized.
if r.Method == "CONNECT" {
return mux.handler(r.Host, r.URL.Path)
}
// All other requests have any port stripped and path cleaned
// before passing to mux.handler.
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
return mux.handler(host, r.URL.Path)
}
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}
mux的ServeHTTP方法通過調用其Handler方法尋找注冊到路由上的handler函數,並調用該函數的ServeHTTP方法,本例則是IndexHandler函數。
mux的Handler方法對URL簡單的處理,然后調用handler方法,后者會創建一個鎖,同時調用match方法返回一個handler和pattern。
在match方法中,mux的m字段是map[string]muxEntry圖,后者存儲了pattern和handler處理器函數,因此通過迭代m尋找出注冊路由的patten模式與實際url匹配的handler函數並返回。
返回的結構一直傳遞到mux的ServeHTTP方法,接下來調用handler函數的ServeHTTP方法,即IndexHandler函數,然后把response寫到http.RequestWirter對象返回給客戶端。
上述函數運行結束即serverHandler{c.server}.ServeHTTP(w, w.req)運行結束。接下來就是對請求處理完畢之后上希望和連接斷開的相關邏輯。
至此,Golang中一個完整的http服務介紹完畢,包括注冊路由,開啟監聽,處理連接,路由處理函數。
多數的web應用基於HTTP協議,客戶端和服務器通過request-response的方式交互。一個server並不可少的兩部分莫過於路由注冊和連接處理。Golang通過一個ServeMux實現了的multiplexer路由多路復用器來管理路由。同時提供一個Handler接口提供ServeHTTP用來實現handler處理其函數,后者可以處理實際request並構造response。
ServeMux和handler處理器函數的連接橋梁就是Handler接口。ServeMux的ServeHTTP方法實現了尋找注冊路由的handler的函數,並調用該handler的ServeHTTP方法。ServeHTTP方法就是真正處理請求和構造響應的地方。

