02对最简单的 api gateway 项目并进行源码分析¶
目录
备注
go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。本文讲解 web 框架的源码分析,下一节讲解 rpc 框架的源码分析。示例是使用最简单的一个功能:传过来一个 url 地址,转化为一个短网址。
生成 api gateway 项目¶
编写 gateway.api 文件:
type (
shortenReq {
Url string `form:"url"`
}
shortenResp {
Shorten string `json:"shorten"`
}
)
service shorturl-api {
@server(
handler: ShortenHandler
)
get /shorten(shortenReq) returns(shortenResp)
}
使用 goctl 命令生成项目:
$ goctl api go -api gateway.api -dir api
Done.
$ tree
├── etc
│ └── shorturl-api.yaml
├── go.mod
├── go.sum
├── internal
│ ├── config
│ │ └── config.go
│ ├── handler
│ │ ├── routes.go
│ │ └── shortenhandler.go
│ ├── logic
│ │ └── shortenlogic.go
│ ├── svc
│ │ └── servicecontext.go
│ └── types
│ └── types.go
├── main.api
└── shorturl.go
7 directories, 11 files
入口文件分析¶
入口文件 分析:
var configFile = flag.String("f", "etc/shorturl-api.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
// 把配置文件载入到 config.Config 结构体中
conf.MustLoad(*configFile, &c)
// 把c封装到 svc.ServiceContext 结构体中
// ctx 用作上下文通信载体
ctx := svc.NewServiceContext(c)
// 封装出一个 *rest.Server 结构体 [注1]
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
// 根据 .api 文件自动生成
// 往 server 中增加路由 [注2]
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
// 启动 http server [注3]
server.Start()
}
小结:
1. 由配置文件生成配置结构体: config.Config
2. 生成上下文: svc.ServiceContext
3. 生成 *rest.Server结构体、注册路由并启动服务
前两步很简单,下面详细说下第3步
主要包括:
1. server := rest.MustNewServer(c.RestConf)
2. handler.RegisterHandlers(server, ctx)
3. server.Start()
1. 生成 rest.Server 结构体¶
相关结构体:
type Server struct {
ngin *engine
opts runOptions
}
type engine struct {
conf RestConf
routes []featuredRoutes
unauthorizedCallback handler.UnauthorizedCallback
unsignedCallback handler.UnsignedCallback
middlewares []Middleware
shedder load.Shedder
priorityShedder load.Shedder
}
type RunOption func(*Server)
type runOptions struct {
start func(*engine) error
}
rest.Server 初使化-整体:
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
// c.SetUp()做一些启动前的准备工作[注: 这部分可暂忽略,以后专门再看]
// 现在主要知道它是做:
// 1. logx初使化
// 2. prometheus 初使化
// 3. 开发模式等设置
if err := c.SetUp(); err != nil {
return nil, err
}
// Server初使化 [注: 待细化]
server := &Server{
ngin: newEngine(c),
opts: runOptions{
start: func(srv *engine) error {
return srv.Start()
},
},
}
// 选项模式: 扩展相关功能[注: 这部分可暂忽略,以后专门再看]
for _, opt := range opts {
opt(server)
}
return server, nil
}
rest.engine 初使化-细化:
func newEngine(c RestConf) *engine {
// 把 RestConf 类型数据封装到 engine 类型中
// 本质: 初使化 engine 的"conf"字段
srv := &engine{
conf: c,
}
// 这块主要是"负荷"相关设置 [注: 这部分可暂忽略,以后专门再看]
// 本质: 初使化 engine 的"shedder"和"priorityShedder"字段
if c.CpuThreshold > 0 {
srv.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
srv.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
(c.CpuThreshold + topCpuUsage) >> 1))
}
return srv
}
小结:
本质是: 生成 *rest.Server 类型变量
我们忽略了:
1. 启动前的准备工作
2. 选项模式: 扩展相关功能
3. load 模块中有关限流、容错相关内容
这些后面文章再细说,现在不是重点
2. 注册路由¶
调用 server.AddRoutes 函数注册路由:
handler.RegisterHandlers(server, ctx)
=>
func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
engine.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/shorten",
Handler: ShortenHandler(serverCtx),
},
},
)
}
说明:
此部分代码是 goctl 命令自动生成的
在 .api 文件中配置成什么样,这儿就会自动生成相关代码
本质: 调用 engine.AddRoutes 命令增加路由
调用 rest.Server 方法 AddRoutes
func (e *Server) AddRoutes(rs []Route, opts ...RouteOption) {
// 真实的路由存储结构
r := featuredRoutes{
routes: rs,
}
// 选项模式 [注: 这部分可暂忽略,以后专门再看]
for _, opt := range opts {
opt(&r)
}
// 本质是调用 Server 下 ngin 字段的 AddRoutes 方法[待细化]
e.ngin.AddRoutes(r)
}
e.ngin.AddRoutes 细化:
// 本质: 初使化&更新 engine 的 "routes" 字段
func (s *engine) AddRoutes(r featuredRoutes) {
s.routes = append(s.routes, r)
}
现在再看 engine 结构:
type engine struct {
conf RestConf
routes []featuredRoutes
unauthorizedCallback handler.UnauthorizedCallback
unsignedCallback handler.UnsignedCallback
middlewares []Middleware
shedder load.Shedder
priorityShedder load.Shedder
}
字段分析:
conf: 配置信息(host, port, cert...)
routes: 路由信息列表
shedder, priorityShedder: "负荷"相关暂忽略
现在只剩下3个字段没有初使化了: [注: 这部分可暂忽略,以后专门再看]
unauthorizedCallback
unsignedCallback
middlewares
小结:
路由注册之后得到 *rest.Server 结构体, 现在关注的字段主要是 conf 和 routes
conf 信息简单,routes 结构如下图所示
3. server 启动¶
server 启动:
server.Start()
=>
func (s *Server) Start() {
handleError(s.opts.start(s.ngin))
}
说明:
handleError 函数使 server 优雅关闭 [注: 这部分可暂忽略,以后专门再看]
本质:
server.Start()
=>
s.opts.start(s.ngin)
=>
server.ngin.Start()
=>
server.ngin.StartWithRouter(router.NewRouter())
分为2部分:
1. router.NewRouter(): 生成 httpx.Router 接口
2. s.StartWithRouter: 启动 http 服务
3.1 生成httpx.Router接口¶
生成的 httpx.Router 接口分析:
type Router interface {
http.Handler
Handle(method, path string, handler http.Handler) error
}
分析:
1. 首先有 http.Handler 接口, 用于启动 http server
2. 其次是增加了 Handle 方法
真实初使化:
func NewRouter() httpx.Router {
return &patRouter{
trees: make(map[string]*search.Tree),
}
}
type patRouter struct {
trees map[string]*search.Tree
}
分析:
1. 用hashmap树结构做路由, 应该是把路由信息最后存储在这儿, 方便快速查询
2. 前面 httpx.Router 接口的 Handle 方法可能是用于赋值patRouter
3. 关于 search.Tree 相关代码暂不深入
3.2 启动 http 服务¶
s.StartWithRouter 服务启动:
func (s *engine) StartWithRouter(router httpx.Router) error {
if err := s.bindRoutes(router); err != nil {
return err
}
if len(s.conf.CertFile) == 0 && len(s.conf.KeyFile) == 0 {
return internal.StartHttp(s.conf.Host, s.conf.Port, router)
}
return internal.StartHttps(s.conf.Host, s.conf.Port, s.conf.CertFile, s.conf.KeyFile, router)
}
分析:
1. s.bindRoutes(router): 本质是赋值 router 变量(期间应该会用到 Handler 方法)
2. internal.StartHttp: 启动 http server
3. internal.StartHttps: 启动 https server[注: 忽略, 暂只需要关注 http]
internal.StartHttp¶
// 这是 http,https 类似
func StartHttp(host string, port int, handler http.Handler) error {
return start(host, port, handler,
func(srv *http.Server) error {
return srv.ListenAndServe()
}
)
}
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error) (err error) {
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", host, port),
Handler: handler,
}
return run(server)
}
说明:
本质是执行的 http.Server.ListenAndServe()
其中http.Server类型的字段:
Addr: host:port
Handler: 前面传递过来的 httpx.Router(类型 router.patRouter)
所以现在的关键是 router.patRouter 如何生成了
备注
到这儿我们就知道,服务启动后,当一个请求过来时,最后真正处理的是 patRouter 的 ServeHTTP 方法。这块后面细看,先看看这个 patRouter 是如何进行赋值的。
s.bindRoutes(router)¶
一级循环生成 router:bindRoutes:
func (s *engine) bindRoutes(router httpx.Router) error {
// [一级 for]:
for _, fr := range s.routes {
if err := s.bindFeaturedRoutes(router, fr, metrics); err != nil {
return err
}
}
return nil
}
二级循环生成 router:bindFeaturedRoutes
func (s *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
verifier, err := s.signatureVerifier(fr.signature)
// [二级 for]:
for _, route := range fr.routes {
if err := s.bindRoute(fr, router, metrics, route, verifier); err != nil {
return err
}
}
return nil
}
说明:
通过 2 级 for 循环把 engine.routes 转化为 httpx.Router
最后的本质是对每一个 rest.Route 执行 bindRoute
func (s *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,
s.getLogHandler(),
handler.PrometheusHandler(route.Path),
handler.MaxConns(s.conf.MaxConns),
handler.BreakerHandler(route.Method, route.Path, metrics),
handler.SheddingHandler(s.getShedder(fr.priority), metrics),
handler.TimeoutHandler(time.Duration(s.conf.Timeout)*time.Millisecond),
handler.RecoverHandler,
handler.MetricHandler(metrics),
handler.MaxBytesHandler(s.conf.MaxBytes),
handler.GunzipHandler,
)
chain = s.appendAuthHandler(fr, chain, verifier)
for _, middleware := range s.middlewares {
chain = chain.Append(convertMiddleware(middleware))
}
handle := chain.ThenFunc(route.Handler)
return router.Handle(route.Method, route.Path, handle)
}
备注
这儿用到了 alice,简单介绍下: alice is Painless middleware chaining for Go. In short, it transforms: Middleware1(Middleware2(Middleware3(App)))
to alice.New(Middleware1, Middleware2, Middleware3).Then(App)
说明:
这块内容比较多,也比较重要,未来会专门介绍这部分
现在需要知道的是这里面默认实现了好多中间件[先忽略,未来专门看]
除此之外,剩下的本质就是:
router.Handle(route.Method, route.Path, handle)
func (pr *patRouter) Handle(method, reqPath string, handler http.Handler) error {
tree, ok := pr.trees[method]
if ok {
return tree.Add(cleanPath, handler)
}
tree = search.NewTree()
pr.trees[method] = tree
return tree.Add(cleanPath, handler)
}
type patRouter struct {
trees map[string]*search.Tree
}
小结:
当一个请求过来时