func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
handler := sh.srv.Handler
handler 变量的静态类型是接口 http.Handler,Go 的接口变量底层是二元结构 (itab, data):
itab(方法表):记录「当前底层类型」+ 该类型实现的 ServeHTTP 函数内存地址
data(数据指针):底层真实对象的地址(也就是 &patRouter{} 的内存地址)
服务启动时发生的绑定
go-zero 启动时执行:
s.srv.Handler = s.engine.router
// s.engine.router 类型:*router.patRouter
赋值时 Go 自动生成接口 itab:
itab 标记底层类型:router.patRouter itab 记录这个类型对应的方法:(patRouter).ServeHTTP 的函数地址
data 存 patRouter 实例指针
所以局部变量 handler 从 sh.srv.Handler 拷贝过来时,完整复制了这套 itab+data,永久绑定了 patRouter 的方法。
执行 handler.ServeHTTP(rw, req) 时的完整寻址流程
handler.ServeHTTP(rw, req)
CPU 执行步骤:
取出接口变量 handler 里的 itab;
在 itab 里找到 ServeHTTP 对应的函数入口地址(就是 (patRouter).ServeHTTP); 取出 data(patRouter 实例指针),作为方法的接收者 pr; 调用函数:(patRouter).ServeHTTP(data, rw, req)
完整顺序:
TCP 解析完 Request → 进入 serverHandler.ServeHTTP
handler := sh.srv.Handler → 取出顶层 patRouter,此时变量 handler 只绑定 patRouter
handler.ServeHTTP(rw, req) → 跳转 (*patRouter).ServeHTTP(进入 go-zero 路由内部)
patRouter 内部匹配路径 /v1/user/1,从路由树取出包装好的中间件链:timeoutHandler 包裹 IP 中间件,再包裹业务 handler
执行 timeoutHandler.ServeHTTP,再执行 IP 中间件,最后执行业务函数