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语言需要我们一步一步的去学习。有什么讲解的不对的地方,请各位指出来,方便大家相处进步。