echo源码分析
web框架的核心作用有三个:分层、路由、中间件。针对于go比较有名的web框架有
https://github.com/labstack/echo
https://github.com/gin-gonic/gin
https://github.com/kataras/iris
https://beego.me/docs/intro/
https://github.com/go-martini/martini
其中echo 是一个比较轻量级的框架,下面基于echo@v1.4.4对它的源码进行分析。
主要有下面6个文件和三个目录组成。
binder.gocontext.goecho.gogroup.goresponse.gorouter.gomiddleware_fixturewebsite
其中middleware里面定义了最基本最常用的四个中间件
auth.gocompress.gologger.gorecover.go
_fixture是一些网页资源
% ls _fixturefavicon.ico folder images index.html
website 是说明文档,中间有个Dockerfile 可以在本地编译镜像,跑起来
% ls websiteDockerfile config.json layoutsargo.json content static
首先我们看下如何使用echo
package mainimport ("net/http""github.com/labstack/echo/v4""github.com/labstack/echo/v4/middleware")func main() {// 创建一个echo实例e := echo.New()// 注册中间件// 需要我们在入口文件手动注入基础中间件e.Use(middleware.Logger())e.Use(middleware.Recover())// 注册路由e.GET("/", hello)// 启动服务e.Logger.Fatal(e.Start(":1323"))}// 路由handle提出来了而已// 匿名函数方式 不重要func hello(c echo.Context) error {return c.String(http.StatusOK, "Hello, World!")}
1,echo.go文件
New函数定义在echo.go 文件里面
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服务异常处理的handlere.HTTPErrorHandler = e.DefaultHTTPErrorHandler//e.Binder = &DefaultBinder{}// 设置日志输出级别e.Logger.SetLevel(log.ERROR)// 绑定标准日志输出实例e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)// 绑定获取请求上下文实例的闭包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}
手先初始化了一个echo对象,然后定义了一个DefaultBinder,实现了Binder接口,这个接口定义在bind.go文件里,后面介绍
Binder interface {Bind(i interface{}, c Context) error}
接着设置了日志级别为ERROR,设置了标准输出Logger,然后初始化了一个Context 对象
// NewContext returns a Context instance.func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {return &context{request: r,response: NewResponse(w, e),store: make(Map),echo: e,pvalues: make([]string, *e.maxParam),handler: NotFoundHandler,}}
context 对象和interface 都定义在context.go文件中,后面讲解context .go文件的时候详细讲解.
其中的Map定义如下
Map map[string]interface{}context里面存储了
r *http.Request, w http.ResponseWriter这也就是为什么写echo的controller的时候只用传 context对象就行,而标准的http包需要顶替包含参数r *http.Request, w http.ResponseWriter的HandleFunc
最后初始化了路由(router.go)里面实现的
func NewRouter(e *Echo) *Router {// 初始化Routerreturn &Router{// 路由树// 路由的信息(包含路由的handler)// 查找路由用的LCP (最长公共前缀)算法tree: &node{// 节点对应的不同http method的handlermethodHandler: new(methodHandler),},// Router.routes存的路由的信息(不包含路由的handler)routes: map[string]*Route{},// 框架实例自身echo: e,}
Router的定义如下
Router struct {tree *noderoutes map[string]*Routeecho *Echo}
是一个棵多叉树,其中tree存储了当前节点里面的值,它的定义如下
node struct {kind kindlabel byteprefix stringparent *nodechildren childrenppath stringpnames []stringmethodHandler *methodHandler}
接着我们看下如何定义路由的,已get请求为例
e.GET("/stats", func(c echo.Context) error {return c.JSON(200, s.Data())})
它的定义如下
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {return e.Add(http.MethodGet, path, h, m...)}
我们可以看到,它的内部调用了Add方法,其实POST、PATCH、DELETE等http method类似都是对Add方法进行了包裹
Add方法的参数有http请求的方法,请求路径,对应的处理函数和中间件参数。
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {// 获取handler的名称// 😨这个方法里面尽然用了反射获取name 只是个name有必要么 没别的办法了吗?name := handlerName(handler)// 注册路由// 注意第三个参数是个闭包 匹配到路由就会执行这个闭包e.router.Add(method, path, func(c Context) error {h := handler// Chain middlewarefor i := len(middleware) - 1; i >= 0; i-- {// 注意这里的中间件是这个路由专属的// 而Use、Pre注册的中间件是全局公共的// 遍历中间件// 注意返回值类型是HandlerFunc//典型的洋葱模式h = middleware[i](h)}// 执行最后一个中间件return h(c)})// 本次注册进来的路由的信息,只存放了handler的方法名r := &Route{Method: method,Path: path,Name: name,}// map存路由信息e.router.routes[method+path] = rreturn r}
注意这里的Route 不是Router定义如下
Route struct {Method string `json:"method"`Path string `json:"path"`Name string `json:"name"`}
Method 存的是通过反射获取handler的名字
func handlerName(h HandlerFunc) string {t := reflect.ValueOf(h).Type()if t.Kind() == reflect.Func {return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()}return t.String()}
最后看下start函数
e.Logger.Fatal(e.Start(":1323"))func (e *Echo) Start(address string) error {e.Server.Addr = addressreturn e.StartServer(e.Server)}
func (e *Echo) StartServer(s *http.Server) (err error) {// Setupe.colorer.SetOutput(e.Logger.Output())s.ErrorLog = e.StdLogger// 设置框架实例到http server的Handler// Echo框架结构体实现了http.Handler接口s.Handler = eif e.Debug {e.Logger.SetLevel(log.DEBUG)}if !e.HideBanner {e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))}if s.TLSConfig == nil {if e.Listener == nil {// 监听ip+porte.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()))}// 启动http serverreturn s.Serve(e.Listener)}if e.TLSListener == nil {// 设置https配置l, err := newListener(s.Addr)if err != nil {return err}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)}
接下来的流程就是标准httpserver的执行流程
s.Serve()⬇️// accept网络请求rw, e := l.Accept()⬇️// goroutine处理请求go c.serve(ctx)⬇️// 执行serverHandler的ServeHTTPserverHandler{c.server}.ServeHTTP(w, w.req)⬇️// 执行当前框架实例的ServeHTTP方法handler.ServeHTTP(rw, req)
看下echo是怎么实现ServeHTTP方法的
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {// Acquire context// 获取上下文实例c := e.pool.Get().(*context)c.Reset(r, w)h := NotFoundHandler// 不存在预执行中间件时// 说说这个预执行中间件的含义:// 看源码注释的含义是在寻找到路由之前执行的中间件// 简单来说和普通中间件的的区别就是,还没走到匹配路由的逻辑就会执行的中间件,从下面来看只是代码逻辑的区别,实际的中间件执行顺序还是谁先注册谁先执行。所以无论是存在普通中间件还是预执行中间件,路由的handle总是最后执行。// 个人感觉预执行中间件的意义不大if e.premiddleware == nil {// 先找当前host组的router// LCP算法寻找当前path的handlere.router.Find(r.Method, getPath(r), c)h = c.Handler()for i := len(e.middleware) - 1; i >= 0; i-- {h = e.middleware[i](h)}} else {// 看见这个预执行中间件的区别了吧// 把注册普通中间件的逻辑又包装成了一个HandlerFunc注册到中间件链中h = func(c Context) error {e.router.Find(r.Method, getPath(r), c)h := c.Handler()for i := len(e.middleware) - 1; i >= 0; i-- {h = e.middleware[i](h)}return h(c)}for i := len(e.premiddleware) - 1; i >= 0; i-- {h = e.premiddleware[i](h)}}// Execute chain// 执行中间件链// 在applyMiddleware中所有中间件构成了一个链if err := h(c); err != nil {e.HTTPErrorHandler(err, c)}// Release context// 释放上下文e.pool.Put(c)}
echo.go的核心逻辑基本讲完了,里面还定义了一系列的辅助类型和方法
// MiddlewareFunc defines a function to process middleware.MiddlewareFunc func(HandlerFunc) HandlerFunc// HandlerFunc defines a function to serve HTTP requests.HandlerFunc func(Context) error// HTTPErrorHandler is a centralized HTTP error handler.HTTPErrorHandler func(error, Context)// Validator is the interface that wraps the Validate function.Validator interface {Validate(i interface{}) error}// Renderer is the interface that wraps the Render function.Renderer interface {Render(io.Writer, string, interface{}, Context) error}// i is the interface for Echo and Group.i interface {GET(string, HandlerFunc, ...MiddlewareFunc) *Route}
// HTTP methods// NOTE: Deprecated, please use the stdlib constants directly instead.const (CONNECT = http.MethodConnectDELETE = http.MethodDelete
// MIME typesconst (MIMEApplicationJSON = "application/json"MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
// Headersconst (HeaderAccept = "Accept"HeaderAcceptEncoding = "Accept-Encoding"
// Errorsvar (ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response// with status code.func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
// Static registers a new route with path prefix to serve static files from the// provided root directory.func (e *Echo) Static(prefix, root string) *Route {if root == "" {root = "." // For security we want to restrict to CWD.}return static(e, prefix, root)}func static(i i, prefix, root string) *Route {h := func(c Context) error {p, err := url.PathUnescape(c.Param("*"))if err != nil {return err}name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for securityreturn c.File(name)}i.GET(prefix, h)if prefix == "/" {return i.GET(prefix+"*", h)}return i.GET(prefix+"/*", h)}
把url中的namd/:id 中的id解析出来存到url中
// Reverse generates an URL from route name and provided parameters.func (e *Echo) Reverse(name string, params ...interface{}) string {uri := new(bytes.Buffer)ln := len(params)n := 0for _, r := range e.router.routes {if r.Name == name {for i, l := 0, len(r.Path); i < l; i++ {if r.Path[i] == ':' && n < ln {for ; i < l && r.Path[i] != '/'; i++ {}uri.WriteString(fmt.Sprintf("%v", params[n]))n++}if i < l {uri.WriteByte(r.Path[i])}}break}}return uri.String()}
2,context.go文件
首先定义了
Context interface {}和对应的对象
context struct {request *http.Requestresponse *Responsepath stringpnames []stringpvalues []stringquery url.Valueshandler HandlerFuncstore Mapecho *Echo}
获取参数值
func (c *context) Param(name string) string {for i, n := range c.pnames {if i < len(c.pvalues) {if n == name {return c.pvalues[i]}}}return ""}
func (c *context) QueryParam(name string) string {if c.query == nil {c.query = c.request.URL.Query()}return c.query.Get(name)}
func (c *context) FormValue(name string) string {return c.request.FormValue(name)}
参数绑定
func (c *context) Bind(i interface{}) error {return c.echo.Binder.Bind(i, c)}
参数校验
func (c *context) Validate(i interface{}) error {if c.echo.Validator == nil {return ErrValidatorNotRegistered}return c.echo.Validator.Validate(i)}
输出json
func (c *context) json(code int, i interface{}, indent string) error {enc := json.NewEncoder(c.response)if indent != "" {enc.SetIndent("", indent)}c.writeContentType(MIMEApplicationJSONCharsetUTF8)c.response.WriteHeader(code)return enc.Encode(i)}
3,router.go
主要是定义了Router的一系列结构体
Router struct {tree *noderoutes []Routeecho *Echo}node struct {kind kindlabel byteprefix stringparent *nodechildren childrenppath stringpnames []stringmethodHandler *methodHandlerecho *Echo}
一系列处理方法
methodHandler struct {connect HandlerFuncdelete HandlerFuncget HandlerFunchead HandlerFuncoptions HandlerFuncpatch HandlerFuncpost HandlerFuncput HandlerFunctrace HandlerFunc}
初始化一个router对象
func NewRouter(e *Echo) *Router {return &Router{tree: &node{methodHandler: new(methodHandler),},routes: []Route{},echo: e,}}
添加路由,构建tire树
// Add registers a new route with a matcher for the URL path.func (r *Router) Add(method, path string, h HandlerFunc, e *Echo) {ppath := path // Pristine pathpnames := []string{} // Param namesfor i, l := 0, len(path); i < l; i++ {if path[i] == ':' {j := i + 1//提取路由中的参数前面部分,构建树r.insert(method, path[:i], nil, skind, "", nil, e)for ; i < l && path[i] != '/'; i++ {}//把参数名放到pnames里面pnames = append(pnames, path[j:i])path = path[:j] + path[i:]i, l = j, len(path)if i == l {r.insert(method, path[:i], h, pkind, ppath, pnames, e)return}r.insert(method, path[:i], nil, pkind, ppath, pnames, e)} else if path[i] == '*' {r.insert(method, path[:i], nil, skind, "", nil, e)pnames = append(pnames, "_*")r.insert(method, path[:i+1], h, akind, ppath, pnames, e)return}}r.insert(method, path, h, skind, ppath, pnames, e)}
提取参数和正则以后,构建tire树
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string, e *Echo) {// Adjust max paraml := len(pnames)if *e.maxParam < l {*e.maxParam = l}cn := r.tree // Current node as rootif cn == nil {panic("echo => invalid method")}search := pathfor {sl := len(search)pl := len(cn.prefix)l := 0// LCPmax := plif sl < max {max = sl}for ; l < max && search[l] == cn.prefix[l]; l++ {}if l == 0 {// At root nodecn.label = search[0]cn.prefix = searchif h != nil {cn.kind = tcn.addHandler(method, h)cn.ppath = ppathcn.pnames = pnamescn.echo = e}} else if l < pl {// Split noden := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames, cn.echo)// Reset parent nodecn.kind = skindcn.label = cn.prefix[0]cn.prefix = cn.prefix[:l]cn.children = nilcn.methodHandler = new(methodHandler)cn.ppath = ""cn.pnames = nilcn.echo = nilcn.addChild(n)if l == sl {// At parent nodecn.kind = tcn.addHandler(method, h)cn.ppath = ppathcn.pnames = pnamescn.echo = e} else {// Create child noden = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames, e)n.addHandler(method, h)cn.addChild(n)}} else if l < sl {search = search[l:]c := cn.findChildWithLabel(search[0])if c != nil {// Go deepercn = ccontinue}// Create child noden := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames, e)n.addHandler(method, h)cn.addChild(n)} else {// Node already existsif h != nil {cn.addHandler(method, h)cn.ppath = ppathcn.pnames = pnamescn.echo = e}}return}}
接着就是路由查找的过程
func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo) {// r.tree.printTree("", true)h = notFoundHandlere = r.echocn := r.tree // Current node as rootvar (search = pathc *node // Child noden int // Param counternk kind // Next kindnn *node // Next nodens string // Next search)// Search order static > param > anyfor {if search == "" {goto End}pl := 0 // Prefix lengthl := 0 // LCP lengthif cn.label != ':' {sl := len(search)pl = len(cn.prefix)// LCPmax := plif sl < max {max = sl}for ; l < max && search[l] == cn.prefix[l]; l++ {}}if l == pl {// Continue searchsearch = search[l:]} else {cn = nnsearch = nsif nk == pkind {goto Param} else if nk == akind {goto Any}// Not foundreturn}if search == "" {goto End}// Static nodeif c = cn.findChild(search[0], skind); c != nil {// Save nextif cn.prefix[len(cn.prefix)-1] == '/' {nk = pkindnn = cnns = search}cn = ccontinue}// Param nodeParam:if c = cn.findChildByKind(pkind); c != nil {// Issue #378if len(ctx.pvalues) == n {continue}// Save nextif cn.prefix[len(cn.prefix)-1] == '/' {nk = akindnn = cnns = search}cn = ci, l := 0, len(search)for ; i < l && search[i] != '/'; i++ {}ctx.pvalues[n] = search[:i]n++search = search[i:]continue}// Any nodeAny:if cn = cn.findChildByKind(akind); cn == nil {if nn != nil {cn = nnnn = nil // Nextsearch = nsif nk == pkind {goto Param} else if nk == akind {goto Any}}// Not foundreturn}ctx.pvalues[len(cn.pnames)-1] = searchgoto End}End:ctx.path = cn.ppathctx.pnames = cn.pnamesh = cn.findHandler(method)if cn.echo != nil {e = cn.echo}// NOTE: Slow zone...if h == nil {h = cn.check405()// Dig further for any, might have an empty value for *, e.g.// serving a directory. Issue #207.if cn = cn.findChildByKind(akind); cn == nil {return}ctx.pvalues[len(cn.pnames)-1] = ""if h = cn.findHandler(method); h == nil {h = cn.check405()}}return}
最后是serveHTTP方法,这个方法是在echo的同名方法里,把router转化成handleFunc,然后调用的。
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := r.echo.pool.Get().(*Context)h, _ := r.Find(req.Method, req.URL.Path, c)c.reset(req, w, r.echo)if err := h(c); err != nil {r.echo.httpErrorHandler(err, c)}r.echo.pool.Put(c)}
4,binder.go
defaultBinder实现了Bind方法,通过http方法以及header里面的ContentType来实现绑定
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {req := c.Request()if req.ContentLength == 0 {if req.Method == http.MethodGet || req.Method == http.MethodDelete {if err = b.bindData(i, c.QueryParams(), "query"); err != nil {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}return}return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")}ctype := req.Header.Get(HeaderContentType)switch {case strings.HasPrefix(ctype, MIMEApplicationJSON):if err = json.NewDecoder(req.Body).Decode(i); err != nil {if ute, ok := err.(*json.UnmarshalTypeError); ok {return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)} else if se, ok := err.(*json.SyntaxError); ok {return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)} else {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}return NewHTTPError(http.StatusBadRequest, err.Error())}case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):if err = xml.NewDecoder(req.Body).Decode(i); err != nil {if ute, ok := err.(*xml.UnsupportedTypeError); ok {return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)} else if se, ok := err.(*xml.SyntaxError); ok {return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)} else {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}return NewHTTPError(http.StatusBadRequest, err.Error())}case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):params, err := c.FormParams()if err != nil {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}if err = b.bindData(i, params, "form"); err != nil {return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)}default:return ErrUnsupportedMediaType}return}
绑定数据的过程是通过reflect实现的
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {typ := reflect.TypeOf(ptr).Elem()val := reflect.ValueOf(ptr).Elem()if typ.Kind() != reflect.Struct {return errors.New("binding element must be a struct")}for i := 0; i < typ.NumField(); i++ {typeField := typ.Field(i)structField := val.Field(i)if !structField.CanSet() {continue}structFieldKind := structField.Kind()inputFieldName := typeField.Tag.Get(tag)if inputFieldName == "" {inputFieldName = typeField.Name// If tag is nil, we inspect if the field is a struct.if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {return err}continue}}inputValue, exists := data[inputFieldName]if !exists {// Go json.Unmarshal supports case insensitive binding. However the// url params are bound case sensitive which is inconsistent. To// fix this we must check all of the map values in a// case-insensitive search.inputFieldName = strings.ToLower(inputFieldName)for k, v := range data {if strings.ToLower(k) == inputFieldName {inputValue = vexists = truebreak}}}if !exists {continue}// Call this first, in case we're dealing with an alias to an array typeif ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {if err != nil {return err}continue}numElems := len(inputValue)if structFieldKind == reflect.Slice && numElems > 0 {sliceOf := structField.Type().Elem().Kind()slice := reflect.MakeSlice(structField.Type(), numElems, numElems)for j := 0; j < numElems; j++ {if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {return err}}val.Field(i).Set(slice)} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {return err}}return nil}
5,response.go
对http response做了一个包装
Response struct {echo *EchobeforeFuncs []func()afterFuncs []func()Writer http.ResponseWriterStatus intSize int64Committed bool}
写响应头
func (r *Response) WriteHeader(code int) {if r.Committed {r.echo.Logger.Warn("response already committed")return}for _, fn := range r.beforeFuncs {fn()}r.Status = coder.Writer.WriteHeader(code)r.Committed = true}
写响应body
func (r *Response) Write(b []byte) (n int, err error) {if !r.Committed {r.WriteHeader(http.StatusOK)}n, err = r.Writer.Write(b)r.Size += int64(n)for _, fn := range r.afterFuncs {fn()}return}
6,group.go
group主要是定义了子路由,针对大家有一个共同ns的复杂路由场景很有用
Group struct {prefix stringmiddleware []MiddlewareFuncecho *Echo}
和普通路由的区别是path存储变成了prefix+path
// Add implements `Echo#Add()` for sub-routes within the Group.func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {// Combine into a new slice to avoid accidentally passing the same slice for// multiple routes, which would lead to later add() calls overwriting the// middleware from earlier calls.m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))m = append(m, g.middleware...)m = append(m, middleware...)return g.echo.Add(method, g.prefix+path, handler, m...)}
7,middleware
7.1auth.go
实现了basic auth
// BasicAuthConfig defines the config for BasicAuth middleware.BasicAuthConfig struct {// Skipper defines a function to skip middleware.Skipper Skipper// Validator is a function to validate BasicAuth credentials.// Required.Validator BasicAuthValidator// Realm is a string to define realm attribute of BasicAuth.// Default value "Restricted".Realm string}// BasicAuthValidator defines a function to validate BasicAuth credentials.BasicAuthValidator func(string, string, echo.Context) (bool, error)
// BasicAuthWithConfig returns an BasicAuth middleware with config.// See `BasicAuth()`.func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {// Defaultsif config.Validator == nil {panic("echo: basic-auth middleware requires a validator function")}if config.Skipper == nil {config.Skipper = DefaultBasicAuthConfig.Skipper}if config.Realm == "" {config.Realm = defaultRealm}return func(next echo.HandlerFunc) echo.HandlerFunc {return func(c echo.Context) error {if config.Skipper(c) {return next(c)}auth := c.Request().Header.Get(echo.HeaderAuthorization)l := len(basic)if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic {b, err := base64.StdEncoding.DecodeString(auth[l+1:])if err != nil {return err}cred := string(b)for i := 0; i < len(cred); i++ {if cred[i] == ':' {// Verify credentialsvalid, err := config.Validator(cred[:i], cred[i+1:], c)if err != nil {return err} else if valid {return next(c)}break}}}realm := defaultRealmif config.Realm != defaultRealm {realm = strconv.Quote(config.Realm)}// Need to return `401` for browsers to pop-up login box.c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm)return echo.ErrUnauthorized}}}
7.2compress.go
进行gzip压缩
unc Gzip() echo.MiddlewareFunc {scheme := "gzip"return func(h echo.HandlerFunc) echo.HandlerFunc {return func(c *echo.Context) error {c.Response().Header().Add(echo.Vary, echo.AcceptEncoding)if strings.Contains(c.Request().Header.Get(echo.AcceptEncoding), scheme) {w := writerPool.Get().(*gzip.Writer)w.Reset(c.Response().Writer())defer func() {w.Close()writerPool.Put(w)}()gw := gzipWriter{Writer: w, ResponseWriter: c.Response().Writer()}c.Response().Header().Set(echo.ContentEncoding, scheme)c.Response().SetWriter(gw)}if err := h(c); err != nil {c.Error(err)}return nil}}}
7.3logger.go
记录请求的一些基本信息,如ip等等
func Logger() echo.MiddlewareFunc {return func(h echo.HandlerFunc) echo.HandlerFunc {return func(c *echo.Context) error {req := c.Request()res := c.Response()logger := c.Echo().Logger()remoteAddr := req.RemoteAddrif ip := req.Header.Get(echo.XRealIP); ip != "" {remoteAddr = ip} else if ip = req.Header.Get(echo.XForwardedFor); ip != "" {remoteAddr = ip} else {remoteAddr, _, _ = net.SplitHostPort(remoteAddr)}start := time.Now()if err := h(c); err != nil {c.Error(err)}stop := time.Now()method := req.Methodpath := req.URL.Pathif path == "" {path = "/"}size := res.Size()n := res.Status()code := color.Green(n)switch {case n >= 500:code = color.Red(n)case n >= 400:code = color.Yellow(n)case n >= 300:code = color.Cyan(n)}logger.Printf(format, remoteAddr, method, path, code, stop.Sub(start), size)return nil}}}
7.4recover.go
对panic进行recover操作
func Recover() echo.MiddlewareFunc {// TODO: Provide better stack trace `https://github.com/go-errors/errors` `https://github.com/docker/libcontainer/tree/master/stacktrace`return func(h echo.HandlerFunc) echo.HandlerFunc {return func(c *echo.Context) error {defer func() {if err := recover(); err != nil {trace := make([]byte, 1<<16)n := runtime.Stack(trace, true)c.Error(fmt.Errorf("panic recover\n %v\n stack trace %d bytes\n %s",err, n, trace[:n]))}}()return h(c)}}}
推荐阅读
