一. 问题概述
了解go-zero底层也是基于net/http标准库实现http的,是怎么实现的,怎么触发到net/http的 go-zero也是基于前缀树进行路由注册的,是怎么注册的,注册过程中有哪些注意点 go-zero中支持中间件, 在服务启动时,中间件,路由是如何保存的,接收请求时是如何执行的 先看一下基础go-zero服务示例
package main
import (
"fmt"
"github.com/zeromicro/go-zero/rest/chain"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/service"
"github.com/zeromicro/go-zero/rest"
"log"
"net/http"
)
func main ( ) {
srv, err := rest. NewServer ( rest. RestConf{
Port: 8080 ,
ServiceConf: service. ServiceConf{
Log: logx. LogConf{ Path: "./logs" } ,
} ,
} )
if err != nil {
log. Fatal ( err)
}
defer srv. Stop ( )
srv. Use ( func ( next http. HandlerFunc) http. HandlerFunc {
return func ( w http. ResponseWriter, r * http. Request) {
fmt. Println ( "before request" )
next ( w, r)
fmt. Println ( "after request" )
}
} )
srv. AddRoutes ( [ ] rest. Route{
{
Method: http. MethodGet,
Path: "/user/info" ,
Handler: userInfo,
} ,
} )
srv. AddRoutes (
[ ] rest. Route{
{
Method: http. MethodPost,
Path: "/afsdfa" ,
Handler: thirdPayment. ThirdPaymentWxPayCallbackHandler ( serverCtx) ,
} ,
} ,
rest. WithJwt ( serverCtx. Config. JwtAuth. AccessSecret) ,
rest. WithPrefix ( "/afas/v1" ) ,
)
srv. AddRoutes (
rest. WithMiddlewares (
[ ] rest. Middleware{
func ( next http. HandlerFunc) http. HandlerFunc {
return func ( w http. ResponseWriter, r * http. Request) {
}
} ,
func ( next http. HandlerFunc) http. HandlerFunc {
return func ( w http. ResponseWriter, r * http. Request) {
}
} ,
rest. ToMiddleware ( func ( next http. Handler) http. Handler {
return next
} ) ,
} ,
rest. Route{
Method: http. MethodGet,
Path: "/user/info2" ,
Handler: userInfo,
} ,
rest. Route{
Method: http. MethodGet,
Path: "/user/info3" ,
Handler: userInfo,
} ) ,
rest. WithPrefix ( "/afas/v2" ) ,
)
srv. Start ( )
}
type User struct {
Name string `json:"name"`
Addr string `json:"addr"`
Level int `json:"level"`
}
func userInfo ( w http. ResponseWriter, r * http. Request) {
var req struct {
UserId int64 `form:"user_id"`
}
if err := httpx. Parse ( r, & req) ; err != nil {
httpx. Error ( w, err)
return
}
users := map [ int64 ] * User{
1 : & User{ "go-zero" , "shanghai" , 1 } ,
2 : & User{ "go-queue" , "beijing" , 2 } ,
}
httpx. WriteJson ( w, http. StatusOK, users[ req. UserId] )
}
二. 底层源码分析
涉及到的一些结构体简介
了解go-zero服务的启动与路由注册,首先要了解几个结构体比如: engine,patRouter,featuredRoutes,Route engine: 服务引擎,构建go-zero服务时首先要创建这个引擎
服务其中时会将服务的配置相关信息存储到engine的conf 属性中 在路由注册时会先将路由保存到engine的routes属性中,后续再通过这个属性获取所有路由构建前缀树 会将通过Server的Use()或rest下WithMiddleware()/WithMiddlewares()函数主动注册中间件存储到middlewares属性中 在后续处理时会获取middlewares属性中保存的中间件,engine的chain处理器链中 …
type engine struct {
conf RestConf
routes [ ] featuredRoutes
timeout time. Duration
unauthorizedCallback handler. UnauthorizedCallback
unsignedCallback handler. UnsignedCallback
chain chain. Chain
middlewares [ ] Middleware
shedder load. Shedder
priorityShedder load. Shedder
tlsConfig * tls. Config
}
patRouter: 通过NewServer()函数创建go-zero服务时内部会调用NewRouter()先初始化这个结构体变量,初始化内部的trees属性,这个trees属性就是多个以路由method为key的前缀树,patRouter是路由注册与请求执行的核心,该结构体实现了ServeHTTP()方法,会通过这个方法处理用户的请求
type patRouter struct {
trees map [ string ] * search. Tree
notFound http. Handler
notAllowed http. Handler
}
type Tree struct {
root * node
}
type node struct {
item any
children [ 2 ] map [ string ] * node
}
featuredRoutes
type featuredRoutes struct {
timeout time. Duration
priority bool
jwt jwtSetting
signature signatureSetting
routes [ ] Route
maxBytes int64
}
Route
type Route struct {
Method string
Path string
Handler http. HandlerFunc
}
初始化
在编写go-zero服务时,可以编写yaml,通过conf/MustLoad()读取yaml配置,通过rest/MustNewServer()创建服务,也可以直接封装rest.RestConf配置变量,调用rest/NewServer()创建服务(实际MustLoad()内部也会调用这个NewServer()),查看NewServer()
首先调用调用newEngine()创建Engine服务引擎 调用NewRouter()函数,初始化patRouter,初始化patRouter中的trees路由前缀树
func NewServer ( c RestConf, opts ... RunOption) ( * Server, error ) {
if err := c. SetUp ( ) ; err != nil {
return nil , err
}
server := & Server{
ngin: newEngine ( c) ,
router: router. NewRouter ( ) ,
}
opts = append ( [ ] RunOption{ WithNotFoundHandler ( nil ) } , opts... )
for _ , opt := range opts {
opt ( server)
}
return server, nil
}
中间件的预设置
有两种方式注册中间件:
通过Server的use方法注册全局中间件, 通过github.com\zeromicro\go-zero\rest\server.go中的WithMiddlewares/WithMiddleware()函数注册针对一组路由的中间件
Server.Use()注册全局中间件,很简单,会调用engine的use()方法,将中间件函数保存到engine引擎的中间件切片属性middlewares中
func ( s * Server) Use ( middleware Middleware) {
s. ngin. use ( middleware)
}
func ( ng * engine) use ( middleware Middleware) {
ng. middlewares = append ( ng. middlewares, middleware)
}
查看WithMiddlewares/WithMiddleware()函数针对一组路由注册中间件函数源码,最终会将中间件封装到每个路由的Handler属性中
func WithMiddleware ( middleware Middleware, rs ... Route) [ ] Route {
routes := make ( [ ] Route, len ( rs) )
for i := range rs {
route := rs[ i]
routes[ i] = Route{
Method: route. Method,
Path: route. Path,
Handler: middleware ( route. Handler) ,
}
}
return routes
}
另外rest下有一个WithChain()函数,直接将中间件添加到chain处理器链中(可以看为将中间件又封装了一层),如下,在创建Server时添加两个拦截器
server := MustNewServer ( RestConf{ } , rest. WithChain ( chain. New ( 中间件函数1 , 中间件函数2 ) ) )
路由注册与中间件的处理
在通过go-zero提供服务时,首先执行rest下的NewServer()函数,读取配置,创建服务端Server, 提供对外的接口时,需要将接口Method,接口函数,接口路径封装为Route结构体变量,调用Server的AddRoutes()方法将这个结构体变量添加到engine引擎的routes路由切片属性中
func ( s * Server) AddRoutes ( rs [ ] Route, opts ... RouteOption) {
r := featuredRoutes{
routes: rs,
}
for _ , opt := range opts {
opt ( & r)
}
s. ngin. addRoutes ( r)
}
func ( ng * engine) addRoutes ( r featuredRoutes) {
ng. routes = append ( ng. routes, r)
}
路由添加完成后,会调用Server的Start()方法启动服务,查看源码.会调用engine的start()方法–>调用bindRoutes()
func ( s * Server) Start ( ) {
handleError ( s. ngin. start ( s. router) )
}
func ( ng * engine) start ( router httpx. Router, opts ... StartOption) error {
if err := ng. bindRoutes ( router) ; err != nil {
return err
}
opts = append ( [ ] StartOption{ ng. withTimeout ( ) } , opts... )
if len ( ng. conf. CertFile) == 0 && len ( ng. conf. KeyFile) == 0 {
return internal. StartHttp ( ng. conf. Host, ng. conf. Port, router, opts... )
}
opts = append ( [ ] StartOption{
func ( svr * http. Server) {
if ng. tlsConfig != nil {
svr. TLSConfig = ng. tlsConfig
}
} ,
} , opts... )
return internal. StartHttps ( ng. conf. Host, ng. conf. Port, ng. conf. CertFile,
ng. conf. KeyFile, router, opts... )
}
查看engine的bindRoutes()方法,获取engine的routes属性中保存的每一个路由,遍历调用bindFeaturedRoutes()–>调用bindRoute()方法在该方法中重点完成了:
获取engine的chain属性,如果为空调用buildChainWithNativeMiddlewares()新建一个chain链,获取配置信息,根据配置判断添加中间件,比如判断是否配置了追踪请求的Trace中间件, 记录请求日志的Log中间件,收集请求指标数据Prometheus中间件,限制最大并发连接数的MaxConns中间件,实现熔断机制Breaker中间件 ,实现流量控制Shedding中间件,设置请求超时时间的Timeout中间件,恢复异常请求的Recover中间件…如果有配置则将这些中间件添加到chain中 获取engine的middlewares中间件,遍历添加到chain中,也就是我们通过Server的Use()方法,或rest下WithMiddleware()/WithMiddlewares()函数主动注册中间件,将这些中间件添加到chain中 调用chain的ThenFunc()方法,遍历所有中间件,调用中间件的Handle()方法,将中间件包装为Handler形成中间件Handler链,并将接口的处理Handler添加到了链条的末尾,在执行时匹配到handler后会基于这个链条向下调用到最终的接口处理函数 调用Router的Handle()方法,将路由注册到路由树上,也就是前缀树的构建
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 = ng. buildChainWithNativeMiddlewares ( fr, route, metrics)
}
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)
}
func ( ng * engine) buildChainWithNativeMiddlewares ( fr featuredRoutes, route Route,
metrics * stat. Metrics) chain. Chain {
chn := chain. New ( )
if ng. conf. Middlewares. Trace {
chn = chn. Append ( handler. TraceHandler ( ng. conf. Name,
route. Path,
handler. WithTraceIgnorePaths ( ng. conf. TraceIgnorePaths) ) )
}
if ng. conf. Middlewares. Log {
chn = chn. Append ( ng. getLogHandler ( ) )
}
if ng. conf. Middlewares. Prometheus {
chn = chn. Append ( handler. PrometheusHandler ( route. Path, route. Method) )
}
if ng. conf. Middlewares. MaxConns {
chn = chn. Append ( handler. MaxConnsHandler ( ng. conf. MaxConns) )
}
if ng. conf. Middlewares. Breaker {
chn = chn. Append ( handler. BreakerHandler ( route. Method, route. Path, metrics) )
}
if ng. conf. Middlewares. Shedding {
chn = chn. Append ( handler. SheddingHandler ( ng. getShedder ( fr. priority) , metrics) )
}
if ng. conf. Middlewares. Timeout {
chn = chn. Append ( handler. TimeoutHandler ( ng. checkedTimeout ( fr. timeout) ) )
}
if ng. conf. Middlewares. Recover {
chn = chn. Append ( handler. RecoverHandler)
}
if ng. conf. Middlewares. Metrics {
chn = chn. Append ( handler. MetricHandler ( metrics) )
}
if ng. conf. Middlewares. MaxBytes {
chn = chn. Append ( handler. MaxBytesHandler ( ng. checkedMaxBytes ( fr. maxBytes) ) )
}
if ng. conf. Middlewares. Gunzip {
chn = chn. Append ( handler. GunzipHandler)
}
return chn
}
func ( ng * engine) appendAuthHandler ( fr featuredRoutes, chn chain. Chain,
verifier func ( chain. Chain) chain. Chain) chain. Chain {
if fr. jwt. enabled {
if len ( fr. jwt. prevSecret) == 0 {
chn = chn. Append ( handler. Authorize ( fr. jwt. secret,
handler. WithUnauthorizedCallback ( ng. unauthorizedCallback) ) )
} else {
chn = chn. Append ( handler. Authorize ( fr. jwt. secret,
handler. WithPrevSecret ( fr. jwt. prevSecret) ,
handler. WithUnauthorizedCallback ( ng. unauthorizedCallback) ) )
}
}
return verifier ( chn)
}
func ( c chain) Then ( h http. Handler) http. Handler {
if h == nil {
h = http. DefaultServeMux
}
for i := range c. middlewares {
h = c. middlewares[ len ( c. middlewares) - 1 - i] ( h)
}
return h
}
查看patRouter的Handle()方法添加路由构建前缀树的源码,内部会
patRouter是在NewServer()函数创建服务Server内部通过NewRouter()初始化的,内部有个trees属性,保存了以Method为维度的多个前缀树 在Handle()方法中会根据当前路由的Method判断是否存在该类型的前缀树,如果存在则调用Tree的Add()方法添加,如果不存在,则先调用调用一个NewTree()新建一个前缀树,然后调用Tree的Add()方法添加 Tree的Add()方法中会调用一个add()函数,该函数中,会根据"/“截取路由path中的每一段,作为token标识判断当前前缀树中是否已经存在该节点,如果存在则递归调用根据”/"继续截取当当前节点作为子节点判断添加前缀树中, 如果不存在则调用newNode()函数新建node节点,.以当前路由截取到的token标识为key添加到前缀树中
func ( pr * patRouter) Handle ( method, reqPath string , handler http. Handler) error {
if ! validMethod ( method) {
return ErrInvalidMethod
}
if len ( reqPath) == 0 || reqPath[ 0 ] != '/' {
return ErrInvalidPath
}
cleanPath := path. Clean ( reqPath)
tree, ok := pr. trees[ method]
if ok {
return tree. Add ( cleanPath, handler)
}
tree = search. NewTree ( )
pr. trees[ method] = tree
return tree. Add ( cleanPath, handler)
}
func ( t * Tree) Add ( route string , item interface { } ) error {
if len ( route) == 0 || route[ 0 ] != slash {
return errNotFromRoot
}
if item == nil {
return errEmptyItem
}
err := add ( t. root, route[ 1 : ] , item)
switch err {
case errDupItem:
return duplicatedItem ( route)
case errDupSlash:
return duplicatedSlash ( route)
default :
return err
}
}
func add ( nd * node, route string , item interface { } ) error {
if len ( route) == 0 {
if nd. item != nil {
return errDupItem
}
nd. item = item
return nil
}
if route[ 0 ] == slash {
return errDupSlash
}
for i := range route {
if route[ i] != slash {
continue
}
token := route[ : i]
children := nd. getChildren ( token)
if child, ok := children[ token] ; ok {
if child != nil {
return add ( child, route[ i+ 1 : ] , item)
}
return errInvalidState
}
child := newNode ( nil )
children[ token] = child
return add ( child, route[ i+ 1 : ] , item)
}
children := nd. getChildren ( route)
if child, ok := children[ route] ; ok {
if child. item != nil {
return errDupItem
}
child. item = item
} else {
children[ route] = newNode ( item)
}
return nil
}
启动服务到触发net/http
在调用Start()方法启动服务时,内部会有一个判断:如果配置文件中没有证书文件和密钥文件,调用 internal 包中的 StartHttp()方法,如果有则调用StartHttps()启动一个 http 服务
func ( s * Server) Start ( ) {
handleError ( s. ngin. start ( s. router) )
}
func ( ng * engine) start ( router httpx. Router, opts ... StartOption) error {
if err := ng. bindRoutes ( router) ; err != nil {
return err
}
opts = append ( [ ] StartOption{ ng. withTimeout ( ) } , opts... )
if len ( ng. conf. CertFile) == 0 && len ( ng. conf. KeyFile) == 0 {
return internal. StartHttp ( ng. conf. Host, ng. conf. Port, router, opts... )
}
opts = append ( [ ] StartOption{
func ( svr * http. Server) {
if ng. tlsConfig != nil {
svr. TLSConfig = ng. tlsConfig
}
} ,
} , opts... )
return internal. StartHttps ( ng. conf. Host, ng. conf. Port, ng. conf. CertFile,
ng. conf. KeyFile, router, opts... )
}
以StartHttp()为例,查看该函数,内部调用了net/http下的ListenAndServe()
func StartHttp ( host string , port int , handler http. Handler, opts ... StartOption) error {
return start ( host, port, handler, func ( svr * http. Server) error {
return svr. ListenAndServe ( )
} , opts... )
}
func start ( host string , port int , handler http. Handler, run func ( svr * http. Server) error ,
opts ... StartOption) ( err error ) {
server := & http. Server{
Addr: fmt. Sprintf ( "%s:%d" , host, port) ,
Handler: handler,
}
for _ , opt := range opts {
opt ( server)
}
healthManager := health. NewHealthManager ( fmt. Sprintf ( "%s-%s:%d" , probeNamePrefix, host, port) )
waitForCalled := proc. AddWrapUpListener ( func ( ) {
healthManager. MarkNotReady ( )
if e := server. Shutdown ( context. Background ( ) ) ; e != nil {
logx. Error ( e)
}
} )
defer func ( ) {
if err == http. ErrServerClosed {
waitForCalled ( )
}
} ( )
healthManager. MarkReady ( )
health. AddProbe ( healthManager)
return run ( server)
}
到这里就来到了go的net/http标准库,具体参考go 进阶 http标准库相关: 三. HttpServer 服务启动到Accept等待接收连接 ,简单复习一下net/http提供服务的流程:
在通过net/http编写服务端时, 首先调用NewServeMux()创建多路复用器,编写对外接收请求的接口函数也就是处理器,然后调用多路复用器上的HandleFunc()方法,将接口与接口路径进行绑定,注册路由, 最后调用ListenAndServe()函数在指定端口开启监听,启动服务 ListenAndServe()方法内部重点调用了"net.Listen(“tcp”, addr)"多路复用相关初始化,初始化socket,端口连接绑定,开启监听,调用"srv.Serve(ln)”:等待接收客户端连接Accept(),与接收到连接后的处理流程 服务相关的我们先关注"srv.Serve(ln)",方法内通过for开启了一个死循环,在循环内部,调用Listener的Accept()方法,假设当前是TCP连接调用的就是TCPListener下的Accept(),监听客户端连接,当接收到客户端连接后,通过开启协程执行serve()方法处理请求,每一个连接开启一个goroutine来处理
接收请求的处理
go-zero底层是基于net/http实现的,再看一下net/http接收请求时底层是如何执行的go 进阶 http标准库相关: 五. HttpServer 接收请求路由发现原理 , 简单复习一下,基于net/http搭建服务时,底层会执行ListenAndServe()方法,最终会执行到Listener的Accept()方法,假设当前是TCP连接调用的就是TCPListener下的Accept(),阻塞监听客户端连接,当有接收到连接请求后Accept()方法返回,拿到一个新的net.Conn连接实例,然后开启协程调用Conn连接实例上的serve()方法处理客户端请求,查看这个serve()方法:
首先调用newBufioReader() 封装了一个bufio.Reader 开启了一个无限for循环,循环内 调用conn的readRequest(ctx)方法读取请求的内容,比如解析HTTP请求协议,读取请求头,请求参数,封装Request和response,在解析时会读取请求头的 Content-Length,不为 0会通过TCPConn.Read() 方法读取指定长度的数据并存入请求体中,如果 Content-Length 为 0 或者没有设置,则请求体为空 封装serverHandler调用serverHandler上的ServeHTTP(w, w.req)方法进行路由匹配,找到对应的处理函数,执行我们写的业务逻辑 调用response的finishRequest()方法进行最后处理工作,当底层 bufio.Writer 缓冲区的大小达到阈值或者Flush() 被显式调用时,就会将缓冲区内的数据写入到底层连接中,并触发 Conn 的 Write() 方法将数据发送到客户端,另外finishRequest()方法还会进行一些比如异常处理,资源回收,状态更新等操作 最后调用conn的setState()设置连接状态为StateIdle,方便后续重用连接
这里执行的ServeHTTP()就是匹配路由触发业务接口的函数,ServeHTTP是一个接口,绝大多数Web框架都是通过实现该接口,从而替换掉Golang默认的路由,这里执行的就是patRouter实现的ServeHTTP(),查看该函数源码
首先根据请求的method在trees中获取到指定前缀树,如果存在则根据请求的reqPath路径在前缀树中查找对应的handler对象,如果找到,则调用这个handler对象的ServeHTTP()方法 在服务启动时会将添加的中间件添加到chain处理器链中,然后遍历所有中间件转换为handler链,并将接口处理函数添加到handler链的末尾,这个过程是在chain类型的Then()方法中完成的。 在路由匹配时拿到第一个中间件handler开始执行,如果执行通过,中间件中会继续调用ServeHTTP(),也就是继续执行下一个中间件,一直执行到接口处理函数
func ( pr * patRouter) ServeHTTP ( w http. ResponseWriter, r * http. Request) {
reqPath := path. Clean ( r. URL. Path)
if tree, ok := pr. trees[ r. Method] ; ok {
if result, ok := tree. Search ( reqPath) ; ok {
if len ( result. Params) > 0 {
r = pathvar. WithVars ( r, result. Params)
}
result. Item. ( http. Handler) . ServeHTTP ( w, r)
return
}
}
allows, ok := pr. methodsAllowed ( r. Method, reqPath)
if ! ok {
pr. handleNotFound ( w, r)
return
}
if pr. notAllowed != nil {
pr. notAllowed. ServeHTTP ( w, r)
} else {
w. Header ( ) . Set ( allowHeader, allows)
w. WriteHeader ( http. StatusMethodNotAllowed)
}
}
三. 总结
go-zero http服务是怎么启动的, 是怎么整合net/http的, 前缀树是怎么构建的,路由,中间件是怎么注册的,当接收到请求后是怎么匹配路由执行的,当问到这些问题都可以由rest下的NewServer()函数创建服务Server开始说,下面总结一下 了解go-zero http服务首先要了解几个结构体,比如engine服务引擎,首先要创建这个引擎,内部有几个比较重要的属性比如
RestConf类型的conf属性: 存储了服务启动运行所需的配置信息 featuredRoutes类型的切片routes属性,在执行AddRoutes()注册接口时会将将接口封装为Router,然后将Router通过AddRoutes()保存到这个切片属性中,服务启动时会通过这个切片获取到所有路由,构建前缀树进行路由注册 Middleware类型切片的middlewares属性,会将通过Server的Use()方法注册的全局中间件保存到这个切片属性中,后续会通过这个属性获取到注册的中间件,加上局部中间件,接口handler转换为chain执行链,转换为Handler处理器链
还有一个比较重要的结构体patRouter, 内部存在一个trees属性,内部存储了以路由的method为key的前缀树,patRouter是路由注册与请求执行的核心,该结构体实现了ServeHTTP()方法,会通过这个方法处理用户的请求 说一下服务启动的执行过程,在构建go-zero http服务时,首先需要将配置设置到RestConf结构体变量上,通过执行rest下的NewServer()函数,读取配置创建服务端的Server,查看这个NewServer()源码
首先调用调用newEngine()创建Engine服务引擎 调用NewRouter()函数,初始化patRouter,初始化patRouter中的trees路由前缀树 将这两个属性封装到了一个Server结构体变量,并返回,然后通过Server结构体变量调用Use()方法注册中间件,调用AddRoutes()方法注册路由,调用Start()方法启动服务
中间件的注册: 在go-zero中可以通过Server的Use()方法注册全局中间件,可以通过WithMiddlewares/WithMiddleware()函数注册针对一组路由的局部中间件
通过Use()方法注册的中间件会保存到Engine的middlewares属性中 通过WithMiddlewares/WithMiddleware()函数注册的局部中间,查看源码发现,中间件函数会封装到Route的Handler中,跟随Route路由一块注册 另外go-zero的rest包下还有一个WithChain()也可以用来注册中举中间件,但是通过该函数注册的中间件会保存到Engine的chain属性中,是一个中间件链条
路由的保存: 当通过NewServer()拿到服务Server以后,调用Server的AddRoutes()方法,将对外的接口封装为Route进行路由后注册,在AddRoutes()方法中,将路由封装为featuredRoutes,保存到了engine的routes 属性中,这是保存,后续构建前缀树的逻辑在Server的Start()启动服务方法中 当拿到服务Server以后,查看Server的Start()启动服务方法,内部会调用engine的start()方法,内部重点执行了:
bindRoutes(): 注册路由中间件,构建前缀树 判断如果没有配置证书,执行StartHttp()启动http服务,有配置执行StartHttps()启动https服务,触发到net/http
bindRoutes()中间件,路由的注册与前缀树的构建,通过engine的routes属性获取到所有路由,遍历调用engine的bindFeaturedRoutes()方法开始注册路由,内部会调用engine的bindRoute(),在该方法中
首先获取engine的chain属性,如果为空,会调用buildChainWithNativeMiddlewares()在该方法中根据配置信息判断添加一下默认的中间件,比如判断是否配置了追踪请求的Trace中间件, 记录请求日志的Log中间件,收集请求指标数据Prometheus中间件,限制最大并发连接数的MaxConns中间件,实现熔断机制Breaker中间件 ,实现流量控制Shedding中间件,设置请求超时时间的Timeout中间件,恢复异常请求的Recover中间件…如果有配置则将这些中间件添加到chain中 遍历engine的middlewares也就是拿到全局中间件,将这些中间件也添加到到engine的chain属性中整个中间件链条封装完毕 比较重要的一个步骤,拿到路由接口的处理器Handler,执行chain的ThenFunc()方法,将保存了中间件链条的chain转换为Handler链,并将接口的处理器Handler添加到Handler链的末尾(在接收请求时根据路由匹配拿到指定的Handler后会基于这个链条向下调用到最终的接口处理函数) 调用Router的Handle()方法,将路由注册到路由树上,也就是前缀树的构建,实际执行的是patRouter的Handle(),方法中: 根据当前路由的Method判断是否存在该类型的前缀树,如果存在则调用Tree的Add()方法添加,如果不存在,则先调用调用一个NewTree()新建一个前缀树,然后调用Tree的Add()方法添加 Tree的Add()方法中会调用一个add()函数,该函数中,会根据"/“截取路由path中的每一段,作为token标识判断当前前缀树中是否已经存在该节点,如果存在则递归调用根据”/"继续截取当当前节点作为子节点判断添加前缀树中, 如果不存在则调用newNode()函数新建node节点,.以当前路由截取到的token标识为key添加到前缀树中
Server的Start()方法服务的启动, 查看源码内部会根据是否配置了证书选择调用StartHttp()/StartHttps()启动http或https服务,以http为例,查看StartHttp()源码内最终封装调用了net/http标准库中的ListenAndServe() 了解go-zero http服务怎么接收请求执行的,要先了解net/http是怎么请求,路由匹配的,在net/http处理请求时会调用路由的ServeHTTP()方法,这里调用的就是patRouter上的这个方法,查看源码:
首先根据请求的method在trees中获取到指定前缀树,如果存在则根据请求的reqPath路径在前缀树中查找对应的handler对象,如果找到,则调用这个handler对象的ServeHTTP()方法 在服务启动时会将添加的中间件添加到chain处理器链中,然后遍历所有中间件转换为handler链,并将接口处理函数添加到handler链的末尾,这个过程是在chain类型的Then()方法中完成的。 在路由匹配时拿到第一个中间件handler开始执行,如果执行通过,中间件中会继续调用ServeHTTP(),也就是继续执行下一个中间件,一直执行到接口处理函数