Go web開發初探


本人之前一直學習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開發的簡單流程:

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!” (這里為了簡便,就沒往網頁里寫入信息)

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

首先是注冊URI與相應的處理函數,這個就跟SpringMVC中的Controller差不多。

 

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語言需要我們一步一步的去學習。有什么講解的不對的地方,請各位指出來,方便大家相處進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM