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


相关文章
|
17天前
|
编译器 Go 开发者
go语言中导入相关包
【11月更文挑战第1天】
28 3
|
1月前
|
传感器 数据采集 物联网
探索.NET nanoFramework:为嵌入式设备编程的新途径
探索.NET nanoFramework:为嵌入式设备编程的新途
42 7
|
1月前
|
网络协议 JavaScript 前端开发
【HTTP】HTTP报文格式和抓包
【HTTP】HTTP报文格式和抓包
47 0
http数据包抓包解析
http数据包抓包解析
|
1月前
|
存储 Go 数据库
Go语言Context包源码学习
【10月更文挑战第21天】Go 语言中的 `context` 包用于在函数调用链中传递请求上下文信息,支持请求的取消、超时和截止时间管理。其核心接口 `Context` 定义了 `Deadline`、`Done`、`Err` 和 `Value` 方法,分别用于处理截止时间、取消信号、错误信息和键值对数据。包内提供了 `emptyCtx`、`cancelCtx`、`timerCtx` 和 `valueCtx` 四种实现类型,满足不同场景需求。示例代码展示了如何使用带有超时功能的上下文进行任务管理和取消。
http数据包抓包解析课程笔记
http数据包抓包解析课程笔记
|
2月前
|
存储 Go
Golang语言基于go module方式管理包(package)
这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。
49 3
|
3月前
|
Web App开发 存储
常见抓包工具配置抓取HTTPS
常见抓包工具配置抓取HTTPS
|
3月前
|
大数据 开发工具 开发者
从零到英雄:.NET核心技术带你踏上编程之旅,构建首个应用,开启你的数字世界探险!
【8月更文挑战第28天】本文带领读者从零开始,使用强大的.NET平台搭建首个控制台应用。无论你是新手还是希望扩展技能的开发者,都能通过本文逐步掌握.NET的核心技术。从环境搭建到创建项目,再到编写和运行代码,详细步骤助你轻松上手。通过计算两数之和的小项目,你不仅能快速入门,还能为未来开发更复杂的应用奠定基础。希望本文为你的.NET学习之旅开启新篇章!
35 1
|
3月前
|
机器学习/深度学习 Ubuntu Linux
在Linux中,如何按照该要求抓包:只过滤出访问http服务的,目标ip为192.168.0.111,一共抓1000个包,并且保存到1.cap文件中?
在Linux中,如何按照该要求抓包:只过滤出访问http服务的,目标ip为192.168.0.111,一共抓1000个包,并且保存到1.cap文件中?

热门文章

最新文章

下一篇
无影云桌面