本文已参与新[人创作礼]活动,一起开启掘金创作之路。
go-zero是一个当下很火,社区很活跃的微服务框架,虽然是微服务框架,但是其中的api server完全可以独立出来进行单体开发。官网给出的基准测试与gin不相上下。好好研究一下很有必要的。
go-zero api server 简单demo
因为要研究其构造原理,所以不用其goctl进行生成,直接用代码书写一个简单demo
func main() {
//文件配置
c := &Config{}
c.Host = "0.0.0.0"
c.Port = 9100
c.Log.Path = "./log"
//go zero的api server
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
//路由 处理器
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/api/myhandler",
Handler: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("myHandler handler \n"))
},
},
{
Method: http.MethodGet,
Path: "/api/zshandler",
Handler: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("zhang san handler \n"))
},
},
},
)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}
代码写好了,拿来浏览器直接测试一下,访问http://localhost:9100/api/myhandler 能通,接下来仔细看看代码, 看着跟goctl生成的不一样啊?的确不一样,这段代码少了一个功能注册的svc.ServiceContext,还少了一个struct形式的handler(logic),那些都是不必要的(对于研究demo来说),只是组织业务代码的技巧(用gin写业务代码也可按照这样的结构搞),省掉那些有助于去去除干扰。
go-zero的Server
server := rest.MustNewServer(c.RestConf) 这段代码返回一个server 但这个不是http.Server 而是go-zero的Server,其位置在github.com/zeromicro/go-zero/rest/server.go中 22行。其定义如下
Server struct {
ngin *engine // 引擎 主要存放路由的地方---------0--------
router httpx.Router //这是一个包含http.handler的接口,也是一个处理器,gozero中实现的httpx.Router相当于http.ServeMux
}
Server主要有两个属性,ngin(引擎)和router(路由)。从目前看到的,还无法理解Server是如何跑起来的,下面一步步分析,看看普通处理器是如何通过路由绑定到http.Server的。
engine中的路由注册
这个结构体在 github.com/zeromicro/go-zero/rest/engine.go中, 其结构如下,其属性含义都已注释:
//--------------0-----------
type engine struct {
conf RestConf //配置数据
routes []featuredRoutes //分组路由。------1--------
unauthorizedCallback handler.UnauthorizedCallback //授权未通过后进行的回调 jwt相关
unsignedCallback handler.UnsignedCallback //无sing的回调,jwt相关
middlewares []Middleware //中间件
shedder load.Shedder //自适应将在保护
priorityShedder load.Shedder //自适应将在保护
tlsConfig *tls.Config //https相关
}
//分组路由
//github.com/zeromicro/go-zero/rest/types.go line 33
//-------1-------------
featuredRoutes struct {
timeout time.Duration
priority bool
jwt jwtSetting
signature signatureSetting
routes []Route //路由 -------2------------
maxBytes int64
}
//github.com/zeromicro/go-zero/rest/types.go line 13
//---------2---------
Route struct {
Method string //GET POST PUT DELETE ...
Path string //url path
Handler http.HandlerFunc //业务处理器
}
这里边最重要的就是routes,其他的暂可忽略。注意这里的routes是真正存储所有路由的地方,而rest.Server.router只是一个路由处理的接口。后边会讲到。
下面看一下,用户写的处理器是如何注册到这个路由中的。
// github.com/zeromicro/go-zero/rest/server.go line 61
//rest.Server的方法
func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {
r := featuredRoutes{ //创建新的分组路由对象用于注册金engine里
routes: rs,
}
for _, opt := range opts { //注册路由的时候可以设置改组路由的一些配置项 比如jwt 超时,用户可以设置每组路由的超时!!
opt(&r)
}
s.ngin.addRoutes(r)
}
//github.com/zeromicro/go-zero/rest/engine.go line 50
func (ng *engine) addRoutes(r featuredRoutes) {
ng.routes = append(ng.routes, r)
}
这个方法 AddRoutes在上边写的demo里可以找到,正是这个函数把,把用户的路由及处理器添加到分组路由中的。路由注册到engine具体流程看下图。
server的启动
启动就要从demo中的server.Start()代码说起了。看一下具体实现代码。
//github.com/zeromicro/go-zero/rest/server.go line 79
func (s *Server) Start() {
handleError(s.ngin.start(s.router))
}
handlerErrors是对启动异常的处理。启动主要还是调用的engine中的start函数,注意这里的参数是一个gozero实现的处理器。该处理器也可用http.ServeMux进行实现。功能类似都是根据路由进行用户注册器调用。
//github.com/zeromicro/go-zero/rest/engine.go line 239
func (ng *engine) start(router httpx.Router) error { //注意启动时候需要一个router参数,这个接口也是一个http.handler类型,是一个处理器
if err := ng.bindRoutes(router); err != nil {
return err
}
if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 { //http启动
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router)
}
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile, //https启动
ng.conf.KeyFile, router, func(svr *http.Server) {
if ng.tlsConfig != nil {
svr.TLSConfig = ng.tlsConfig
}
})
}
internal.StartHttp(ng.conf.Host, ng.conf.Port, router) 这段代码最终调用http.Server进行启动,具体实现可以跟踪进去查看。
这段代码的重点是 ng.bindRoutes(router) 是把之前注册的用户启动器与router进行了关联,http.Server才最终能调用用户的启动器。
//github.com/zeromicro/go-zero/rest/engine.go line 110
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
}
// line 70
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
}
//line 85
func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
route Route, verifier func(chain alice.Chain) alice.Chain) error {
chain := alice.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,
)
chain = ng.appendAuthHandler(fr, chain, verifier)
for _, middleware := range ng.middlewares { //客户注册的中间件
chain = chain.Append(convertMiddleware(middleware))
}
handle := chain.ThenFunc(route.Handler)
return router.Handle(route.Method, route.Path, handle) //最终绑定到了engine.router处理器里。
}
这三个函数实现了把engine中注册的用户处理器绑定到engine.router处理器中。而新生成的处理器最终带进http.Server中。
bindRoute 中的alice是一个中间件处理对象,处理的最后会生成一个http.handler类型的对象。
总结
整个gozero api server代码条例还是比较清晰,gozero的rest.Server中的engine和router是关键,engine负责注册用户编写的路由及处理器,同时启动的时候还负责加载各种中间件。router是gozero实现的一个类似http.ServeMux的处理器,其实现的代码有点复杂,以后有机会在出专门博文进行梳理,这里也完全可以用http.ServeMux来实现httpx.Router接口。有了router后最后通过http.server进行绑定tcp。
这里边还有个中间件知识点,后边会专门出个博文进行梳理。go-zero中的中间件功能强大,如果你一直在用gin其实可以直接把这些中间件拿来用的。做一个gin版本gozero。