go-zero:handler.ServeHTTP(rw, req)

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 中间件,最后执行业务函数