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


相关文章
|
11天前
|
供应链 安全 开发者
供应链投毒预警:恶意Py包伪装HTTP组件开展CStealer窃密后门攻击
近日(2024年4月25号),悬镜供应链安全情报中心在Pypi官方仓库(https://pypi.org/)中捕获1起CStealer窃密后门投毒事件,投毒者连续发布6个不同版本的恶意Py包multiplerequests,目标针对windows平台python开发者,该恶意包在安装时会远程加载CStealer后门到受害者系统上执行,该后门会窃取受害者系统敏感信息、主流浏览器隐私数据、数字货币钱包应用数据以及系统屏幕截屏等。此外,后门还会尝试驻留Windows系统启动目录实现开机自启动。
22 0
供应链投毒预警:恶意Py包伪装HTTP组件开展CStealer窃密后门攻击
|
11天前
|
数据库连接 Go 数据库
【Go 语言专栏】Go 语言中的错误注入与防御编程
【4月更文挑战第30天】本文探讨了Go语言中的错误注入和防御编程。错误注入是故意引入错误以测试系统异常情况下的稳定性和容错性,包括模拟网络故障、数据库错误和手动触发错误。防御编程则强调编写代码时考虑并预防错误,确保程序面对异常时稳定运行。Go语言的错误处理机制包括多返回值和自定义错误类型。结合错误注入和防御编程,可以提升软件质量和可靠性,打造更健壮的系统。开发人员应重视这两方面,以实现更优质的软件产品。
|
11天前
|
网络协议 安全 Go
【Go语言专栏】Go语言中的WebSocket编程
【4月更文挑战第30天】本文介绍了在Go语言中使用WebSocket进行实时Web应用开发的方法。通过第三方包`gorilla/websocket`,开发者可建立WebSocket服务器和客户端。文中展示了如何创建服务器,升级HTTP连接,以及处理读写消息的示例代码。同时,客户端的创建和通信过程也得以阐述。文章还提及WebSocket的生命周期管理、性能与安全性考虑,以及实践中的最佳做法。通过学习,读者将能运用Go语言构建高效、实时的Web应用。
|
11天前
|
算法 Java Go
【Go语言专栏】Go语言中的泛型编程探索
【4月更文挑战第30天】Go语言新引入的泛型编程支持使得代码更通用灵活。通过类型参数在函数和接口定义中实现泛型,如示例中的泛型函数`Swap`和泛型接口`Comparator`。泛型应用包括数据结构、算法实现、函数包装和错误处理,提升代码复用与维护性。这一特性扩展了Go语言在云计算、微服务、区块链等领域的应用潜力。
|
13天前
|
JSON 编解码 Go
Golang深入浅出之-HTTP客户端编程:使用net/http包发起请求
【4月更文挑战第25天】Go语言`net/http`包提供HTTP客户端和服务器功能,简化高性能网络应用开发。本文探讨如何发起HTTP请求,常见问题及解决策略。示例展示GET和POST请求的实现。注意响应体关闭、错误处理、内容类型设置、超时管理和并发控制。最佳实践包括重用`http.Client`,使用`context.Context`,处理JSON以及记录错误日志。通过实践这些技巧,提升HTTP编程技能。
24 1
|
13天前
|
中间件 Go API
Golang深入浅出之-Go语言标准库net/http:构建Web服务器
【4月更文挑战第25天】Go语言的`net/http`包是构建高性能Web服务器的核心,提供创建服务器和发起请求的功能。本文讨论了使用中的常见问题和解决方案,包括:使用第三方路由库改进路由设计、引入中间件处理通用逻辑、设置合适的超时和连接管理以防止资源泄露。通过基础服务器和中间件的代码示例,展示了如何有效运用`net/http`包。掌握这些最佳实践,有助于开发出高效、易维护的Web服务。
28 1
|
14天前
|
Go 开发者
Golang深入浅出之-HTTP客户端编程:使用net/http包发起请求
【4月更文挑战第24天】Go语言的`net/http`包在HTTP客户端编程中扮演重要角色,但使用时需注意几个常见问题:1) 检查HTTP状态码以确保请求成功;2) 记得关闭响应体以防止资源泄漏;3) 设置超时限制,避免长时间等待;4) 根据需求处理重定向。理解这些细节能提升HTTP客户端编程的效率和质量。
18 1
|
16天前
|
SQL 关系型数据库 MySQL
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
|
17天前
|
网络协议 Linux Go
Go语言TCP Socket编程(下)
Go语言TCP Socket编程
|
存储 Web App开发 监控
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
我们以前使用过的对hbase和hdfs进行健康检查,及剩余hdfs容量告警,简单易用 1.针对hadoop2的脚本: #/bin/bashbin=`dirname $0`bin=`cd $bin;pwd`STATE_OK=...
1016 0

热门文章

最新文章