Go HTTP 编程 | 02 - net/http 包剖析

简介: Go HTTP 编程 | 02 - net/http 包剖析

一、net/http 包详解

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

Conn

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

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,这个 handlernil,在 ServeHTTP 函数中如果 handlernil,会给这个 handler 赋值一个 DefaultServeMux

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

DefaultServeMux 就是一个 ServeMux

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
复制代码

ServeMux 结构体的定义如下:

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 结构体的定义如下:

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

Handler 结构体的定义如下:

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

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

image.png

HandleFun 类型定义如下:

type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
   f(w, r)
}
复制代码

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

http.HandleFunc("/", sayHelloName)
复制代码

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

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

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,获取之后就可以调用 handlerServeHTTP 方法执行相应的函数了。

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

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

//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,浏览器显示内容如下:

image.png

自定义的路由器生效。

二、Go 代码的执行流程

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

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

第一步:调用了 DefaultServeMuxHandleFunc 方法;

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

第二步:调用了 DefaultServeMuxHandle 方法:

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

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

image.png

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

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

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

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

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)

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() 方法完成的:

// 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,并在最后调用了 DefaultServeMuxServeHTTP 方法:

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

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

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() 方法来选择 handlerhandler 的选择会有三种情况:

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
}
复制代码

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

image.png


相关文章
|
2月前
|
前端开发 网络协议 安全
【网络原理】——HTTP协议、fiddler抓包
HTTP超文本传输,HTML,fiddler抓包,URL,urlencode,HTTP首行方法,GET方法,POST方法
|
3月前
|
数据库连接 Go 数据库
Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性
本文探讨了Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性;防御编程则强调在编码时考虑各种错误情况,确保程序健壮性。文章详细介绍了这两种技术在Go语言中的实现方法及其重要性,旨在提升软件质量和可靠性。
55 1
|
4月前
|
开发框架 .NET 测试技术
了解 .NET 9 中的新 Microsoft.AspNetCore.OpenApi 包,并将其与 NSwag 和 Swashbuckle.AspNetCore 进行比较。
本文介绍了 `.NET 9` 中新推出的 `Microsoft.AspNetCore.OpenApi` 包,该包旨在为 `ASP.NET Core` 应用程序生成 `OpenAPI` 文档。文章对比了 `NSwag` 和 `Swashbuckle.AspNetCore` 两大现有库,探讨了新包的优势和不足,特别是在性能和功能方面。尽管新包在某些方面尚不及成熟库完善,但其对原生 `AoT` 编译的支持和未来的扩展潜力使其成为一个值得考虑的选择。文章还提供了详细的性能测试数据和优化建议,适合对 `OpenAPI` 文档生成感兴趣的开发者阅读。
221 3
了解 .NET 9 中的新 Microsoft.AspNetCore.OpenApi 包,并将其与 NSwag 和 Swashbuckle.AspNetCore 进行比较。
|
3月前
|
数据采集 监控 Java
go语言编程学习
【11月更文挑战第3天】
55 7
|
3月前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
88 13
|
3月前
|
Unix Linux Go
go进阶编程:Golang中的文件与文件夹操作指南
本文详细介绍了Golang中文件与文件夹的基本操作,包括读取、写入、创建、删除和遍历等。通过示例代码展示了如何使用`os`和`io/ioutil`包进行文件操作,并强调了错误处理、权限控制和路径问题的重要性。适合初学者和有经验的开发者参考。
|
4月前
|
Java 大数据 Go
Go语言:高效并发的编程新星
【10月更文挑战第21】Go语言:高效并发的编程新星
72 7
|
4月前
|
Go 数据处理 调度
Go语言中的并发模型:解锁高效并行编程的秘诀
本文将探讨Go语言中独特的并发模型及其在现代软件开发中的应用。通过深入分析 Goroutines 和 Channels,我们将揭示这一模型如何简化并行编程,提升应用性能,并改变开发者处理并发任务的方式。不同于传统多线程编程,Go的并发方法以其简洁性和高效性脱颖而出,为开发者提供了一种全新的编程范式。
|
4月前
|
网络协议 JavaScript 前端开发
【HTTP】HTTP报文格式和抓包
【HTTP】HTTP报文格式和抓包
134 0
http数据包抓包解析
http数据包抓包解析