开启掘金成长之旅!这是我参与「掘金日新计划 · 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函数里的:
logx.SetUp(sc.Log)根据配置对日志logx进行初始化。prometheus.StartAgent(sc.Prometheus)根据配置去启动一个协程开启普罗米修斯监控的代理。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去启动服务。