本人以前一直學習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語言須要咱們一步一步的去學習。有什麼講解的不對的地方,請各位指出來,方便你們相處進步。