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

Go web开发初探相关推荐

  1. Web开发工具之HBuilder初探

    工欲善其事,必先利其器.作为一名web前端开发者,我们要想工作得心应手,势必离不开顺手的工具.Hbuilder作为web编辑器的新秀就是这样一个利器.下面来一探究竟. HBuilder是DCloud推 ...

  2. RESTful Web Services初探

    RESTful Web Services初探 作者:杜刚 近几年,RESTful Web Services渐渐开始流行,大量用于解决异构系统间的通信问题.很多网站和应用提供的API,都是基于RESTf ...

  3. Unity3D游戏开发初探—1.跨平台的游戏引擎让.NET程序员新生

    一.Unity3D平台简介 Unity是由Unity Technologies开发的一个让轻松创建诸如三维视频游戏.建筑可视化.实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的 ...

  4. web开发微信文章目录

    Web开发微信文章目录 2015-12-13 Web开发 本文是Web开发微信的文章目录.通过目录查看文章编号,回复文章编号就能查看文章全文. 回复编号查看全文,搜索分类名可以获得该分类下的文章.   ...

  5. 写给自己的web开发资源

    web开发给我的感觉就是乱七八糟,而且要学习感觉总是会有东西要学习,很乱很杂我也没空搞,(其实学习这个的方法就是去用它,什么你直接用?学过js么学过jquery么?哈哈,我没有系统的看完过,但是也做出 ...

  6. 第五篇:Visual Studio 2008 Web开发使用的新特性

    第五篇:Visual Studio 2008 Web开发使用的新特性 本篇翻译自MSDN. .NET Framwork 3.5与Visual Studio 2008 包含很多新特性.AJAX的Web开 ...

  7. Web 开发与设计之 Google 兵器谱

    Google 的使命是 Web,在 Google 眼中,未来的一切应用都将 Web 化,一直以来,Google 为 Web 开发与设计者推出了大量免费工具,让他们更好地创建,维护,改善他们的 Web ...

  8. 如何用Python做Web开发?——Django环境配置

    用Python做Web开发,Django框架是个非常好的起点.如何从零开始,配置好Django开发环境呢?本文带你一步步无痛上手. 概念 最近有个词儿很流行,叫做"全栈"(full ...

  9. Web 开发人员必备的随机 JSON 数据生成工具

    在 Web 开发中,经常会需要一些测试数据来测试接口或者功能时候正确.JSON Generator 就是这样一款生成随机 JSON 数据的在线工具,Web 开发人员必备,记得收藏和分享啊. 您可能感兴 ...

最新文章

  1. python类方法是什么_python类方法和普通方法区别是什么
  2. 2021-05-10 矩阵AB和矩阵BA有什么联系?
  3. C++和Java中成员数据名和成员函数名的冲突问题
  4. Dlib学习笔记:dlib array2d与 OpenCV Mat互转
  5. IdentityServer4-前后端分离的授权验证(六)
  6. 二叉树的创建_大多数人都不会手写创建并遍历二叉树,一航这里帮你终结了
  7. Qt文档阅读笔记-WebEngine Content Manipulatoin Example
  8. 跨平台异步IO库 libuv 源代码接口详解
  9. 企业运维经典面试题汇总(5)
  10. cheat engine 将选中目标的函数_EXCEL函数与公式剖析:IF
  11. 一个小时,零基础入门,看完这篇30行代码 教你实现百度换肤!
  12. 五分钟学会安装电脑操作系统
  13. 【深入理解Java原理】Java类加载机制
  14. Linux性能工具:系统CPU
  15. python局域网文件互传
  16. 【C/C++基础进阶系列】C/C++ STL -- 智能指针
  17. php theexcerpt,WordPress获取文章摘要函数the_excerpt详解
  18. LeetCode——1900. 最佳运动员的比拼回合(The Earliest and Latest Rounds Where Players Compete)[困难]——分析及代码(Java)
  19. PHP获取钉钉审批,PHP获取钉钉考勤信息源代码
  20. 3种在JavaScript中验证电子邮件地址的方法

热门文章

  1. Linux系统编程 —共享内存之mmap
  2. TensorFlow入门教程(十):LSTM网络实现手写字体识别
  3. JavaGUI设计-计算器
  4. SRAM、PSRAM、SPI FLASH杂记
  5. CBTC系统标准: 1474.3---系统设计和功能分配需求
  6. 如何清理Tomcat缓存
  7. 有oracle操作系统,哪些操作系统可以安装ORACLE软件
  8. android如何加载长图
  9. RBAC权限模型详解
  10. 《REWORK》启示录 可笑的求职简历——可笑的求职经历