go-zero rest 源码学习笔记


概述

go-zero 基于 net/http 标准库实现了一套 rest web 框架。在使用 goctl 快速开发的同时,也需要了解 go-zero 内部做了什么。本文结合 go-zero rest学习其中的源码,力图做到知其所以然。

源码

流程图

image

在阅读源码之前,先看下流程图有个印象。从流程图大致可以看出来:

  • go-zero 会创建路由组,其中按顺序注册了几类 handler(中间件),最后在 business handler 处理业务逻辑。

大致有个印象后开始源码走读。

源码走读

启动 api 服务:

func main() {  
    ...
    server := rest.MustNewServer(c.RestConf)  
    defer server.Stop()  
  
    ctx := svc.NewServiceContext(c)  
    handler.RegisterHandlers(server, ctx)  
  
    fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)  
    server.Start()  
}

启动服务主要做了三件事:

  • 创建服务端 server
  • 注册 handler 到 server
  • 启动服务端 server

按顺序介绍。

创建服务端 server

func MustNewServer(c RestConf, opts ...RunOption) *Server {  
    // NewServer 创建 server
    server, err := NewServer(c, opts...)  
    if err != nil {  
       logx.Must(err)  
    }  
  
    return server  
}

func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
	// c.SetUp 启动 Prometheus,tracing, profiling 等服务
    if err := c.SetUp(); err != nil {  
       return nil, err  
    }  
  
    server := &Server{  
       ngin:   newEngine(c),  
       router: router.NewRouter(),  
    }  
  
    ...
    return server, nil  
}

创建 server 实际创建的是 server 的 engine 和 router。

engine 主要结构如下:

type engine struct {  
    // server 的配置
    conf   RestConf  
    routes []featuredRoutes  // 业务路由
    // 调用链
    chain                chain.Chain 
    // 中间件 
    middlewares          []Middleware  
    ...
}

func newEngine(c RestConf) *engine {  
    svr := &engine{  
       conf:    c,  
       timeout: time.Duration(c.Timeout) * time.Millisecond,  
    }
    ...
}

router 结构如下:

func NewRouter() httpx.Router {  
    return &patRouter{  
       trees: make(map[string]*search.Tree),  
    }  
}

type Router interface {  
    http.Handler  
    Handle(method, path string, handler http.Handler) error  
    SetNotFoundHandler(handler http.Handler)  
    SetNotAllowedHandler(handler http.Handler)  
}

patRouter 包含路由信息,其实现了 Router 接口。

创建了 server 后还需要注册路由 handler 到 server,这样服务端才能根据路由找到对应的 handler 处理。

注册路由 handler

func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {  
    // server.AddRoutes 注册路由 handler
    server.AddRoutes(  
       []rest.Route{  
          {  
             Method:  http.MethodGet,  
             Path:    "/ping",  
             Handler: pingHandler(serverCtx),  
          },  
       },  
    )
}

func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {  
    // 自定义的业务路由将被封装到 featuredRoutes 对象
    r := featuredRoutes{  
       routes: rs,  
    }  
    for _, opt := range opts {  
       opt(&r)  
    }  
    
    // 将 featuredRoutes 添加到 Server.engine
    s.ngin.addRoutes(r)  
}

func (ng *engine) addRoutes(r featuredRoutes) {  
    ...
    // 实际是将路由组添加到 engine.routes 中  
    ng.routes = append(ng.routes, r) 
}

业务路由注册完,接下来将进入启动 server,这是需要关注的重点。

启动 server

func (s *Server) Start() {  
    // 调用 Server.engine.start 启动服务端 server
    handleError(s.ngin.start(s.router))  
}

func (ng *engine) start(router httpx.Router, opts ...StartOption) error {     // engine.bindRoutes 绑定路由到 router
    if err := ng.bindRoutes(router); err != nil {  
       return err  
    }  

    ...
    return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,  
       ng.conf.KeyFile, router, opts...)  
}

func (ng *engine) bindRoutes(router httpx.Router) error {  
    // engine.routes 
    for _, fr := range ng.routes {  
       // 绑定 rest.featuredRoutes 
       if err := ng.bindFeaturedRoutes(router, fr, metrics); err != nil { 
          return err  
       }  
    }  
  
    return nil  
}

func (ng *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {  
    ...
    for _, route := range fr.routes {  
       if err := ng.bindRoute(fr, router, metrics, route, verifier); err != nil {  
          return err  
       }  
    }  
  
    return nil  
}

func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,  
    route Route, verifier func(chain.Chain) chain.Chain) error {  
    // engine.chain,初始化为 nil
    chn := ng.chain  
    if chn == nil {  
       // engine.buildChainWithNativeMiddlewares 注册自带中间件到 engine.chain
       chn = ng.buildChainWithNativeMiddlewares(fr, route, metrics)  
    }  
  
    // 添加 AuthHandler 到 engine.chain 中
    chn = ng.appendAuthHandler(fr, chn, verifier)  
  
    // 将自定义中间件注册到 engine.chain
    for _, middleware := range ng.middlewares {  
       chn = chn.Append(convertMiddleware(middleware))  
    }  
    
    // engine.chain.ThenFunc 将 handler 串联成 handler
    handle := chn.ThenFunc(route.Handler)  
  
    return router.Handle(route.Method, route.Path, handle)  
}

启动 server 的重点在 engine.bindRoute
其中,engine.buildChainWithNativeMiddlewares 注册 go-zero 自带中间件:

func (ng *engine) buildChainWithNativeMiddlewares(fr featuredRoutes, route Route,  
    metrics *stat.Metrics) chain.Chain {  
    chn := chain.New()
    ...
    // MaxConns 用于并发控制
    if ng.conf.Middlewares.MaxConns {  
        chn = chn.Append(handler.MaxConnsHandler(ng.conf.MaxConns))  
    }  
    if ng.conf.Middlewares.Breaker {  
        chn = chn.Append(handler.BreakerHandler(route.Method, route.Path, metrics))  
    }
    ...
}

类似的,自定义中间件通过 chn.Append(convertMiddleware(middleware)) 注册到 engine.chain 中。

接着调用 chain.ThenFunc 串联中间件成 handler

func (c chain) ThenFunc(fn http.HandlerFunc) http.Handler {  
    ...
    return c.Then(fn)  
}

func (c chain) Then(h http.Handler) http.Handler {  
    if h == nil {  
       h = http.DefaultServeMux  
    }  
  
    // 这段代码很有意思,它将所有中间件按顺序串联起来组成一个 handler
    // 调用这个 handler 处理时会经过后续一系列的中间件,最终到业务 handler 处理
    // 具体可参考 https://github.com/zeromicro/go-zero/blob/master/rest/chain/chain.go#L81
    for i := range c.middlewares {  
       h = c.middlewares[len(c.middlewares)-1-i](h)  
    }  
  
    return h  
}

最后将该 handler 和路由信息注册到 router 中,后续服务端根据请求在 router 中查找对应的 handler 处理。

func (pr *patRouter) Handle(method, reqPath string, handler http.Handler) error {  
    ...
    
    tree, ok := pr.trees[method]  
    if ok {  
       return tree.Add(cleanPath, handler)  
    }  
  
    tree = search.NewTree()  
    pr.trees[method] = tree  
    return tree.Add(cleanPath, handler)  
}

详细流程如下图:

image

小结

本文介绍了 go-zero rest 的源码是怎么处理请求的。从源码也可以看出每个请求背后是一系列中间件 handler 在处理,并且 server 启动了 Prometheus,Trace 等服务负责监控,链路追踪等,使得开发微服务时只需要关注业务逻辑即可,非常方便。

参考资料


posted @ 2025-12-26 22:19  胡云Troy  阅读(2)  评论(0)    收藏  举报