HTTP 源码解析

HTTP 源码解析

💡 原文中文,约7700字,阅读约需19分钟。
📝

内容提要

本文解析 Go 标准库 net/http 源码:HandleFunc 将路由注册到 DefaultServeMux,用 map 保存路径与处理函数的映射,以斜杠结尾的路由存入 es 切片以优化匹配;ListenAndServe 创建 Server 并监听端口,Serve 循环接收连接,每个连接启一个协程处理;请求经 ServeMux 匹配路由后调用对应 Handler。

🔎

延伸解读

路由匹配的优化设计

ServeMux 使用 map 存储精确路径,同时用 es 切片保存以斜杠结尾的路由。匹配时先查 map,再遍历 es 进行前缀匹配。这种设计避免了 /path 和 /path/ 的重复处理,但 es 的线性遍历在路由数量多时可能成为性能瓶颈,这也是许多第三方框架优化路由算法的原因之一。

并发处理与错误恢复

Serve 方法在循环中接受连接,每个连接启动一个独立的 goroutine 处理。对于临时性网络错误,采用指数退避重试,初始延迟 5 毫秒,每次翻倍,上限 1 秒。这种机制提高了服务在瞬时错误下的稳定性,但需注意 Temporary 方法已被标记为废弃。

默认多路复用器的角色

当 ListenAndServe 的 handler 参数为 nil 时,服务器会使用 DefaultServeMux 作为默认处理器。这意味着通过 http.HandleFunc 注册的路由都绑定到全局的 DefaultServeMux 上。在大型应用中,直接使用默认多路复用器可能导致路由冲突,建议显式创建自定义的 ServeMux。

❓

Q&A

Go 标准库 net/http 中 HandleFunc 是如何注册路由的?

HandleFunc 将 pattern 和 handler 注册到 DefaultServeMux。它调用 DefaultServeMux.HandleFunc,后者将 handler 转换为 HandlerFunc 并调用 mux.Handle。mux.Handle 将 pattern 和 handler 存入 muxEntry,并保存到 ServeMux 的 map 中,key 为 pattern。如果 pattern 以斜杠结尾,还会追加到 es 切片。

ServeMux 中的 es 切片有什么作用?

es 切片保存以斜杠结尾的路由模式对应的 muxEntry。它的作用是在请求 URL 去掉末尾斜杠后进行匹配,避免重复处理类似 /path 和 /path/ 的 URL,从而提高匹配效率,防止处理器被重复调用。

ListenAndServe 启动 HTTP 服务的过程是怎样的?

ListenAndServe 创建 Server 对象,然后调用 Server.ListenAndServe。后者检查服务是否关闭,若 Addr 为空则使用 ":http",接着通过 net.Listen 监听 TCP 端口,最后调用 srv.Serve 进入循环。

Server.Serve 如何处理每个到来的连接?

Serve 循环调用 l.Accept 接收连接。若出错,根据错误类型决定重试或返回。对于每个连接,创建新连接对象 c,设置状态为 StateNew,然后启动一个 goroutine 执行 c.serve 来处理该连接。

当请求到达时,ServeMux 如何匹配路由并调用处理器?

ServeMux.ServeHTTP 调用 mux.Handler 查找匹配的处理器。Handler 方法通过 match 函数查找:先在 map 中精确匹配,若未找到则遍历 es 切片进行前缀匹配。找到后调用对应处理器的 ServeHTTP 方法。

为什么 Go 标准库的 HTTP 服务会催生许多第三方框架?

标准库的 ServeMux 使用 map 保存路由映射,对于稍微复杂的 URL 匹配能力有限,无法很好地支持动态路由等高级功能。因此出现了 gin、echo 等框架,它们改写了路由匹配算法以提供更强大的路由功能。

🏷️

标签

➡️

继续阅读