首先,熟悉http協議的都知道,http協議是基于TCP實現的。
http服務器的工作方式大概就是監聽socket端口,接受連接,獲取到請求,處理請求,返回響應。
所以,對應的會有幾個部分
Request:用戶請求的信息。post、get、url等這些信息
Response: 返回給客戶端的信息
Conn: 用戶每次的連接請求
Handler:處理請求和返回信息的邏輯處理
?
用golang可以很快地建立一個Web服務器
// httpWeb PRoject main.gopackage mainimport ( "fmt" "log" "net/http" "strings")func HandleRequest(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Println(r.Form) fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) fmt.Println(r.Form["bookId"]) for k, v := range r.Form { fmt.Printf("key: %s, value: %s /n", k, strings.Join(v, " ")) } fmt.Fprintf(w, "Respone message: Server get bookId successed....")}func main() { http.HandleFunc("/", HandleRequest) err := http.ListenAndServe(":6666", nil) if err != nil { log.Fatal("ListenError:", err) }}當輸入 localhost:6060/?bookId=2222&bookId=6666 ,就會連接到該服務器,效果如下:

服務接收請求。至于二次請求favicon.ico的問題暫不討論。

這樣子,簡單的一個web服務器就可以運行,處理簡單的用戶請求。
只是這樣子知道怎么搭起一個web服務器并不能滿足對技術的好奇心
想要了解簡單的幾行代碼是如何就建立起一web服務器的?
最好的方法就是直接讀源代碼,讓代碼來告訴我們實現的原理
回到一開始的web服務器,仔細想想,基本上是兩個函數撐起整個Web服務器?。?/p>
居然如此簡潔??!
http.HandleFunc("/", HandleRequest)http.ListenAndServe(":6666", nil)不難理解,大概就是處理請求的函數,和監聽端口。
首先,先分析一下http.HandleFunc()這個函數。
直接進入函數HandleFunc的聲明,源碼如下
這里DefaultServeMux調用了HandleFunc()
參數就是傳進來的“/”和HandleFunc(定義一個函數類型,就可以把函數作為參數傳入)
對于DefaultServeMux在源代碼中的聲明如下
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames}type muxEntry struct { explicit bool h Handler pattern string}// NewServeMux allocates and returns a new ServeMux.func NewServeMux() *ServeMux { return &ServeMux{m: make(map[string]muxEntry)} }// DefaultServeMux is the default ServeMux used by Serve.var DefaultServeMux = NewServeMux()可以看到,DefaultServeMux是一個默認的ServerMux,而ServeMux的結構體中,mu sync.RWMutex是一個并發控制的鎖,一個裝有muxEntry結構的map,一個hosts判斷是否包含主機名。
?。?!仔細一看,這個muxEntry結構體,其中一個變量h Handler和另一個變量pattern string。
而這個Handler定義如下
type Handler interface { ServeHTTP(ResponseWriter, *Request)}是一個接口!!方法是ServeHTTP(ResponseWriter, *Request)。
總結一下
DefaultServeMux的代碼看完,大概清楚了。DefaultServeMux是一個ServerMux結構,這個結構里面最最主要包含一個map[string]muxEntry,map里面的每個muxEntry又包含一個string類型的變量pattern和一個接口Handler,這個接口里面的方法是ServeHTTP(ResponseWriter, *Request)。
好了,了解完DefaultServeMux,繼續進入它調用的函數。
// HandleFunc registers the handler function for the given pattern.func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler))}這里相當于調用了ServeMux的方法Handle()。(涉及到golang中的面向對象的方法)
這里先分析HandlerFunc(handler),也就是把func(ResponseWriter, *Request)函數類型轉換為HandlerFunc類型(注意!是HandlerFunc,不是HandleFunc)
先來看看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 object 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這里居然定義了一個func(ResponseWriter, *Request)的函數類型,HandlerFunc(handler)實際上就是handler本身。為什么這么做?
接看下面的函數。
HandlerFunc實現了ServeHTTP(w ResponseWriter, r *Request) 這個方法?。。?!里面只有一行代碼f(w, r),也就是說實際上是調用handler,也就是我們一開始在http.HandleFunc("/", HandleRequest)中自己定義的函數HandleRequest。
關鍵來了?。。。?/p>
ServeHTTP(w ResponseWriter, r *Request)這個方法不就是上面Handler接口里面的方法嗎!
HandlerFunc實現Handler接口里面的方法。跟java里面的接口一樣,任何實現接口的類型,都可以向上轉換為該接口類型。這就意味著HandlerFunc類型的值可以自動轉換為Handler類型的值作為參數傳進任何函數中。這很重要!回頭看看muxEntry結構體里面的兩個變量pattern string和h Handler,不正好對應剛才傳入的參數pattern, HandlerFunc(handler)嗎??!
總結一下
所以,HandlerFunc(handler)就是一個適配器模式!HandlerFunc實現Handler接口,ServeHTTP方法里面調用的實際上是我們一開始定義的函數HandleRequest。
這樣的一個好處就是,func(ResponseWriter, *Request) -> HandlerFunc -> Handler ,那定義的函數HandleRequest可以作為Handler類型的一個參數。調用Handler的ServeHTTP方法,也就是調用定義的函數HandleRequest。
理解完HandlerFunc(handler),再來看看整句mux.Handle(pattern, HandlerFunc(handler))
Handle函數源碼如下:
// 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) } 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} }}這里的代碼直接關注到第17行和37行
由于37行的代碼是經過判斷后進入if的語句,所以別扣細節,直接關注第17行。
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}都是賦值語句,基本邏輯都是為DefaultServeMux里面的map賦值。
總結一下
這里就是把傳進來的pattern和handler保存在muxEntry結構中,并且pattern作為key,把muxEntry裝入到DefaultServeMux的Map里面。
至此,第一個關鍵的函數http.HandleFunc("/", HandleRequest)就分析完了,就是把當前的參數"/", HandleRequest保存在http包里面的默認的一個ServeMux結構中的map中,簡單來說就是保存當前路由和自己定義的那個處理函數。雖然理順思路感覺挺簡單,但是不得不贊嘆設計得實在太妙了
那么接下來就是http.ListenAndServe(":6666", nil)
依舊,直接進入ListenAndServe函數
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe()}函數小而精巧,把addr放到一個Server結構中,并且調用ListenAndServer()。這里面向對象的方法,相當于Java中new一個對象的實例,并且調用該實例的方法。
繼續進入函數
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)})}可以看出HTTP協議也是基于TCP實現的。
監聽端口,使用tcp協議,并把tcp的監聽傳入到Serve()函數中
Server()源碼如下:
/ 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.func (srv *Server) Serve(l net.Listener) error { defer l.Close() var tempDelay time.Duration // how long to sleep on accept failure for { rw, e := l.Accept() if e != nil { 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, err := srv.newConn(rw) if err != nil { continue } c.setState(c.rwc, StateNew) // before Serve can return go c.serve() }}這個函數就體現了golang并發性。
在for循環里面,直接看到第8行、第26行和第31行
for { rw, e := l.Accept() ... c, err := srv.newConn(rw) ... go c.serve() }簡化之后,看上去邏輯就清晰多了
首先,tcp在監聽,然后循環接受請求,建立連接,并且用關鍵字go開啟一個服務并發地處理每一個連接。
關于gorutine,作為一個輕量級的線程——協程,每一個消耗資源很小,能夠有效地處理并發的問題。這里對每一個服務開啟一個gorutine來處理,這里就是高并發的體現!
那么繼續往下,到底是怎樣處理每個的連接?
這個server()代碼很長,源碼如下:
// Serve a new connection.func (c *conn) serve() { origConn := c.rwc // copy it before it's set nil on Close or Hijack defer func() { if err := recover(); err != nil { 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(origConn, 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 } } for { w, err := c.readRequest() if c.lr.N != c.server.initialLimitedReaderSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } if err != nil { 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. io.WriteString(c.rwc, "HTTP/1.1 413 Request Entity Too Large/r/n/r/n") c.closeWriteAndWait() break } else if err == io.EOF { break // Don't reply } else if neterr, ok := err.(net.Error); ok && neterr.Timeout() { break // Don't reply } io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request/r/n/r/n") break } // 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} } req.Header.Del("Expect") } else if req.Header.get("Expect") != "" { w.sendExpectationFailed() break } // 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. serverHandler{c.server}.ServeHTTP(w, w.req) if c.hijacked() { return } w.finishRequest() if !w.shouldReuseConnection() { if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } break } c.setState(c.rwc, StateIdle) }}實際上,大概思路也是只要關注for循環里面的第40行、第82行和第86行
for{ w, err := c.readRequest() ... serverHandler{c.server}.ServeHTTP(w, w.req) ... w.finishRequest()}這樣就清晰很多了。
循環地讀取請求,并且開始處理請求的服務,最后請求完。
先直接進入ServeHTTP
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)}因為一開始http.ListenAndServe(":6666", nil)第二個參數Handler設置為nil,而sh.srv.Handler就是第二個參數的值。
所以handler := sh.srv.Handler這里獲取的handler為nil,接著進入if,執行handler = DefaultServeMux。
這里居然把DefaultServeMux賦值給handler,那么DefaultServeMux也實現了Handler的接口。
在源碼中找到DefaultServeMux實現的ServeHTTP函數
// 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)}同樣,跳過這里的if判斷部分,假設程序正常執行。
所以直接關注最后兩句
h, _ := mux.Handler(r)h.ServeHTTP(w, r)第一句,直接找到ServeMux結構體擁有的Handler方法。
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { if r.Method != "CONNECT" { if p := cleanPath(r.URL.Path); p != r.URL.Path { _, pattern = mux.handler(r.Host, p) url := *r.URL url.Path = p return RedirectHandler(url.String(), StatusMovedPermanently), pattern } } return mux.handler(r.Host, r.URL.Path)}繼續跳過這里的if判斷部分,假設程序正常執行,r.Method是”CONNECT”。
直接進入mux.handler(r.Host, r.URL.Path)
看到這里,已經很清楚了,正常執行主要就是執行mux.match()這個函數,并且返回Handler類型的值。
func (mux *ServeMux) match(path string) (h Handler, pattern string) { 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}對一個map循環,匹配出key為pattern的值,值為Handler
到這里!已經完全明白了,一層一層函數看回頭。
也就是在 DefaultServeMux中的map中找到個pattern相對應的Handler(其實值就是我們自定義的處理函數)。
第一句h, _ := mux.Handler(r)就是根據請求的路徑來匹配一個路由,并且返回一個Handler值。
接著第二句h.ServeHTTP(w, r)
又是熟悉的ServeHTTP函數。看回第一部分析http.HandleFunc("/", HandleRequest)的時候,此時h值就是那時候保存的HandlerFunc類型,使用了適配器模式,而調用ServeHTTP就是直接調用我們定義的函數HandleRequest。
到這里是不是就恍然大悟了!原來是這樣。監聽,建立連接,獲取請求,通過匹配路由,來返回相應的處理函數。在處理函數里面處理請求的信息,并且返回消息給客戶端。

那么整for循環里面,最后執行w.finishRequest()
這里思路非常簡單,直接關注其中的兩句
w.w.Flush()w.conn.buf.Flush()由于,在自定義的處理函數HandleRequest中,會寫出數據
fmt.Fprintf(w, "Respone message: Server get bookId successed....")
http.ResponseWriter已經寫出數據
這時候http.ResponseWriter再Flush(),把緩沖里的東西也Flush()
最后就可以往客戶端發回信息了。
這時候有突然又有個好奇
HandleRequest(w http.ResponseWriter, r *http.Request)這里函數的參數為什么一個是值傳遞,一個是引用傳遞?
直接看源代碼就知道答案了
查看兩個參數的結構
type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(int)}type Request struct { Method string URL *url.URL ... Header Header Body io.ReadCloser ContentLength int64 ... Host string Form url.Values ...}一個是接口,一個是結構體
對于接口,可以直接傳進來,然后調用其方法
對于結構體,如果直接傳值,把整個結構體傳進來有點消耗太大了,把它的引用傳進來開銷就小了很多。
整個http包實現web部分的源代碼已經分析完了
設計實在非常妙
新聞熱點
疑難解答