Go语言net/http包的用法(附带实例)
谈到网络,必然有人会想到 OSI 七层模型,在 OSI 七层模型中,Socket 协议位于传输层(第 4 层),而 HTTP 协议位于应用层(第 7 层),该协议是基于 TCP/IP 协议进行通信的。
Go 语言的 net/http 包为实现 HTTP 客户端和服务器提供了丰富的 API,此包中常用的功能如下图所示。

图 1 net/http 包的常用功能
Go 语言的 net/http 包提供了 http.HandleFunc 方法,它可以根据不同的访问路径执行不同的处理逻辑,示例代码如下:

图 2 在浏览器中看到的效果
上述两行代码分别做了什么?先来看看 http.HandleFunc:
当 http.ListenAndServe(":9080",nil) 传入的 handler 为 nil 时,表示使用默认的 DefaultServeMux. HandleFunc。默认的 DefaultServeMux.HandleFunc 在处理请求时,会先校验 URL 路径,然后将对应的 URL 和 handler 存入映射中。映射的键为 URL,其值就是具体的业务处理逻辑 handler(handler 是通过 mux.Handle 转换而来的 HandlerFunc)。
再来看一下 http.ListenAndServe,它用于启动和监听 HTTP 服务器,它的关键代码如下:
1) 使用 &Server{Addr: addr, Handler: handler} 初始化 http.Server 结构体。
http.Server 结构体封装了 HTTP 服务器的配置和行为。在 http.ListenAndServe 中,先创建一个默认的 http.Server 实例,然后将处理器和传入的地址参数分别设置为实例的 handler 和 addr 字段。
注意,此处理器指的就是 http.ListenAndServe(":9080",handler) 需要传入的 handler。如果第二个参数 handler 传入的是 nil,那么 Go语言会使用默认的 HTTP 多路复用器 DefaultServeMux 来处理请求。
2) 创建监听器。使用 ln, err := net.Listen("tcp", addr) 创建指定端口号的监听器 listener。
net.Listen 函数负责创建监听器,它接收传入的 TCP 连接,传入的地址参数(包括IP和端口)用于创建监听器。如果地址中未指定 IP,则监听器将监听所有可用的网络接口。
调用 srv.Serve(ln)。(*Server).Serve 方法会启动 HTTP 服务器,并使用之前创建的监听器接收客户端连接。服务器将接收到的每个连接传递给处理器,以处理 HTTP 请求。这个方法包含一个 for 循环,所以它会一直运行,不停地接收外部的 TCP 请求,直到监听器关闭或出现错误为止。对于接收到的每个请求,再使用 c := srv.newConn(rw) 新生成一个连接。
3) 使用 go c.serve(ctx) 开启协程,并发地处理每个连接请求。
在 c.serve(ctx) 中,最终会调用 serverHandler{c.server}.ServeHTTP(w, w.req) 方法,它用于处理客户端发送的 HTTP 请求并生成响应的关键代码。在 ServeHTTP 方法中,会根据客户端的请求调用相应的处理函数(如路由分发、处理器函数等)来生成 HTTP 响应。然后,通过 http.ResponseWriter 函数将响应发送回客户端。
http.HandleFunc 方法还可以用作多路复用器,它可以将不同的处理器函数注册到默认的多路复用器(DefaultServeMux)上,然后根据不同的请求 URI 调用对应的处理器函数。
http.HandleFunc 方法的执行流程如下图所示:

图 3 http.HandleFunc方法的执行流程
相关伪代码如下:
下面是一个简单的 HTTP 客户端使用示例:
这个示例展示的仅仅是一个简单的 HTTP 客户端,我们还可以根据实际需求对其进行修改,以支持不同的请求方法(如 POST、PUT 等),并处理 JSON 响应和重定向等操作。
除了可以自定义请求头,还可以使用著名的第三方包 fake-useragent 生成随机的 User-Agent 字符串来模拟不同的浏览器和设备,这样在每次运行程序时,都会使用不同的 User-Agent 发送请求。伪代码如下:
要使用Client结构体,首先要创建一个实例 client := &http.Client{},然后发出请求 resp, err := Client.Get("http://XXX.com"),此后的使用方式就与使用 http.Get 类似了。
除此之外,net/http 包还提供了诸多功能,包括管理 CookieJar、操作 Cookie、配置 Transport、设置代理以及连接控制等。由于篇幅限制,本节不再逐一介绍。
Go 语言的 net/http 包为实现 HTTP 客户端和服务器提供了丰富的 API,此包中常用的功能如下图所示。

图 1 net/http 包的常用功能
Go语言HTTP服务器
Go 语言很好地封装了 HTTP 协议,在下面的示例中,main 函数只用两行代码就实现了 HTTP 服务器的监听和处理功能。Go 语言的 net/http 包提供了 http.HandleFunc 方法,它可以根据不同的访问路径执行不同的处理逻辑,示例代码如下:
func main() { http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer,"hello http!") }) http.ListenAndServe(":9080",nil) // 实际是http.ListenAndServe(":9080",handler),当使用默认的HTTP多路复用器DefaultServeMux时,传入nil即可 }在浏览器中输入相应的 IP 地址、端口和 URI,可以看到如下图所示的效果:

图 2 在浏览器中看到的效果
上述两行代码分别做了什么?先来看看 http.HandleFunc:
http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer,"hello http!") })其中,"/hello" 是需要监听的访问路径,匿名函数 func(ResponseWriter, *Request) 作为 HandleFunc 的参数监听到以 "/hello" 开头的请求,然后给出对应的处理逻辑。
当 http.ListenAndServe(":9080",nil) 传入的 handler 为 nil 时,表示使用默认的 DefaultServeMux. HandleFunc。默认的 DefaultServeMux.HandleFunc 在处理请求时,会先校验 URL 路径,然后将对应的 URL 和 handler 存入映射中。映射的键为 URL,其值就是具体的业务处理逻辑 handler(handler 是通过 mux.Handle 转换而来的 HandlerFunc)。
再来看一下 http.ListenAndServe,它用于启动和监听 HTTP 服务器,它的关键代码如下:
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } func (srv *Server) ListenAndServe() error { …… ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(ln) } func (srv *Server) Serve(l net.Listener) error { // ... for { rw, e := l.Accept() if e != nil { // ... continue } tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) go c.serve(ctx) } }代码说明如下:
1) 使用 &Server{Addr: addr, Handler: handler} 初始化 http.Server 结构体。
http.Server 结构体封装了 HTTP 服务器的配置和行为。在 http.ListenAndServe 中,先创建一个默认的 http.Server 实例,然后将处理器和传入的地址参数分别设置为实例的 handler 和 addr 字段。
注意,此处理器指的就是 http.ListenAndServe(":9080",handler) 需要传入的 handler。如果第二个参数 handler 传入的是 nil,那么 Go语言会使用默认的 HTTP 多路复用器 DefaultServeMux 来处理请求。
2) 创建监听器。使用 ln, err := net.Listen("tcp", addr) 创建指定端口号的监听器 listener。
net.Listen 函数负责创建监听器,它接收传入的 TCP 连接,传入的地址参数(包括IP和端口)用于创建监听器。如果地址中未指定 IP,则监听器将监听所有可用的网络接口。
调用 srv.Serve(ln)。(*Server).Serve 方法会启动 HTTP 服务器,并使用之前创建的监听器接收客户端连接。服务器将接收到的每个连接传递给处理器,以处理 HTTP 请求。这个方法包含一个 for 循环,所以它会一直运行,不停地接收外部的 TCP 请求,直到监听器关闭或出现错误为止。对于接收到的每个请求,再使用 c := srv.newConn(rw) 新生成一个连接。
3) 使用 go c.serve(ctx) 开启协程,并发地处理每个连接请求。
在 c.serve(ctx) 中,最终会调用 serverHandler{c.server}.ServeHTTP(w, w.req) 方法,它用于处理客户端发送的 HTTP 请求并生成响应的关键代码。在 ServeHTTP 方法中,会根据客户端的请求调用相应的处理函数(如路由分发、处理器函数等)来生成 HTTP 响应。然后,通过 http.ResponseWriter 函数将响应发送回客户端。
http.HandleFunc 方法还可以用作多路复用器,它可以将不同的处理器函数注册到默认的多路复用器(DefaultServeMux)上,然后根据不同的请求 URI 调用对应的处理器函数。
http.HandleFunc 方法的执行流程如下图所示:

图 3 http.HandleFunc方法的执行流程
相关伪代码如下:
http.HandleFunc("/image", func(writer http.ResponseWriter, request *http.Request) { //...监听URI为/image的请求,并进行相关业务逻辑处理 }) http.HandleFunc("/video", func(writer http.ResponseWriter, request *http.Request) { //...监听URI为/video的请求,并进行相关业务逻辑处理 }) http.HandleFunc("/upload", func(writer http.ResponseWriter, request *http.Request) { //...监听URI为/upload的请求,并进行相关业务逻辑处理 })
Go语言HTTP客户端
处理来自 HTTP 客户端的请求也很容易,使用 net/http 包中的 http.Client 类型创建一个 HTTP 客户端,http.Client 类型提供了一些方法来发送 HTTP 请求和处理响应。下面是一个简单的 HTTP 客户端使用示例:
import ( "fmt" "io/ioutil" "net/http" ) func main() { // 创建一个 http.Client client := &http.Client{} // 创建一个 HTTP 请求 req, err := http.NewRequest("GET", "https://api.example.com/data", nil) if err != nil { fmt.Println("Error creating request:", err) return } // 添加请求头(可选) req.Header.Add("Authorization", "Bearer your-access-token") req.Header.Add("Accept", "application/json") // 使用客户端发送请求 resp, err := client.Do(req) if err != nil { fmt.Println("Error sending request:", err) return } defer resp.Body.Close() // 读取响应体 body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response body:", err) return } // 打印响应状态和响应体 fmt.Println("Status:", resp.Status) fmt.Println("Response Body:", string(body)) }在上面的示例中,我们首先创建了一个 http.Client 类型,然后使用 http.NewRequest 函数创建了一个新的 HTTP 请求。在为请求添加了一些请求头后,使用 client.Do(req) 方法发送请求。Do 方法返回一个 http.Response 类型的值,其中包含了响应状态码、头信息和响应体等信息。我们从 resp.Body 中读取响应体,然后打印响应状态和响应体内容。
这个示例展示的仅仅是一个简单的 HTTP 客户端,我们还可以根据实际需求对其进行修改,以支持不同的请求方法(如 POST、PUT 等),并处理 JSON 响应和重定向等操作。
除了可以自定义请求头,还可以使用著名的第三方包 fake-useragent 生成随机的 User-Agent 字符串来模拟不同的浏览器和设备,这样在每次运行程序时,都会使用不同的 User-Agent 发送请求。伪代码如下:
//可以定义User-Agent等与header有关的信息,本质都是调用client.Do(req) //还可以使用著名的第三方包fake-useragent来生成随机的 User-Agent import "github.com/eddycjy/fake-useragent" req, err := http.NewRequest("GET", "https://api.example.com/data", nil) randomUserAgent := fake_useragent.Random() req.Header.Set("User-Agent", randomUserAgent) resp, err: = http.DefaultClient.Do(req) 在进行 HTTP 请求操作时,常用的还有 Get、Post 和 PostForm 等函数。相关伪代码如下: resp, err := http.Get("http://XXX/") resp, err := http.Post("http://XXX/upload", "image/jpeg", &buf) resp, err := http.PostForm("http://XXX/form",url.Values{"key": {"Value"}, "id": {"a1b2c3d4"}})使用完以后需要关闭响应 resp 的 body,代码为 defer resp.Body.Close()。
net/http包的补充说明
我们已经了解到,诸如 http.Get 等的 HTTP 请求最终都会调用 client.Do(req) 。而 http.Client 结构体在 net/http 包中的作用并非仅限于发起 HTTP 请求,它还提供了丰富的配置选项,允许开发者控制 HTTP 请求的超时时间、重定向策略、代理设置、TLS 配置等。要使用Client结构体,首先要创建一个实例 client := &http.Client{},然后发出请求 resp, err := Client.Get("http://XXX.com"),此后的使用方式就与使用 http.Get 类似了。
除此之外,net/http 包还提供了诸多功能,包括管理 CookieJar、操作 Cookie、配置 Transport、设置代理以及连接控制等。由于篇幅限制,本节不再逐一介绍。