函数怎么屏蔽其他echo_Go框架解析:echo

83c088a49ccc47612b899e01f9dbfe91.png

前言

今天是我golang框架阅读系列第四篇文章,今天我们主要看看echo的框架执行流程。关于golang框架生命周期源码阅读下面是我的计划:

计划状态
Go框架解析:beego✅done
Go框架解析:iris✅done
Go框架解析:gin✅done
Go框架解析:echo✅done
Go框架解析:revel✈️doing
Go框架解析:Martini️️✈️doing

再完成各个golang框架生命周期的解析之后,我会计划对这几个框架的优略进行一个系列分析,由于业内大多都是性能分析的比较多,我可能会更侧重于以下维度:

  • 框架设计

  • 路由算法

第一波我们主要把重点放在框架设计上面。

安装

使用go mod安装:

// 初始化go.mod文件
go mod init echo-code-read
// 安装echo
go get github.com/labstack/echo/
// touch main.go 创建main.go文件贴如下面的示例
// 复制依赖到vendor目录
go mod vendor

启动一个简单的echo http服务:

package mainimport ("net/http""github.com/labstack/echo/v4""github.com/labstack/echo/v4/middleware"
)func main() {// 创建一个echo实例// 不同于iris、gin 是直接使用New
e := echo.New()// 注册中间件// 需要我们在入口文件手动注入基础中间件// iris、gin是又封装了一次 其实对我们来讲挺好的// 只是iris、gin命名封装的方法Default 实在没有语意
e.Use(middleware.Logger())
e.Use(middleware.Recover())// 注册路由// 这里这几个框架的写法没啥区别
e.GET("/", hello)// 启动服务// 基本同gin// 只是加了个返回值的日志
e.Logger.Fatal(e.Start(":1323"))
}// 路由handle提出来了而已// 匿名函数方式 不重要func hello(c echo.Context) error {return c.String(http.StatusOK, "Hello, World!")
}

echo的生命周期

看完echo的框架流程,再对别iris、gin。总体的感受就是套路一致,对于使用者来说,在入口文件需要手动的注册Logger、Recover公共中间件,而不像iris、gin一样封装一个Default方法,其次在构成中间件链的实现方式上有些小的区别(之后文章统一分析),其他的框架设计思路基本和gin一样。

下图就是我对整个Echo框架生命周期的输出,由于图片过大存在平台压缩的可能,建议大家直接查看原图链接。

bb98a0a11aefe619d2fe642682ea6e8c.png

访问图片源地址查看大图 http://cdn.tigerb.cn/20190711122919.png

关键代码解析

// 初始化一个echo框架实例// 不同于iris和gin// iris和gin在这之上又封装了一层 包含必须的中间件注册
e := echo.New()
⬇️// 具体的获取实例方法func New() (e *Echo) {
e = &Echo{// 创建一个http Server指针
Server: new(http.Server),// 创建一个https的 Server指针
TLSServer: new(http.Server),
AutoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},// 日志实例
Logger: log.New("echo"),// 控制台、日志可以彩色输出的实例
colorer: color.New(),
maxParam: new(int),
}// http server绑定实现了server.Handler的实例// 也就是说Echo框架自身实现了http.Handler接口
e.Server.Handler = e// https server绑定实现了server.Handler的实例
e.TLSServer.Handler = e// 绑定http服务异常处理的handler
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler//
e.Binder = &DefaultBinder{}// 设置日志输出级别
e.Logger.SetLevel(log.ERROR)// 绑定标准日志输出实例
e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)// 和iris、gin都一样// 绑定获取请求上下文实例的闭包
e.pool.New = func() interface{} {return e.NewContext(nil, nil)
}// 绑定路由实例
e.router = NewRouter(e)// 绑定路由map// 注意这个属性的含义:路由分组用的,key为host,则按host分组// 记住与Router.routes区别// Router.routes存的路由的信息(不包含路由的handler)
e.routers = map[string]*Router{}return
}
⬇️func NewRouter(e *Echo) *Router {// 初始化Routerreturn &Router{// 路由树// 路由的信息(包含路由的handler)// 查找路由用的LCP (最长公共前缀)算法
tree: &node{// 节点对应的不同http method的handler
methodHandler: new(methodHandler),
},// Router.routes存的路由的信息(不包含路由的handler)
routes: map[string]*Route{},// 框架实例自身
echo: e,
}
}// ---------router---------// 接下来我们看看路由相关的流程// 之前我们先看看相关一些重要的结构体
Router struct {// 路由树
tree *node// 路由信息mao
routes map[string]*Route// 框架实例
echo *Echo
}// LCP (最长公共前缀) 算法 通过path查找路由// 之后路由算法专题细聊
node struct {
kind kind
label byte
prefix string
parent *node
children children
ppath string
pnames []string
methodHandler *methodHandler
}
kind uint8// 子节点
children []*node// 不同http method的handler
methodHandler struct {
connect HandlerFuncdelete HandlerFunc
get HandlerFunc
head HandlerFunc
options HandlerFunc
patch HandlerFunc
post HandlerFunc
propfind HandlerFunc
put HandlerFunc
trace HandlerFunc
report HandlerFunc
}
HandlerFunc func(Context) errorRoute struct {// http method
Method string `json:"method"`// 路由path
Path string `json:"path"`// 路由handler名称
Name string `json:"name"`
}// 注册路由
e.GET("/", hello)
⬇️// 注册路由func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {// 注册路由return e.Add(http.MethodGet, path, h, m...)
}
️️⬇️func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {// 注册路由 add方法相对于Add多了host参数return e.add("", method, path, handler, middleware...)
}
⬇️func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {// 获取handler的名称// ?这个方法里面尽然用了反射获取name 只是个name有必要么 没别的办法了吗?
name := handlerName(handler)// 寻找当前host的路由实例
router := e.findRouter(host)// 注册路由// 注意第三个参数是个闭包 匹配到路由就会执行这个闭包
router.Add(method, path, func(c Context) error {// 初始化一个handler类型的实例
h := handlerfor i := len(middleware) - 1; i >= 0; i-- {// 注意这里的中间件是这个路由专属的// 而Use、Pre注册的中间件是全局公共的// 遍历中间件// 注意返回值类型是HandlerFunc// 感觉他们这里设计的复杂了// 后期我看看可不可以把责任链模式引入进来 一下就清晰了
h = middleware[i](h)
}// 执行最后一个中间件return h(c)
})// 本次注册进来的路由的信息// 感觉设计的很怪 上面注册一次路由且包含路由的handler// 这里又单独存一个不包含路由handler的路由信息// 为啥不都放一起呢?哎
r := &Route{
Method: method,
Path: path,
Name: name,
}// map存路由信息
e.router.routes[method+path] = rreturn r
}// ---------start---------// 启动http server
e.Start(":1323")func (e *Echo) Start(address string) error {// 设置server地址
e.Server.Addr = address// 启动serverreturn e.StartServer(e.Server)
}
⬇️func (e *Echo) StartServer(s *http.Server) (err error) {
e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger// 设置框架实例到http server的Handler// Echo框架结构体实现了http.Handler接口
s.Handler = eif e.Debug {// 如果开启了debug则设置日志级别为 debug
e.Logger.SetLevel(log.DEBUG)
}// 是否隐藏框架启动输出的标志if !e.HideBanner {
e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
}// 启动http serverif s.TLSConfig == nil {if e.Listener == nil {// 监听ip+port
e.Listener, err = newListener(s.Addr)if err != nil {return err
}
}// 打印服务地址if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}return s.Serve(e.Listener)
}// 启动https serverif e.TLSListener == nil {
l, err := newListener(s.Addr)if err != nil {return err
}// 监听ip+port// 设置https配置
e.TLSListener = tls.NewListener(l, s.TLSConfig)
}if !e.HidePort {// 打印服务地址
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
}return s.Serve(e.TLSListener)
}
⬇️
s.Serve()
⬇️// accept网络请求
rw, e := l.Accept()
⬇️// goroutine处理请求go c.serve(ctx)
⬇️// 执行serverHandler的ServeHTTP
serverHandler{c.server}.ServeHTTP(w, w.req)
⬇️// 执行当前框架实例的ServeHTTP方法
handler.ServeHTTP(rw, req)
⬇️func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {// 获取上下文实例
c := e.pool.Get().(*context)// 重置上下文
c.Reset(r, w)// 默认handler
h := NotFoundHandler// 不存在预执行中间件时// 说说这个预执行中间件的含义:// 看源码注释的含义是在寻找到路由之前执行的中间件// 简单来说和普通中间件的的区别就是,还没走到匹配路由的逻辑就会执行的中间件,从下面来看只是代码逻辑的区别,实际的中间件执行顺序还是谁先注册谁先执行。所以无论是存在普通中间件还是预执行中间件,路由的handle总是最后执行。// 个人感觉预执行中间件的意义不大if e.premiddleware == nil {// 先找当前host组的router// LCP算法寻找当前path的handler
e.findRouter(r.Host).Find(r.Method, getPath(r), c)// 找到当前路由的handler
h = c.Handler()// 构成中间件链
h = applyMiddleware(h, e.middleware...)
} else {// 看见这个预执行中间件的区别了吧// 把注册普通中间件的逻辑又包装成了一个HandlerFunc注册到中间件链中
h = func(c Context) error {// 先找当前host组的router// LCP算法寻找当前path的handler
e.findRouter(r.Host).Find(r.Method, getPath(r), c)
h := c.Handler()
h = applyMiddleware(h, e.middleware...)return h(c)
}// 构成中间件链
h = applyMiddleware(h, e.premiddleware...)
}// 执行中间件链// 在applyMiddleware中所有中间件构成了一个链if err := h(c); err != nil {
e.HTTPErrorHandler(err, c)
}// 释放上下文
e.pool.Put(c)
}// 构成中间件链// 在构建这个中间件链的细节方式上 和iris,gin还是不一样// 后续专写一篇文章详细对比各框架中间件的实现func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {for i := len(middleware) - 1; i >= 0; i-- {// 注意这里返回的还是一个闭包// 所以说还没真正执行
h = middleware[i](h)
}return h
}// ---------log---------// 记录启动错误日志
e.Logger.Fatal()

结语

最后我们再简单的回顾下上面的流程。

5fa7884bbe53ed6c12bc9421b9b865bb.png


《Go框架解析》系列文章链接如下:

  • Go框架解析:echo

  • Go框架解析:gin

  • Go框架解析:iris

  • Go框架解析:beego

推荐阅读

  • 最流行的 Web 框架 Gin 源码阅读


喜欢本文的朋友,欢迎关注“Go语言中文网”:

996473c9c45a4c302b19648b75da9de6.png

Go语言中文网启用微信学习交流群,欢迎加微信:274768166,投稿亦欢迎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值