首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Go HTTP 编程 | 02 - net\u002Fhttp 包剖析

Go HTTP 编程 | 02 - net\u002Fhttp 包剖析

作者头像
RiemannHypothesis
发布于 2022-11-25 11:33:10
发布于 2022-11-25 11:33:10
7670
举报
文章被收录于专栏:ElixirElixir

一、net/http 包详解

在上一篇文章中我们已经使用 net/http(以下简称 http) 创建了一个 Web 服务,并从源码层面分析了整个请求流转的过程,其中有两个比较核心的组件或者功能,一个是连接 Conn,另外一个是 ServeMux。

Conn

在 http 包中使用 goroutine 来处理连接的读写,这样既可以使每个请求保持独立,请求相互之间不会影响,不会阻塞,可以高效的响应网络请求,有可以实现高并发和高性能。Go 在等待客户端请求的代码如下:

代码语言:javascript
复制
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)

go c.serve(connCtx) 会为每一个请求创建一个 Conn,这个 Conn 中保存了该次请求的信息,然后在通过 serve 函数传递到对应的 handler,该 handler 中可以读取到相应请求的 header 信息,保证了每个请求的独立性。

ServeMux

在创建 Web 服务器的时候,我们通过 ListenAndServe 函数的第二个参数传递了一个 handler,这个 handler 为 nil,在 ServeHTTP 函数中如果 handler 为 nil,会给这个 handler 赋值一个 DefaultServeMux。

代码语言:javascript
复制
err := http.ListenAndServe(":9000", nil)
代码语言:javascript
复制
handler := sh.srv.Handler
if handler == nil {
   handler = DefaultServeMux
}

DefaultServeMux 就是一个 ServeMux:

代码语言:javascript
复制
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

ServeMux 结构体的定义如下:

代码语言:javascript
复制
type ServeMux struct {
   mu    sync.RWMutex // 锁,请求涉及到并发处理,这里需要一个锁的机制
   m     map[string]muxEntry // 路由规则,一个 string 对应一个 mux 实体,这里的 string 就是注册的路由表达方式
   es    []muxEntry // slice of entries sorted from longest to shortest.
   hosts bool       // whether any patterns contain hostnames
}

muxEntry 结构体的定义如下:

代码语言:javascript
复制
type muxEntry struct {
   h       Handler // 路由对应的 handler
   pattern string 
}

Handler 结构体的定义如下:

代码语言:javascript
复制
type Handler interface {
   ServeHTTP(ResponseWriter, *Request) // 路由实现器
}

Handler 是一个接口,该接口中定义了一个 ServeHTTP 方法,查看 HandleFun:

HandleFun 类型定义如下:

代码语言:javascript
复制
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
   f(w, r)
}

我们在创建 Web 服务器的时候通过 HandleFun 函数将路由和处理函数进行了绑定:

代码语言:javascript
复制
http.HandleFunc("/", sayHelloName)

我们定义的方法 sayHelloName 虽然没有直接实现 ServeHTTP 方法,但是 sayHelloName 方法就是 HandleFunc 调用后的结果,这个类型默认实现了 ServeHTTP 方法,即调用了 HandleFunc(f) 强制类型转换 f 为 HandleFunc 类型,这样就拥有了 ServeHTTP 方法。

路由器里面存储好了相应的路由映射规则后,通过调用 mux.handler(r).ServeHTTP(w,r) 来分发请求,*mux.handler(r)* 方法的源代码如下:

代码语言:javascript
复制
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
   mux.mu.RLock()
   defer mux.mu.RUnlock()

   // Host-specific pattern takes precedence over generic ones
   if mux.hosts {
      h, pattern = mux.match(host + path)
   }
   if h == nil {
      h, pattern = mux.match(path)
   }
   if h == nil {
      h, pattern = NotFoundHandler(), ""
   }
   return
}

handler 函数是根据用户请求的 URL 和路由器里面存储的 map 匹配获取存储的 handler,获取之后就可以调用 handler 的 ServeHTTP 方法执行相应的函数了。

Go 支持外部实现路由器,也就是 ListenerAndServe 方法的第二个参数,它是一个 Handler 接口,也就是实现 Handler 接口就可以实现自定义路由器。

在自定义的 Web 服务器的 main.go 文件中增加自定义路由器的代码:

代码语言:javascript
复制
//noinspection ALL
func main(){
   //http.HandleFunc("/", sayHelloName)
   alphaMux := &AlphaMux{}
   err := http.ListenAndServe(":9000", alphaMux)

   if err != nil {
      log.Fatal("ListenAndServer: ", err)
   }
}

func sayHelloName(w http.ResponseWriter, r *http.Request){

   fmt.Fprint(w, "Hello, AlphaMux")
}


// 自定义路由器结果体
type AlphaMux struct {

}

// 自定义路由规则
func (p *AlphaMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   if r.URL.Path == "/" {
      sayHelloName(w, r)
      return
   }

   http.NotFound(w, r)
   return
}

执行 main.go 文件,在浏览器中输入 localhost:9000,浏览器显示内容如下:

自定义的路由器生效。

二、Go 代码的执行流程

在使用默认的 DefaultServeMux 作为路由器的代码中,来看一下代码的执行流程:

Go 首先会调用 *http.HandleFunc*,这一行代码内部执行了以下几步:

第一步:调用了 DefaultServeMux 的 HandleFunc 方法;

代码语言:javascript
复制
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   DefaultServeMux.HandleFunc(pattern, handler)
}

第二步:调用了 DefaultServeMux 的 Handle 方法:

代码语言:javascript
复制
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   if handler == nil {
      panic("http: nil handler")
   }
   mux.Handle(pattern, HandlerFunc(handler))
}

第三步:通过 Handler 方法,往 DefaultServeMux 的 map[string]muxEntry 中增加对应的 handler 和路由规划:

接着调用了 http.ListenAndServe("9000", nil),这行代码内部执行了以下几件事:

第一:实例化一个 Server,并调用 Server 的 ListenAndServe() 方法

代码语言:javascript
复制
func ListenAndServe(addr string, handler Handler) error {
   server := &Server{Addr: addr, Handler: handler}
   return server.ListenAndServe()
}

第二:ListenAndServe() 方法中通过 net.Listen 监听传递的端口,在 ListenAndServe() 方法最后调用了 Serve() 方法:

代码语言:javascript
复制
func (srv *Server) ListenAndServe() error {
   if srv.shuttingDown() {
      return ErrServerClosed
   }
   addr := srv.Addr
   if addr == "" {
      addr = ":http"
   }
   ln, err := net.Listen("tcp", addr)
   if err != nil {
      return err
   }
   return srv.Serve(ln)
}

第三:Serve 方法中通过 for() 循环接收请求,然后在该方法末尾实例化了一个新的连接 Conn,并开启一个新的 goroutine 为这个请求进行服务 go serve(connCtx):

代码语言:javascript
复制
func (srv *Server) Serve(l net.Listener) error {
   
   // 其他代码省略
   for {
      rw, err := l.Accept()
      
      // 其他代码省略
      
      c := srv.newConn(rw)
      c.setState(c.rwc, StateNew, runHooks) // before Serve can return
      go c.serve(connCtx)
   }
}

第四:serve() 函数中通过 for 循环读取请求,并给每个请求分发一个 handler,分发 handler 是通过 ServeHTTP() 方法完成的:

代码语言:javascript
复制
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
   
   // 其余代码省略
   
   // 通过 for 循环读取请求
   for {
      w, err := c.readRequest(ctx)
      
      // 其余代码省略    
      
      // 获取 handler
      serverHandler{c.server}.ServeHTTP(w, w.req)
      inFlightResponse = nil
      
      // 其余代码省略
   }
}

第五:ServeHTTP 方法会分配 handler,我们在 main.go 文件中的 ListenAndServe 方法传递的第二个参数为 nil,这里就分配了一个 DefaultServeMux,并在最后调用了 DefaultServeMux 的 ServeHTTP 方法:

代码语言:javascript
复制
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
   handler := sh.srv.Handler
   if handler == nil {
      handler = DefaultServeMux
   }
   
   // 其余代码省略
   
   handler.ServeHTTP(rw, req)
}

第六:DefaultServeMux 其实就是一个 ServeMux, ServeMux 的 ServeHTTP 方法会根据请求匹配相应的 handler

代码语言:javascript
复制
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
   }
   h, _ := mux.Handler(r)
   h.ServeHTTP(w, r)
}

第七:查看 Handler() 方法的源码,是通过 mux.handler() 方法来选择 handler,handler 的选择会有三种情况:

代码语言:javascript
复制
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
   mux.mu.RLock()
   defer mux.mu.RUnlock()

   // Host-specific pattern takes precedence over generic ones
   if mux.hosts {
      h, pattern = mux.match(host + path)
   }
   if h == nil {
      h, pattern = mux.match(path)
   }
   if h == nil {
      h, pattern = NotFoundHandler(), ""
   }
   return
}

第八:最后调用 handler 的 ServeHTTP 方法就实现了响应客户端:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-15,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 一、net/http 包详解
    • Conn
    • ServeMux
  • 二、Go 代码的执行流程
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档