0 前言
我自己使用 Go 语言进行项目开发已经有一年多的时间,不过一直很少去了解 Go 语言相关的底层设计的一些东西。最近花时间去看了 net/http 的底层设计的一些东西,这篇文章就是把我觉得 net/http 最有价值、最需要了解的东西拿出来分享。
1 从使用 net/http 的使用方式出发
使用 net/http 启动一个服务非常简单:
package main import ( "io" "log" "net/http" ) func main() { // Hello world, the web server helloHandler := func(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "Hello, world!\n") } http.HandleFunc("/", helloHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
上面这段代码有三个核心。
定义处理器函数
helloHandler := func(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "Hello, world!\n") }
这里定义了一个匿名函数 helloHandler,它接受两个参数:
- w http.ResponseWriter: 这是一个用于构造 HTTP 响应的接口对象。我们可以使用它来向客户端发送数据。
- req *http.Request: 这是一个包含 HTTP 请求所有信息的结构体。
io.WriteString 方法用来向 http.ResponseWriter 写入字符串 "Hello, world!\n",客户端接收 http.ResponseWriter 中的内容。
注册处理器
http.HandleFunc("/", helloHandler)
http.HandleFunc 用来注册处理器函数。第一个参数是路径模式,这里我们指定为根路径 /,这意味着对于所有发往服务器根路径的请求都将由 helloHandler 处理。
启动服务器
log.Fatal(http.ListenAndServe(":8080", nil))
http.ListenAndServe 方法用来启动 HTTP 服务器,监听并服务于指定的网络地址。这里我们让服务器监听本地机器上的 8080 端口。函数的第二个参数是处理器,当该参数为 nil 时,默认使用 http.DefaultServeMux。
2 http.ListenAndServe
我们当然选择从最关键的启动服务 http.ListenAndServe 入手,看看启动服务这个过程具体是做了什么。
- 监听地址:当 http.ListenAndServe 启动后,它会在指定的地址上创建一个监听套接字(socket),等待客户端的连接请求。
- 接受连接:每当有一个新的连接请求到达时,ListenAndServe 会接受这个连接,并为每个连接创建一个新的 goroutine。(一个连接一个 goroutine)
- 读取请求:在每个新创建的 goroutine 中,服务器会读取客户端发送过来的 HTTP 请求。请求中包含了客户端想要访问的 URL、请求方法(如 GET、POST 等)以及其他相关信息。(每个连接可能有多个 HTTP 请求)
- 请求分发:一旦请求被完全读取,服务器会将请求传递给多路复用器进行分发。(多路复用器可以基于请求的 URL 路径来决定哪个处理器应该处理这个请求)
- 请求处理:多路复用器查找与请求 URL 匹配的处理器,并调用这个处理器来处理请求。
- 发送响应:处理函数完成请求处理后,向客户端发送响应。响应可以包括状态码、响应头和响应体等部分。
上面流程就是 http.ListenAndServe 具体在做的事情。我刻意回避了一些底层的关键模块名如多路复用器 http.ServeMux,原因是希望你们对基本流程有所了解,然后再深入细节。
3 http.ServeMux 多路复用器
前面我们了解了 http.ListenAndServe 的具体流程,这里面涉及到非常重要的一点就是,请求的分发和处理,不同的路由使用不同的处理器进行处理。
在我们实际使用 net/http 进行开发的时候,什么样的路由如何进行对应的处理,这是我们自己决定和编写的,这是我们在进行 web 开发中非常重要的一步。因此,下面我们来了解 net/http 是如何对我们的路由和处理进行管理的。
http.ServeMux 的内部维护了一个路由表,它存储了所有注册的处理器。路由表是由一组键值对组成的,其中键是 URL 路径模式,值是对应的处理器。当一个 HTTP 请求到达时,ServeMux 会使用其内部的路由表来查找与请求 URL 匹配的处理器。
注册处理器主要有两种方式:
-
使用 http.HandleFunc:
http.HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
-
使用 http.ServeMux.Handle:
func (mux *ServeMux) Handle(pattern string, handler Handler)
这个方法允许你将任意实现了 http.Handler 接口的对象注册到一个具体的 ServeMux 实例上。而 http.Handler 接口对象需要实现的函数是 func(http.ResponseWriter, *http.Request)
这个函数会将给定的处理函数注册到默认的 http.ServeMux 上。pattern 是一个 URL 路径模式,handler 是一个处理函数。
可以看到,无论是 处理函数 还是 处理器,其核心都是函数 func(http.ResponseWriter, *http.Request)。
所以:
- 如果你有一个处理函数 func(http.ResponseWriter, *http.Request),可以直接使用 http.HandleFunc 进行注册。
- 同样,你也可以把处理函数 func(http.ResponseWriter, *http.Request) 包装进 http.Handler,然后使用 func (mux *ServeMux) Handle(pattern string, handler Handler) 进行注册。
4 总结
以上即为我关于 Go net/http 的简单分享,实际简单使用 net/http 时,就是简单两步:
- 准备路由和处理函数 func(ResponseWriter, *Request),进行注册
- 调用 http.ListenAndServe 完成服务的启动
谢谢大家。
文章评论