2017年的第一篇博客,也是第一次寫博客,寫的很差,請各位見諒。html
本人以前一直學習java、java web,最近開始學習Go語言,因此也想了解一下Go語言中web的開發方式以及運行機制。java
在《Go web編程》一書第三節中簡要的提到了Go語言中http的運行方式,我這裏是在這個的基礎上更加詳細的梳理一下。web
這裏先提一句,本文中展現的源代碼都是在Go安裝目錄下src/net/http/server.go文件中(除了本身寫的實例程序),若是各位還想理解的更詳細,能夠本身再去研究一下源代碼。編程
《Go web編程》3.4節中提到http有兩個核心功能:Conn, ServeMux , 可是我以爲還有一個Handler接口也挺重要的,後邊我們提到了再說。
瀏覽器
先從一個簡單的實例來看一下Go web開發的簡單流程:tomcat
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!」 (這裏爲了簡便,就沒往網頁裏寫入信息)多線程
根據這個簡單的例子,一步一步的分析它是如何運行。tcp
首先是註冊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語言須要咱們一步一步的去學習。有什麼講解的不對的地方,請各位指出來,方便你們相處進步。