2017年的第一篇博客,也是第一次寫博客,寫的不好,請各位見諒。
本人之前一直學習java、java web,最近開始學習Go語言,所以也想了解一下Go語言中web的開發方式以及運行機制。
在《Go web編程》一書第三節中簡要的提到了Go語言中http的運行方式,我這里是在這個的基礎上更加詳細的梳理一下。
這里先提一句,本文中展示的源代碼都是在Go安裝目錄下src/net/http/server.go文件中(除了自己寫的實例程序),如果各位還想理解的更詳細,可以自己再去研究一下源代碼。
《Go web編程》3.4節中提到http有兩個核心功能:Conn, ServeMux , 但是我覺得還有一個Handler接口也挺重要的,后邊咱們提到了再說。
先從一個簡單的實例來看一下Go web開發的簡單流程:
package main import ( "fmt" "log" "net/http" ) func sayHello(w http.ResponseWriter, r *http.Request) { fmt.Println("Hello World!") } func main() { http.HandleFunc("/hello", sayHello) //注冊URI路徑與相應的處理函數 er := http.ListenAndServe(":9090", nil) // 監聽9090端口,就跟javaweb中tomcat用的8080差不多一個意思吧 if er != nil { log.Fatal("ListenAndServe: ", er) } }
在瀏覽器運行localhost:9090/hello 就會在命令行或者所用編輯器的輸出窗口 “Hello World!” (這里為了簡便,就沒往網頁里寫入信息)
根據這個簡單的例子,一步一步的分析它是如何運行。
首先是注冊URI與相應的處理函數,這個就跟SpringMVC中的Controller差不多。
http.HandleFunc("/hello", sayHello)
來看一下他的源碼:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
里邊實際是調用了DefaultServeMux的HandlerFunc方法,那么這個DefaultServeMux是啥,HandleFunc又干了啥呢?
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
}
func NewServeMux() *ServeMux { return &ServeMux{m: make(map[string]muxEntry)} }
var DefaultServeMux = NewServeMux()
事實上這個DefaultServeMux就是ServeMux結構的一個實例(好吧,看名字也看的出來),ServeMux是Go中默認的路由表,里邊有個一map類型用於存儲URI與處理方法的對應的鍵值對(String,muxEntry),muxEntry中的Handler類型就是對應的方法。
再來看HandleFunc方法:
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
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}
}
}
HandleFunc中調用了ServeMux的handle方法,這個handle才是真正的注冊處理函數,而且注意到調用handle方法是第二個參數進行了強制類型轉換(紅色加粗標注部分),將一個func(ResponseWriter, *Request)函數轉換成了HanderFunc(ResponseWriter, *Request)函數(注意這里HandlerFunc比一開始調用的HandleFunc多了個r,別弄混了),下面看一下這個函數:
type HandlerFunc func(ResponseWriter, *Request)
這個HandlerFunc和我們之前寫的sayHello函數有相同的參數,所以能強制轉換。 而Handle方法的第二個參數是Handler類型,這就說明HandlerFunc函數也是一個Handler,下邊看一個Handler的定義:
type Handler interface { ServeHTTP(ResponseWriter, *Request) } func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
Handler是定義的是一個接口,里邊只有一個ServeHTTP函數,根據Go里邊的實現接口的規則,只要實現了ServeHTTP函數,都算是實現了Handler方法。HandlerFunc函數實現了ServeHTTP函數,只不過內部還是調用的HandlerFunc函數。通過這個流程我們可以知道,我們一個開始寫的一個普通方法sayHello方法最后被轉換成了一個Handler,當Handler調用ServeHTTP函數時就是調用了我們的sayHello函數。
到這差不多,這個注冊的過程就差不多了,如果想了解的更詳細,需要各位自己去細細的研究代碼了~~
下邊看一下查找相應的Handler是怎樣一個過程:
er := http.ListenAndServe(":9090", nil)
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } 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)}) }
ListenAndServe中生成了一個Server的實例,並最終調用了它的Serve方法。把Serve方法單獨放出來,以免貼的代碼太長,大家看不下去。
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(); err != nil { return err } 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 := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve() } }
這個方法就比較重要了,里邊的有一個for循環,不停的監聽端口來的請求,go c.serve()為每一個來的請求創建一個線程去出去該請求(這里我們也看到了Go處理多線程的方便性),這里的c就是一個conn類型。
func (c *conn) serve() {
c.remoteAddr = c.rwc.RemoteAddr().String()
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(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
}
}
c.r = &connReader{r: c.rwc}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
w, err := c.readRequest()
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 {
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 431 Request Header Fields Too Large\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n431 Request Header Fields Too Large")
c.closeWriteAndWait()
return
}
if err == io.EOF {
return // don't reply
}
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
return // don't reply
}
var publicErr string
if v, ok := err.(badRequestError); ok {
publicErr = ": " + string(v)
}
io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n400 Bad Request"+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
}
// 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()
}
return
}
c.setState(c.rwc, StateIdle)
}
}
這個方法稍微有點長,其他的先不管,上邊紅色加粗標注的代碼就是查找相應Handler的部分,這里用的是一個serverHandler,並調用了它的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) }
從上邊的代碼可以看出,當handler為空時,handler被設置為DefaultServeMux,就是一開始注冊時使用的路由表。如果一層一層的往上翻,就會看到sh.srv.Handler在ListenAndServe函數中的第二個參數,而這個參數我們傳入的就是一個nil空值,所以我們使用的路由表就是這個DefaultServeMux。當然我們也可以自己傳入一個自定義的ServMux,但是后續的查找過程都是一樣的,具體的例子可以參考Go-HTTP。到這里又出現了跟上邊一樣的情況,雖然實際用的時候是按照Handler使用的,但實際上是一個ServeMux,所以最后調用的ServeHTTP函數,我們還是得看ServeMux的具體實現。
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) }
具體的實現就是根據傳入的Request,解析出URI來,然后從其內部的map中找到相應的Handler並返回,最后調用ServeHTTP,也就是上邊提到的我們注冊時傳入的sayHello方法(上邊也提過,ServeHTTP的具體實現,就是調用了sayHello)。
到這里,整個的大體流程就差不多了,從注冊到請求來時的處理方法查找。
本文所述的過程還是一個比較表面的過程,很淺顯,但是凡事都是由淺入深的,慢慢來吧,Go語言需要我們一步一步的去學習。有什么講解的不對的地方,請各位指出來,方便大家相處進步。