找回密码
 立即注册
首页 业界区 业界 go-zero rest 源码学习笔记

go-zero rest 源码学习笔记

人弧 3 小时前
概述

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

流程图

1.png

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

  • go-zero 会创建路由组,其中按顺序注册了几类 handler(中间件),最后在 business handler 处理业务逻辑。
大致有个印象后开始源码走读。
源码走读

启动 api 服务:
  1. func 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
  1. func 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. func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
  11.         // c.SetUp 启动 Prometheus,tracing, profiling 等服务
  12.     if err := c.SetUp(); err != nil {  
  13.        return nil, err  
  14.     }  
  15.   
  16.     server := &Server{  
  17.        ngin:   newEngine(c),  
  18.        router: router.NewRouter(),  
  19.     }  
  20.   
  21.     ...
  22.     return server, nil  
  23. }
复制代码
创建 server 实际创建的是 server 的 engine 和 router。
engine 主要结构如下:
  1. type engine struct {  
  2.     // server 的配置
  3.     conf   RestConf  
  4.     routes []featuredRoutes  // 业务路由
  5.     // 调用链
  6.     chain                chain.Chain
  7.     // 中间件
  8.     middlewares          []Middleware  
  9.     ...
  10. }
  11. func newEngine(c RestConf) *engine {  
  12.     svr := &engine{  
  13.        conf:    c,  
  14.        timeout: time.Duration(c.Timeout) * time.Millisecond,  
  15.     }
  16.     ...
  17. }
复制代码
router 结构如下:
  1. func NewRouter() httpx.Router {  
  2.     return &patRouter{  
  3.        trees: make(map[string]*search.Tree),  
  4.     }  
  5. }
  6. type Router interface {  
  7.     http.Handler  
  8.     Handle(method, path string, handler http.Handler) error  
  9.     SetNotFoundHandler(handler http.Handler)  
  10.     SetNotAllowedHandler(handler http.Handler)  
  11. }
复制代码
patRouter 包含路由信息,其实现了 Router 接口。
创建了 server 后还需要注册路由 handler 到 server,这样服务端才能根据路由找到对应的 handler 处理。
注册路由 handler
  1. func 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. func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {  
  14.     // 自定义的业务路由将被封装到 featuredRoutes 对象
  15.     r := featuredRoutes{  
  16.        routes: rs,  
  17.     }  
  18.     for _, opt := range opts {  
  19.        opt(&r)  
  20.     }  
  21.    
  22.     // 将 featuredRoutes 添加到 Server.engine
  23.     s.ngin.addRoutes(r)  
  24. }
  25. func (ng *engine) addRoutes(r featuredRoutes) {  
  26.     ...
  27.     // 实际是将路由组添加到 engine.routes 中  
  28.     ng.routes = append(ng.routes, r)
  29. }
复制代码
业务路由注册完,接下来将进入启动 server,这是需要关注的重点。
启动 server
  1. func (s *Server) Start() {  
  2.     // 调用 Server.engine.start 启动服务端 server
  3.     handleError(s.ngin.start(s.router))  
  4. }
  5. func (ng *engine) start(router httpx.Router, opts ...StartOption) error {     // engine.bindRoutes 绑定路由到 router
  6.     if err := ng.bindRoutes(router); err != nil {  
  7.        return err  
  8.     }  
  9.     ...
  10.     return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,  
  11.        ng.conf.KeyFile, router, opts...)  
  12. }
  13. func (ng *engine) bindRoutes(router httpx.Router) error {  
  14.     // engine.routes
  15.     for _, fr := range ng.routes {  
  16.        // 绑定 rest.featuredRoutes
  17.        if err := ng.bindFeaturedRoutes(router, fr, metrics); err != nil {
  18.           return err  
  19.        }  
  20.     }  
  21.   
  22.     return nil  
  23. }
  24. func (ng *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {  
  25.     ...
  26.     for _, route := range fr.routes {  
  27.        if err := ng.bindRoute(fr, router, metrics, route, verifier); err != nil {  
  28.           return err  
  29.        }  
  30.     }  
  31.   
  32.     return nil  
  33. }
  34. func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,  
  35.     route Route, verifier func(chain.Chain) chain.Chain) error {  
  36.     // engine.chain,初始化为 nil
  37.     chn := ng.chain  
  38.     if chn == nil {  
  39.        // engine.buildChainWithNativeMiddlewares 注册自带中间件到 engine.chain
  40.        chn = ng.buildChainWithNativeMiddlewares(fr, route, metrics)  
  41.     }  
  42.   
  43.     // 添加 AuthHandler 到 engine.chain 中
  44.     chn = ng.appendAuthHandler(fr, chn, verifier)  
  45.   
  46.     // 将自定义中间件注册到 engine.chain
  47.     for _, middleware := range ng.middlewares {  
  48.        chn = chn.Append(convertMiddleware(middleware))  
  49.     }  
  50.    
  51.     // engine.chain.ThenFunc 将 handler 串联成 handler
  52.     handle := chn.ThenFunc(route.Handler)  
  53.   
  54.     return router.Handle(route.Method, route.Path, handle)  
  55. }
复制代码
启动 server 的重点在 engine.bindRoute。
其中,engine.buildChainWithNativeMiddlewares 注册 go-zero 自带中间件:
  1. func (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:
  1. func (c chain) ThenFunc(fn http.HandlerFunc) http.Handler {  
  2.     ...
  3.     return c.Then(fn)  
  4. }
  5. func (c chain) Then(h http.Handler) http.Handler {  
  6.     if h == nil {  
  7.        h = http.DefaultServeMux  
  8.     }  
  9.   
  10.     // 这段代码很有意思,它将所有中间件按顺序串联起来组成一个 handler
  11.     // 调用这个 handler 处理时会经过后续一系列的中间件,最终到业务 handler 处理
  12.     // 具体可参考 https://github.com/zeromicro/go-zero/blob/master/rest/chain/chain.go#L81
  13.     for i := range c.middlewares {  
  14.        h = c.middlewares[len(c.middlewares)-1-i](h)  
  15.     }  
  16.   
  17.     return h  
  18. }
复制代码
最后将该 handler 和路由信息注册到 router 中,后续服务端根据请求在 router 中查找对应的 handler 处理。
  1. func (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. }
复制代码
详细流程如下图:
2.png

小结

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


  • go-zero rest
  • Go 责任链模式

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册