概述

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

源码

流程图

go-zero rest 流程图.png

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

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

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

源码走读

启动 api 服务:

 1func main() {  
 2    ...
 3    server := rest.MustNewServer(c.RestConf)  
 4    defer server.Stop()  
 5  
 6    ctx := svc.NewServiceContext(c)  
 7    handler.RegisterHandlers(server, ctx)  
 8  
 9    fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)  
10    server.Start()  
11}

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

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

按顺序介绍。

创建服务端 server

 1func MustNewServer(c RestConf, opts ...RunOption) *Server {  
 2    // NewServer 创建 server
 3    server, err := NewServer(c, opts...)  
 4    if err != nil {  
 5       logx.Must(err)  
 6    }  
 7  
 8    return server  
 9}
10
11func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
12	// c.SetUp 启动 Prometheus,tracing, profiling 等服务
13    if err := c.SetUp(); err != nil {  
14       return nil, err  
15    }  
16  
17    server := &Server{  
18       ngin:   newEngine(c),  
19       router: router.NewRouter(),  
20    }  
21  
22    ...
23    return server, nil  
24}

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

engine 主要结构如下:

 1type engine struct {  
 2    // server 的配置
 3    conf   RestConf  
 4    routes []featuredRoutes  // 业务路由
 5    // 调用链
 6    chain                chain.Chain 
 7    // 中间件 
 8    middlewares          []Middleware  
 9    ...
10}
11
12func newEngine(c RestConf) *engine {  
13    svr := &engine{  
14       conf:    c,  
15       timeout: time.Duration(c.Timeout) * time.Millisecond,  
16    }
17    ...
18}

router 结构如下:

 1func NewRouter() httpx.Router {  
 2    return &patRouter{  
 3       trees: make(map[string]*search.Tree),  
 4    }  
 5}
 6
 7type Router interface {  
 8    http.Handler  
 9    Handle(method, path string, handler http.Handler) error  
10    SetNotFoundHandler(handler http.Handler)  
11    SetNotAllowedHandler(handler http.Handler)  
12}

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

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

注册路由 handler

 1func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {  
 2    // server.AddRoutes 注册路由 handler
 3    server.AddRoutes(  
 4       []rest.Route{  
 5          {  
 6             Method:  http.MethodGet,  
 7             Path:    "/ping",  
 8             Handler: pingHandler(serverCtx),  
 9          },  
10       },  
11    )
12}
13
14func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {  
15    // 自定义的业务路由将被封装到 featuredRoutes 对象
16    r := featuredRoutes{  
17       routes: rs,  
18    }  
19    for _, opt := range opts {  
20       opt(&r)  
21    }  
22    
23    // 将 featuredRoutes 添加到 Server.engine
24    s.ngin.addRoutes(r)  
25}
26
27func (ng *engine) addRoutes(r featuredRoutes) {  
28    ...
29    // 实际是将路由组添加到 engine.routes 中  
30    ng.routes = append(ng.routes, r) 
31}

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

启动 server

 1func (s *Server) Start() {  
 2    // 调用 Server.engine.start 启动服务端 server
 3    handleError(s.ngin.start(s.router))  
 4}
 5
 6func (ng *engine) start(router httpx.Router, opts ...StartOption) error {     // engine.bindRoutes 绑定路由到 router
 7    if err := ng.bindRoutes(router); err != nil {  
 8       return err  
 9    }  
10
11    ...
12    return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,  
13       ng.conf.KeyFile, router, opts...)  
14}
15
16func (ng *engine) bindRoutes(router httpx.Router) error {  
17    // engine.routes 
18    for _, fr := range ng.routes {  
19       // 绑定 rest.featuredRoutes 
20       if err := ng.bindFeaturedRoutes(router, fr, metrics); err != nil { 
21          return err  
22       }  
23    }  
24  
25    return nil  
26}
27
28func (ng *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {  
29    ...
30    for _, route := range fr.routes {  
31       if err := ng.bindRoute(fr, router, metrics, route, verifier); err != nil {  
32          return err  
33       }  
34    }  
35  
36    return nil  
37}
38
39func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,  
40    route Route, verifier func(chain.Chain) chain.Chain) error {  
41    // engine.chain,初始化为 nil
42    chn := ng.chain  
43    if chn == nil {  
44       // engine.buildChainWithNativeMiddlewares 注册自带中间件到 engine.chain
45       chn = ng.buildChainWithNativeMiddlewares(fr, route, metrics)  
46    }  
47  
48    // 添加 AuthHandler 到 engine.chain 中
49    chn = ng.appendAuthHandler(fr, chn, verifier)  
50  
51    // 将自定义中间件注册到 engine.chain
52    for _, middleware := range ng.middlewares {  
53       chn = chn.Append(convertMiddleware(middleware))  
54    }  
55    
56    // engine.chain.ThenFunc 将 handler 串联成 handler
57    handle := chn.ThenFunc(route.Handler)  
58  
59    return router.Handle(route.Method, route.Path, handle)  
60}

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

 1func (ng *engine) buildChainWithNativeMiddlewares(fr featuredRoutes, route Route,  
 2    metrics *stat.Metrics) chain.Chain {  
 3    chn := chain.New()
 4    ...
 5    // MaxConns 用于并发控制
 6    if ng.conf.Middlewares.MaxConns {  
 7        chn = chn.Append(handler.MaxConnsHandler(ng.conf.MaxConns))  
 8    }  
 9    if ng.conf.Middlewares.Breaker {  
10        chn = chn.Append(handler.BreakerHandler(route.Method, route.Path, metrics))  
11    }
12    ...
13}

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

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

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

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

 1func (pr *patRouter) Handle(method, reqPath string, handler http.Handler) error {  
 2    ...
 3    
 4    tree, ok := pr.trees[method]  
 5    if ok {  
 6       return tree.Add(cleanPath, handler)  
 7    }  
 8  
 9    tree = search.NewTree()  
10    pr.trees[method] = tree  
11    return tree.Add(cleanPath, handler)  
12}

详细流程如下图:

go-zero rest 详细流程.png

小结

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

参考资料