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

时间: 2024-12-21 10:07:11

Go web开发初探的相关文章

移动端web开发初探之Vuejs的简单实战

这段时间在做的东西,是北邮人论坛APP的注册页.这个注册页是内嵌的网页,因为打算安卓和IOS平台同时使用.因此实际上就是在做移动端的web开发了. 在这过程中遇到了不少有意思的东西. DEMO的github地址在这里 内容提要: meta标签 Vuejs的简单实战 CSS移动端全屏背景 CSS移动端动画初探 meta标签 这点与在PC端写前端有着很大的区别,移动端的meta标签简直多.我就说说我所用到的标签. <!-- 1.如果支持Google Chrome Frame:GCF,则使用GCF渲染

WebSocket集成XMPP网页即时通讯1:Java Web Project服务端/客户端Jetty9开发初探

Web 应用的信息交互过程通常是客户端通过浏览器发出一个请求,服务器端接收和审核完请求后进行处理并返回结果给客户端,然后客户端浏览器将信息呈现出来,这种机制对于信息变化不是特别频繁的应用尚能相安无事,但是对于那些实时要求比较高的应用来说,比如说在线游戏.在线证券.设备监控.新闻在线播报.RSS 订阅推送等等,当客户端浏览器准备呈现这些信息的时候,这些信息在服务器端可能已经过时了.所以保持客户端和服务器端的信息同步是实时 Web 应用的关键要素,对 Web 开发人员来说也是一个难题.在 WebSo

高性能WEB开发:重排与重绘

DOM编程可能最耗时的地方,重排和重绘. 1.什么是重排和重绘 浏览器下载完页面中的所有组件--HTML标记.JavaScript.CSS.图片之后会解析生成两个内部数据结构--DOM树和渲染树. DOM树表示页面结构,渲染树表示DOM节点如何显示.DOM树中的每一个需要显示的节点在渲染树种至少存在一个对应的节点(隐藏的DOM元素disply值为none 在渲染树中没有对应的节点).渲染树中的节点被称为"帧"或"盒",符合CSS模型的定义,理解页面元素为一个具有填充

[Java Web]2\Web开发中的一些架构

1.企业开发架构: 企业平台开发大量采用B/S开发模式,不管采用何种动态Web实现手段,其操作形式都是一样的,其核心操作的大部分都是围绕着数据库进行的.但是如果使用编程语言进行数据库开发,要涉及很多诸如事务.安全等操作问题,所以现在开发往往要通过中间件进行过渡,即,程序运行在中间件上,并通过中间件进行操作系统的操作,而具体一些相关的处理,如事务.安全等完全由中间件来负责,这样程序员只要完成具体的功能开发即可. 2.Java EE架构: Java EE 是在 Java SE 的基础上构建的,.NE

七日Python之路--第十二天(Django Web 开发指南)

<Django Web 开发指南>.貌似使用Django1.0版本,基本内容差不多,细读无妨.地址:http://www.jb51.net/books/76079.html (一)第一部分 入门 (1)内置数字工厂函数 int(12.34)会创建一个新的值为12的整数对象,而float(12)则会返回12.0. (2)其他序列操作符 连接(+),复制(*),以及检查是否是成员(in, not in) '**'.join('**')   或  '***%s***%d' % (str, int)

Unity3D游戏开发初探—2.初步了解3D模型基础

一.什么是3D模型? 1.1 3D模型概述 简而言之,3D模型就是三维的.立体的模型,D是英文Dimensions的缩写. 3D模型也可以说是用3Ds MAX建造的立体模型,包括各种建筑.人物.植被.机械等等,比如一个大楼的3D模型图.3D模型也包括玩具和电脑模型领域. 互联网的形态一直以来都是2D模式的,但是随着3D技术的不断进步,在未来的时间里,将会有越来越多的互联网应用以3D的方式呈现给用户,包括网络视讯.电子阅读.网络游戏.虚拟社区.电子商务.远程教育等等.甚至对于旅游业,3D互联网也能

Java Web开发基础(3)-JSTL

在DRP项目中接触到了JSTL标签库,对我这样的比較懒的人来说,第一感觉就是"惊艳". JSTL标签库的使用.能够消除大量复杂.繁复的工作.工作量降低的不是一点半点.是降低了一大半.不论什么工具的引入,都会使我们的工作变的简单.可是问题是我们工作能够变的简单,可是我们不能让自己的大脑变的简单.所以,我们不是简单的会用就能够.我们还须要了解这个工具是怎样工作的.怎样为我们提供便利的.ok.以下进入正题,我们从问题開始-- JSP脚本带来的问题 不知道看到这几个子,各位有什么想法?反正我认

给大家分享web开发新手修改hosts文件实现本地域名访问的正确方法

1.如何正确修改hosts文件: 一般打开hosts文件里面都会有个示例,按照其格式修改即可 比如以下内容: # For example: # # 102.54.94.97 rhino.acme.com # source server # 38.25.63.10 x.acme.com # x client host 即代表打开rhino.acme.com这个网址将解析到102.54.94.97,ip地址与网址间至少有一空格,当然建议通过按Table键来编辑,即美观又不容易编写失误;这也就是通过解

java web开发阅读笔记:第一章

学习该书前所用推荐书籍<名师讲坛-java开发实战经典> 一web开发前奏 1.1网页发展 首先搞懂. 1.HTTP:超级文本传输协议,是一种通讯协议. 通过这个网络协议WW浏览器与WWW服务器之间的通讯进行规定,并且通过这个协议我们可以浏览网页,通过网页从客户端写入信息,从服务端得到信息.而通常的HTTP消息包括"客户端->服务端"与"服务端->客户端"的消息传送,图解HTTP协议的功能: 是在客户端通过浏览器根据HTTP协议中向服务器端发