zoukankan      html  css  js  c++  java
  • 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 %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 Content-Type: text/plain Connection: close 431 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 Content-Type: text/plain Connection: close 400 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语言需要我们一步一步的去学习。有什么讲解的不对的地方,请各位指出来,方便大家相处进步。

  • 相关阅读:
    一分钟应对勒索病毒WannaCry
    你不知道网络安全有多严峻
    MongoDB 文章目录
    SQL Server 文章目录
    MySQL 文章目录
    领域驱动(DD)目录
    Oracle基本教程
    系统架构研究目录
    设计原则目录
    开源项目学习历程
  • 原文地址:https://www.cnblogs.com/hehheai/p/6513883.html
Copyright © 2011-2022 走看看