github.com/code-reading/golang@v0.0.0-20220303082512-ba5bc0e589a3/docs/000-http.ListenAndServe机制源码分析.md (about)

     1  <!-- vscode-markdown-toc -->
     2  * 1. [说明](#)
     3  * 2. [分析概要](#-1)
     4  	* 2.1. [ 名词解释](#-1)
     5  	* 2.2. [http包执行流程](#http)
     6  	* 2.3. [http执行流程小结](#http-1)
     7  * 3. [源码分析](#-1)
     8  	* 3.1. [端口监听](#-1)
     9  	* 3.2. [接收请求](#-1)
    10  	* 3.3. [读取请求并解析](#-1)
    11  	* 3.4. [选择路由器](#-1)
    12  	* 3.5. [路由分配handler](#handler)
    13  
    14  <!-- vscode-markdown-toc-config
    15  	numbering=true
    16  	autoSave=true
    17  	/vscode-markdown-toc-config -->
    18  <!-- /vscode-markdown-toc -->
    19  
    20  ##  1. <a name=''></a>说明
    21  
    22  [分析示例:coding/net/00-listenAndServe.go](../coding/net/00-listenAndServe.go)
    23  
    24  [源码位置:/src/net/http/server.go](../go/src/net/http/server.go)
    25  
    26  ##  2. <a name='-1'></a>分析概要
    27  
    28  ###  2.1. <a name='-1'></a> 名词解释
    29  
    30  Request:用户请求的信息,用来解析用户的请求信息,包括method,Cookie,url等信息。
    31  
    32  Response:服务器需要反馈给客户端的信息。
    33  
    34  Conn:用户的每次请求链接。
    35  
    36  Handle:处理请求和生成返回信息的处理逻辑。
    37  
    38  ###  2.2. <a name='http'></a>http包执行流程
    39  
    40  ![http flow](../resources/images/http-flow.png)
    41  
    42  *golang 实现web服务的流程*
    43  
    44  创建Listen Socket,监听指定的端口,等待客户端请求到来。
    45  Listen Socket接受客户端的请求,得到Client Socket,接下来通过Client Socket与客户端通信。
    46  处理客户端请求,首先从Client Socket读取HTTP请求的协议头,如果是POST方法,还可能要读取客户端提交的数据,然后交给相应的handler处理请求,handler处理完,将数据通过Client Socket返回给客户端。
    47  
    48  ![一个http连接处理流程](../resources/images/http-connect-flow.png)
    49  ###  2.3. <a name='http-1'></a>http执行流程小结
    50  
    51  <strong>1、启动监听服务</strong>
    52  实例化Server。
    53  调用Server的ListenAndServe()。
    54  调用net.Listen("tcp",addr)监听端口。
    55  启动一个for循环,在循环体中Accept请求。
    56  
    57  <strong>2、注册路由处理器</strong>
    58  a. 首先调用Http.HandleFunc,按如下顺序执行:
    59  - 调用了DefaultServerMux的HandleFunc。
    60  - 调用了DefaultServerMux的Handle。
    61  - 往DefaultServerMux的map[string] muxEntry中增加对应的handler和路由规则。
    62  
    63  b. 调用http.ListenAndServe(":9090",nil),按如下顺序执行:
    64  
    65  <strong>3、响应请求并处理逻辑</strong>
    66  a.对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()。
    67  b.读取每个请求的内容w,err:=c.readRequest()。
    68  c.判断handler是否为空,如果没有设置handler,handler默认设置为DefaultServeMux。
    69  d.调用handler的ServeHttp。
    70  e.根据request选择handler,并且进入到这个handler的ServeHTTP, mux.handler(r).ServeHTTP(w,r)
    71  f.选择handler
    72  - 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)。
    73  - 如果有路由满足,调用这个路由handler的ServeHttp。
    74  - 如果没有路由满足,调用NotFoundHandler的ServeHttp。
    75  
    76  ![http 执行流程](../resources/images/http-web-flow.jpeg)
    77  
    78  ##  3. <a name='-1'></a>源码分析
    79  
    80  ###  3.1. <a name='-1'></a>端口监听
    81  搭建 web 服务器的时候有行代码:http.ListenAndServe(":9999", nil)
    82  
    83  http.ListenAndServe 实际上,初始化一个server对象,调用了 server 的 ListenAndServe 方法
    84  
    85  ```go
    86  func ListenAndServe(addr string, handler Handler) error {
    87      server := &Server{Addr: addr, Handler: handler}
    88      return server.ListenAndServe()
    89  }
    90  ```
    91  然后是 ln, err := net.Listen("tcp", addr) ,用TCP协议搭建了一个服务,监听着设置的端口
    92  
    93  ```go
    94  func (srv *Server) ListenAndServe() error {
    95      if srv.shuttingDown() {
    96          return ErrServerClosed
    97      }
    98      addr := srv.Addr
    99      if addr == "" {
   100          addr = ":http"
   101      }
   102      ln, err := net.Listen("tcp", addr)
   103      if err != nil {
   104          return err
   105      }
   106      return srv.Serve(ln)
   107  }
   108  ```
   109  
   110  ###  3.2. <a name='-1'></a>接收请求
   111  
   112  ```go
   113  for {
   114          // 接受监听器listener的请求
   115          rw, e := l.Accept()
   116          if e != nil {
   117              // 监听是否关闭信号
   118              select {
   119              case <-srv.getDoneChan():
   120                  return ErrServerClosed
   121              default:
   122              }
   123          }
   124              ……
   125          // 创建新连接
   126          c := srv.newConn(rw)
   127          // 再返回之前,设置连接状态
   128          c.setState(c.rwc, StateNew) // before Serve can return
   129          // 创建goroutine,真正处理连接
   130          go c.serve(ctx)
   131      }
   132  ```
   133  
   134  如上, srv.Serve(ln) 中 使用一个for{} 循环接收请求,
   135  > 1.首先通过 listener.Accept 接受请求,是不是跟上面提到的socket的运行机制很像了。
   136  > 2.接着用接受到的请求创建一个新的 Conn,并设置为New状态。
   137  > 3.最后创建 goroutine,真正的处理连接。
   138  
   139  <strong>补充</strong>:每个请求都会创建一个对应的goroutine去处理,所以各个请求之间是相互不影响的,同时提高并发性能。
   140  
   141  ###  3.3. <a name='-1'></a>读取请求并解析
   142  
   143  ```go
   144  for {
   145          ……
   146      
   147          // 读request请求
   148          w, err := c.readRequest(ctx)
   149          
   150          ……
   151          // 调用业务层定义的路由
   152          serverHandler{c.server}.ServeHTTP(w, w.req)
   153          
   154          ……
   155          // flush刷io buffer的数据
   156          w.finishRequest()
   157  ```
   158  
   159  go c.serve(ctx) 响应并处理请求, 
   160  readRequest 便是读取数据,解析请求的地方,包括解析请求的header、body,和一些基本的校验,比如header头信息,请求method等。
   161  
   162  最后将请求的数据赋值到Request,并初始化Response对象,供业务层调用。
   163  
   164  ###  3.4. <a name='-1'></a>选择路由器
   165  上面关键流程已经看到了serverHandler{c.server}.ServeHTTP(w, w.req),这个实际上就是调用最开始在main函数定义的handler,并将处理好的Request、Response对象作为参数传入。
   166  
   167  ```go
   168  type serverHandler struct {
   169      srv *Server
   170  }
   171  
   172  func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
   173      handler := sh.srv.Handler
   174      if handler == nil {
   175          handler = DefaultServeMux
   176      }
   177      if req.RequestURI == "*" && req.Method == "OPTIONS" {
   178          handler = globalOptionsHandler{}
   179      }
   180      handler.ServeHTTP(rw, req)
   181  }
   182  ```
   183  
   184  还记得吗,最上面是这样调用的 http.ListenAndServe(":9999", nil),第二个参数是nil。
   185  
   186  所以,你看在ServeHTTP中,handler = DefaultServeMux,我们使用了默认的路由器,如果 ListenAndServe 不是传nil的话,那就会使用你自己定义的路由器。
   187  
   188  ###  3.5. <a name='handler'></a>路由分配handler
   189  好了,我们知道了使用默认的路由器(DefaultServeMux),再看看它是怎么根据路径找对应handler的吧
   190  
   191  路由的过程里面只要弄懂下面的三个问题,就知道 Go 自带的路由是怎么运行的了:
   192  
   193  1.什么时候注册的路由?
   194  2.如何根据注册的路由找对应的handler?
   195  3.如果没注册路由访问会返回什么?
   196  
   197  先来看看默认路由器(DefaultServeMux)的结构定义:
   198  
   199  ```go
   200  type ServeMux struct {
   201      mu    sync.RWMutex
   202      m     map[string]muxEntry
   203      es    []muxEntry 
   204      hosts bool
   205  }
   206  ```
   207  其中 m 是一个map,用来存储路由pattern与handler的关系;es 是一个slice,将路由按长度从大到小排序存储起来。
   208  
   209  匹配规则:首先精确匹配 m 中的pattern;如果在 m 不能精确匹配路径时,会在 es 中找到最接近的路由规则:比如注册了两个路径 /a/b/ /a/ ,当请求URL是 /a/b/c时,会匹配到 /a/b/ 而不是 /a/。
   210  
   211  <strong>1. 什么时候注册的路由?</strong>
   212  
   213  还记得吧,在 ListenAndServe 之前,有这么一行代码,http.HandleFunc("/", indexHandler),这个便是在注册路由。来我们把一些检查代码去掉,看看关键代码:
   214  
   215  ```go
   216  func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   217      DefaultServeMux.HandleFunc(pattern, handler)
   218  }
   219  
   220  func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   221      ……
   222      mux.Handle(pattern, HandlerFunc(handler))
   223  }
   224  
   225  func (mux *ServeMux) Handle(pattern string, handler Handler) {
   226      ……
   227      e := muxEntry{h: handler, pattern: pattern}
   228      mux.m[pattern] = e
   229      if pattern[len(pattern)-1] == '/' {
   230          mux.es = appendSorted(mux.es, e)
   231      }
   232  
   233      if pattern[0] != '/' {
   234          mux.hosts = true
   235      }
   236  }
   237  ```
   238  这就是把路由表往 ServeMux.m 和 ServeMux.es 写的全过程
   239  
   240  <strong>2. 如何根据注册的路由找对应的handler?</strong>
   241  
   242  其实知道怎么写路由表,大概也能猜到是怎么进行找了,无非就是从上面的 m,es 进行匹配。
   243  
   244  我们上面从端口监听,接受请求,读取请求并解析,再到路由分配handler,一路追到 handler.ServeHTTP(rw, req),现在再看看这段代码的实现
   245  
   246  ```go
   247  // 根据预设的pattern,将request分配最匹配的handler处理
   248  func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
   249      ……
   250      h, _ := mux.Handler(r)
   251      h.ServeHTTP(w, r)
   252  }
   253  
   254  ……
   255  
   256  func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
   257      if mux.hosts {
   258          h, pattern = mux.match(host + path)
   259      }
   260      if h == nil {
   261          h, pattern = mux.match(path)
   262      }
   263      if h == nil {
   264          h, pattern = NotFoundHandler(), ""
   265      }
   266      return
   267  }
   268  
   269  func (mux *ServeMux) match(path string) (h Handler, pattern string) {
   270      // 优先查找m表
   271      v, ok := mux.m[path]
   272      if ok {
   273          return v.h, v.pattern
   274      }
   275  
   276      // 未精确匹配成功,查询es(已排序),路径长的优先匹配
   277      for _, e := range mux.es {
   278          if strings.HasPrefix(path, e.pattern) {
   279              return e.h, e.pattern
   280          }
   281      }
   282      return nil, ""
   283  }
   284  ```
   285  
   286  <strong>3. 如果没注册路由访问会返回什么?</strong>
   287  
   288  这个问题其实上面代码已经看到了,Go 内置了一个 NotFoundHandler(),返回 "404 page not found"