go-zero api源码阅读

380 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

demo例子

var configFile = flag.String("f", "etc/user-api.yaml", "the config file")

func main() {
   flag.Parse()

   var c config.Config
   conf.MustLoad(*configFile, &c)

   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()
}

MustLoad加载配置文件

conf.MustLoad(*configFile, &c)会根据配置文件的类型(json、yaml、toml)选择对应的解析方式,把配置文件内容映射到结构体c上,代码相对简单。

MustNewServer创建一个Http服务

server := rest.MustNewServer(c.RestConf) 返回一个server,定义如下:

// A Server is a http server.
Server struct {
   ngin   *engine    // 引擎,包含了注册的路由、中间件等信息
   router httpx.Router   // 是一个httpx.Router,路由树
}

函数中有这样一段代码c.SetUp(),c是RestConf配置,setup目的是根据配置去初始化日志或者启动一下软件的代理,如Setup函数里的:

  1. logx.SetUp(sc.Log)根据配置对日志logx进行初始化。
  2. prometheus.StartAgent(sc.Prometheus)根据配置去启动一个协程开启普罗米修斯监控的代理。
  3. trace.StartAgent(sc.Telemetry)根据配置去启动开启日志追踪的代理。

RegisterHandlers注册路由

func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
   server.AddRoutes(
      []rest.Route{
         {
            Method:  http.MethodPost,
            Path:    "/user/login",
            Handler: loginHandler(serverCtx),
         },
      },
   )
}

把开发者写的路由存在分组路由featuredRoutes中,并添加到server的引擎里

// github.com\zeromicro\go-zero@v1.4.3\rest\types.go
// 分组路由
featuredRoutes struct {
        timeout   time.Duration
        priority  bool
        jwt       jwtSetting
        signature signatureSetting
        routes    []Route
        maxBytes  int64
}

// AddRoutes add given routes into the Server.
func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {
   r := featuredRoutes{
      routes: rs,
   }
   for _, opt := range opts {
      opt(&r)
   }
   s.ngin.addRoutes(r)
}

Start启动服务

server.Start()中主要把分组路由featuredRoutes绑定到server中的路由树httpx.Router上。

重点关注bindRoutes

func (ng *engine) bindRoutes(router httpx.Router) error {
   metrics := ng.createMetrics()

   for _, fr := range ng.routes {   // 遍历每一个分组
      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 {
	verifier, err := ng.signatureVerifier(fr.signature)
	if err != nil {
		return err
	}

	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 {
	chn := ng.chain
	if chn == nil {
		chn = chain.New(
                        // 构造各种中间件
			handler.TracingHandler(ng.conf.Name, route.Path),
			ng.getLogHandler(),
			handler.PrometheusHandler(route.Path),
			handler.MaxConns(ng.conf.MaxConns),
			handler.BreakerHandler(route.Method, route.Path, metrics),
			handler.SheddingHandler(ng.getShedder(fr.priority), metrics),
			handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)),
			handler.RecoverHandler,
			handler.MetricHandler(metrics),
			handler.MaxBytesHandler(ng.checkedMaxBytes(fr.maxBytes)),
			handler.GunzipHandler,
		)
	}

	chn = ng.appendAuthHandler(fr, chn, verifier)

	for _, middleware := range ng.middlewares {
		chn = chn.Append(convertMiddleware(middleware))  // 添加开发者自己设置的中间件
	}
	handle := chn.ThenFunc(route.Handler)

	return router.Handle(route.Method, route.Path, handle)
}

三个函数实现了把server的engine引擎中的开发者注册的路由绑定到engine.router路由树中。 bindRoute 中的ng.chain是一个中间件处理对象,生成各种中间件会保存在chain中,最后会生成http.handler对象。

总结

go-zero中api启动流程重点关注rest.Server这个结构体中的两个变量router和engine,开发者写的路由先注册到engine中,最后再绑定到router路由树上,最后在start函数中,用go原生的http去启动服务。