Go web開發初探

本人以前一直學習java、java web,最近開始學習Go語言,因此也想了解一下Go語言中web的開發方式以及運行機制。html

在《Go web編程》一書第三節中簡要的提到了Go語言中http的運行方式,我這裏是在這個的基礎上更加詳細的梳理一下。java

這裏先提一句,本文中展現的源代碼都是在Go安裝目錄下src/net/http/server.go文件中(除了本身寫的實例程序),若是各位還想理解的更詳細,能夠本身再去研究一下源代碼。web

《Go web編程》3.4節中提到http有兩個核心功能:Conn, ServeMux , 可是我以爲還有一個Handler接口也挺重要的,後邊我們提到了再說。編程

先從一個簡單的實例來看一下Go web開發的簡單流程:瀏覽器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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!」 (這裏爲了簡便,就沒往網頁裏寫入信息)tomcat

根據這個簡單的例子,一步一步的分析它是如何運行。多線程

首先是註冊URI與相應的處理函數,這個就跟SpringMVC中的Controller差很少。tcp

 

1
http.HandleFunc( "/hello" , sayHello)

  來看一下他的源碼:編輯器

1
2
3
func  HandleFunc(pattern string, handler  func (ResponseWriter, *Request)) {
     DefaultServeMux.HandleFunc(pattern, handler)
}

  裏邊實際是調用了DefaultServeMux的HandlerFunc方法,那麼這個DefaultServeMux是啥,HandleFunc又幹了啥呢?函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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,別弄混了),下面看一下這個函數:

1
type  HandlerFunc  func (ResponseWriter, *Request)

  這個HandlerFunc和咱們以前寫的sayHello函數有相同的參數,因此能強制轉換。 而Handle方法的第二個參數是Handler類型,這就說明HandlerFunc函數也是一個Handler,下邊看一個Handler的定義:

  

1
2
3
4
5
6
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是怎樣一個過程:

1
er := http.ListenAndServe( ":9090" , nil)

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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方法單獨放出來,以避免貼的代碼太長,你們看不下去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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類型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的具體實現。

1
2
3
4
5
6
7
8
9
10
11
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
     }
     <strong>h, _ := mux.Handler(r)
     h.ServeHTTP(w, r)</strong>
}

  具體的實現就是根據傳入的Request,解析出URI來,而後從其內部的map中找到相應的Handler並返回,最後調用ServeHTTP,也就是上邊提到的咱們註冊時傳入的sayHello方法(上邊也提過,ServeHTTP的具體實現,就是調用了sayHello)。

到這裏,整個的大致流程就差很少了,從註冊到請求來時的處理方法查找。

本文所述的過程仍是一個比較表面的過程,很淺顯,可是凡事都是由淺入深的,慢慢來吧,Go語言須要咱們一步一步的去學習。有什麼講解的不對的地方,請各位指出來,方便你們相處進步。

相關文章
相關標籤/搜索