Skip to main content

Go: 标准库 net/http

📅 2026-03-11 ✏️ 2026-03-18 Go 标准库 CS GO
No related notes

1 · 标准库 net/http#

HTTP 是请求-响应协议,自然有 ClientServer

Request/Response 是数据载体。

服务端:Server 监听连接,Handler 处理请求,通常使用ServeMux 按请求路径进行路由处理。

客户端:Client 提供 Get/Post 等http methods方法,真正发请求的是其持有的 RoundTripper(接口),默认实现是 Transport,负责 TCP 连接、连接池、HTTP/2、TLS 等。

服务端:
  Server → conn → Handler(通常是 ServeMux)

          Request → | → ResponseWriter

客户端:
  Client → Do(Request) → RoundTripper(接口)

                        Transport(默认实现)

                     TCP/TLS Conn → Network

                         Response
go doc net/http.Client
go doc net/http.Server

go doc net/http.Request
go doc net/http.Response

# 接口,定义了服务端处理http请求的行为
go doc net/http.Handler
# 实现
go doc net/http.ServeMux
# 定义服务端如何构建http响应,实现为包中不可导出的response类型
go doc net/http.ResponseWriter

# 接口,定义了客户端进行一次http请求的行为
go doc net/http.RoundTripper
# 实现
go doc net/http.Transport

1.1 · 服务端

ListenAndServe -> Handler(使用ServeMux进行路由) -> ResponseWriter

// 监听 addr 产生的 tcp 连接,使用 handler 进行处理(为 nil 使用 DefaultServeMux )
// 本质:新建了一个 Server,
// 监听 tcp 端口,for 循环获取连接conn,将server包装成serverHandler,内部使用 handler 进行处理
func ListenAndServe(addr string, handler Handler) error {}

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

var DefaultServeMux = &defaultServeMux // ServeMux 类型,实现了 Handler
type ServeMux struct{}

// 从请求 r 中 findHandler 获取到 Handler,进行处理
// 可以看出:ServeMux 是 Handler 的装饰器
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

// 给定 pattern,注册 handler 到 DefaultServeMux
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {}

// 非导出类型 [response] 实现了接口,在每次conn时被创建,持有请求、连接、缓存writer等
type ResponseWriter interface {
	// 写响应体
	// 如果`响应未开始`,也就是没有调用[WriteHeader],以默认成功状态`200 OK`开始
	Write([]byte) (int, error)

	// 拿到响应头:配置待发送的响应头
	Header() Header
	// 开始响应(显式给出状态码,提交响应头[Header])
	WriteHeader(statusCode int)

	// 分别对应http协议的:状态行、响应头、响应体
	// ? 为什么不直接返回Response:因为服务端响应经常是`边算边写`
}

type Server struct {
	Addr    string
	Handler Handler // handler to invoke, http.DefaultServeMux if nil
}

总结:Server负责接入连接,将请求交给Handler;Handler负责处理请求,并写响应。

1.2 · 客户端

Client -> Do -> RoundTripper -> Transport

var DefaultClient = &Client{}

type Client struct {
	Transport RoundTripper
}

// 发送请求,获得响应
func (c *Client) Do(req *Request) (*Response, error)

// 获取RoundTripper:客户端自有的,或者全局的DefaultTransport(类型Transport)
func (c *Client) transport() RoundTripper {}

// 调用 RoundTrip 方法发送请求
type RoundTripper interface {
	RoundTrip(*Request) (*Response, error)
}

// 默认RoundTripper的具体实现,执行请求动作
// 负责:连接建立、连接池、keep-alive、TLS、HTTP/2
type Transport struct{}

总结:Client 是请求编排层(调用语义),Transport 是网络执行层(传输语义),这两层边界在RoundTripper接口。 前者,如何使用和调度 Request;后者,如何执行这个 Request。

1.3 · Request, Response#

服务端:

  • ServeHTTP 的 Request 参数是如何构造的?Body 是谁提供的。
  • 如何写响应的,ResponseWriter 的实现?

客户端:

  • Do 的 Request 参数是如何构造的?Body 是谁提供的。
  • 如何处理 Response 的。

1.4 · 超时、取消

timeout, context

客户端:

  • Client.Timeout 整个请求总超时
  • Transport 有哪些超时配置?
  • Request.WithContext 超时/取消;更细粒度(单个请求层面),更推荐

Dial TCP -> TLS 握手 -> 发请求头/体 -> 等响应头 -> 读响应体

  • DialContext 建 TCP 连接
  • TLSHandshakeTimeout TLS 握手
  • IdleConnTimeout 连接池里,空闲连接能活多久
  • ResponseHeaderTimeout 发完请求后等响应头
  • ExpectContinueTimeout 等 100 Continue
	tr := &http.Transport{
		DialContext:           (&net.Dialer{Timeout: 3 * time.Second}).DialContext,
		TLSHandshakeTimeout:   5 * time.Second,
		ResponseHeaderTimeout: 5 * time.Second,
		// 连接池
		IdleConnTimeout:     90 * time.Second,
		MaxIdleConns:        100,
		MaxIdleConnsPerHost: 10,
	}

服务端:

建立连接 -> 读请求头 -> 读请求体 -> 业务处理 -> 写响应 -> 空闲等待下一次请求

  • ReadHeaderTimeout 读请求头
  • ReadTimeout 读请求头 + 请求体
  • WriteTimeout 写响应
  • IdleTimeout keep-alive 空闲期
type Server struct {
	ReadHeaderTimeout time.Duration
	ReadTimeout       time.Duration
	WriteTimeout      time.Duration
	IdleTimeout       time.Duration

	// ... other fields elided ...
}
客户端 HTTP/1.x

连接池空闲
<------ Transport.IdleConnTimeout ------>

开始请求
   |
   |---- net.Dialer.Timeout ----|              TCP connect
   |                            |
   |------ Transport.TLSHandshakeTimeout -----|   HTTPS 才有
   |                                          |
   | 发请求头
   |---- Transport.ExpectContinueTimeout ---->|   仅 Expect: 100-continue
   |                                          |   等 100 Continue
   | 发请求体
   |---------------- Transport.ResponseHeaderTimeout ---------------->| 等响应头
   |                                                                  |
   |------------------------------ 读响应体 ---------------------------->|
   |
   |<-------------------- http.Client.Timeout / req.Context -------------------->|

服务端 HTTP/1.x

连接建立
   |
   |---- TLS handshake ----|   HTTPS 才有
   |
   |---- Server.ReadHeaderTimeout ---->| 读请求头
   |-------------------------------- Server.ReadTimeout ------------------------->|
   |                                   读请求头 + 读请求体
   |
   |            handler / 业务处理
   |
   |--------------------- Server.WriteTimeout --------------------->| 写响应头+体
   |
请求结束,连接进入 keep-alive
   |-------- Server.IdleTimeout -------->| 等下一个请求到来

1.5 · 中间件

服务端:

func(http.Handler) http.Handler

客户端:

RoundTripper

2 · httptest#

httptest分两层:NewRequest+Recorder做进程内handler测试,Server做真实loopback端到端测试,兼顾易用与贴近真实。

  • NewRequest: 模拟”请求进来”
  • NewRecorder: 实现了http.ResponseWriter接口,模拟”响应写出”
  • NewServer: 接受http.Handler接口的实现为参数,启动真实httpserver,用于测试必须经过网络栈的东西
// 构造请求
// go doc net/http/httptest.NewRequest
func NewRequest(method, target string, body io.Reader) *http.Request {}

// go doc net/http/httptest.NewRecorder
func NewRecorder() *ResponseRecorder {}

type ResponseRecorder struct{}

// go doc net/http/httptest.NewServer
func NewServer(handler http.Handler) *Server {}

3 · httptrace#

https://go.dev/blog/http-tracing

为 HTTP Client 单次请求提供阶段性埋点 httptrace.ClientTrace 是一个方法集结构体,用户自定义相关阶段的方法

通过httptrace.WithClientTracehttptrace.ClientTrace挂到 Request.Context; 通过httptrace.ContextClientTrace从ctx中取出httptrace.ClientTrace,调用相关tracing方法

4 · 看 http 包设计#

优点:少量抽象,覆盖很大问题空间

  • 统计抽象非常小:服务端只有HandlerResponseWriter(更贴近io.Writer)
  • 函数适配接口:HandleFunc使得函数实现了Handler接口
  • 组合优于集成:中间件就是func(Handler) Handler函数组合
  • 接口边界按职责切分:客户端负责高层策略(重定向、cookies等),RoundTripper负责底层机制(一次请求,一次响应);
  • 零值可用、极短默认路径:DefaultClient等默认值,http.Get等包级别函数

设计问题:

  • ResponseWriter 状态机式接口, 一旦开始写,很多状态就冻结了;
  • ResponseWriter 的高级能力,散落在额外接口,写中间件的时候,就需要类型断言进行透传,不然会丢失
  • Request 同时表示入站和出站,请求模型偏重。