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

packagemain

import(

"fmt"

"log"

"net/http"

)

funcsayHello(w http.ResponseWriter, r *http.Request) {

fmt.Println("Hello World!")

}

funcmain() {

http.HandleFunc("/hello", sayHello)  //注册URI路径与相应的处理函数

er := http.ListenAndServe(":9090", nil)// 监听9090端口,就跟javaweb中tomcat用的8080差不多一个意思吧

ifer != nil {

log.Fatal("ListenAndServe: ", er)

}

}

  在浏览器运行localhost:9090/hello 就会在命令行或者所用编辑器的输出窗口 “Hello World!” (这里为了简便,就没往网页里写入信息)

根据这个简单的例子,一步一步的分析它是如何运行。

首先是注册URI与相应的处理函数,这个就跟SpringMVC中的Controller差不多。

1

http.HandleFunc("/hello", sayHello)

  来看一下他的源码:

1

2

3

funcHandleFunc(pattern string, handlerfunc(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

typeServeMuxstruct{

mu sync.RWMutex

mmap[string]muxEntry

hosts bool// whether any patterns contain hostnames

}

typemuxEntrystruct{

explicit bool

h Handler

pattern string

}

funcNewServeMux() *ServeMux {return&ServeMux{m: make(map[string]muxEntry)} }

varDefaultServeMux = 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, handlerfunc(ResponseWriter, *Request)) {

mux.Handle(pattern, HandlerFunc(handler))

}

func(mux *ServeMux) Handle(pattern string, handler Handler) {

mux.mu.Lock()

defermux.mu.Unlock()

ifpattern ==""{

panic("http: invalid pattern "+ pattern)

}

ifhandler == nil {

panic("http: nil handler")

}

ifmux.m[pattern].explicit {

panic("http: multiple registrations for "+ pattern)

}

mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

ifpattern[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)

ifn > 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

ifpattern[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

typeHandlerFuncfunc(ResponseWriter, *Request)

  这个HandlerFunc和我们之前写的sayHello函数有相同的参数,所以能强制转换。 而Handle方法的第二个参数是Handler类型,这就说明HandlerFunc函数也是一个Handler,下边看一个Handler的定义:

  

1

2

3

4

5

6

typeHandlerinterface{

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

funcListenAndServe(addr string, handler Handler) error {

server := &Server{Addr: addr, Handler: handler}

returnserver.ListenAndServe()

}

func(srv *Server) ListenAndServe() error {

  addr := srv.Addr

  ifaddr ==""{

    addr =":http"

  }

  ln, err := net.Listen("tcp", addr)

  iferr != nil {

    returnerr

  }

  returnsrv.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 {

deferl.Close()

iffn := testHookServerServe; fn != nil {

fn(srv, l)

}

vartempDelay time.Duration// how long to sleep on accept failure

iferr := srv.setupHTTP2(); err != nil {

returnerr

}

for{

rw, e := l.Accept()

ife != nil {

ifne, ok := e.(net.Error); ok && ne.Temporary() {

iftempDelay == 0 {

tempDelay = 5 * time.Millisecond

}else{

tempDelay *= 2

}

ifmax := 1 * time.Second; tempDelay > max {

tempDelay = max

}

srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)

time.Sleep(tempDelay)

continue

}

returne

}

tempDelay = 0

c := srv.newConn(rw)

c.setState(c.rwc, StateNew)// before Serve can return

goc.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()

deferfunc() {

iferr := recover(); err != nil {

constsize = 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)

}

}()

iftlsConn, ok := c.rwc.(*tls.Conn); ok {

ifd := c.server.ReadTimeout; d != 0 {

c.rwc.SetReadDeadline(time.Now().Add(d))

}

ifd := c.server.WriteTimeout; d != 0 {

c.rwc.SetWriteDeadline(time.Now().Add(d))

}

iferr := 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()

ifproto := c.tlsState.NegotiatedProtocol; validNPN(proto) {

iffn := 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()

ifc.r.remain != c.server.initialReadLimitSize() {

// If we read any bytes off the wire, we're active.

c.setState(c.rwc, StateActive)

}

iferr != nil {

iferr == 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

}

iferr == io.EOF {

return// don't reply

}

ifneterr, ok := err.(net.Error); ok && neterr.Timeout() {

return// don't reply

}

varpublicErr string

ifv, 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

ifreq.expectsContinue() {

ifreq.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}

}

}elseifreq.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)

ifc.hijacked() {

return

}

w.finishRequest()

if!w.shouldReuseConnection() {

ifw.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

typeserverHandlerstruct{

srv *Server

}

func(sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {

handler := sh.srv.Handler

ifhandler == nil {

handler = DefaultServeMux

}

ifreq.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) {

ifr.RequestURI =="*"{

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