echo源码分析

Go语言精选

共 29318字,需浏览 59分钟

 ·

2021-05-10 17:59

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 main
import ( "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服务异常处理的handler  e.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 {  // 初始化Router  return &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    routes map[string]*Route    echo   *Echo  }

是一个棵多叉树,其中tree存储了当前节点里面的值,它的定义如下

  node struct {    kind          kind    label         byte    prefix        string    parent        *node    children      children    ppath         string    pnames        []string    methodHandler *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 middleware    for 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] = r return 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 = address  return e.StartServer(e.Server)}
func (e *Echo) StartServer(s *http.Server) (err error) {  // Setup  e.colorer.SetOutput(e.Logger.Output())  s.ErrorLog = e.StdLogger  // 设置框架实例到http server的Handler  // Echo框架结构体实现了http.Handler接口  s.Handler = e  if 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+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())) } // 启动http server return 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的handler 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) } } 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.MethodConnect  DELETE  = 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 security return 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 := 0  for _, 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.Request    response *Response    path     string    pnames   []string    pvalues  []string    query    url.Values    handler  HandlerFunc    store    Map    echo     *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   *node    routes []Route    echo   *Echo  }  node struct {    kind          kind    label         byte    prefix        string    parent        *node    children      children    ppath         string    pnames        []string    methodHandler *methodHandler    echo          *Echo  }


一系列处理方法

  methodHandler struct {    connect HandlerFunc    delete  HandlerFunc    get     HandlerFunc    head    HandlerFunc    options HandlerFunc    patch   HandlerFunc    post    HandlerFunc    put     HandlerFunc    trace   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 path  pnames := []string{} // Param names
for 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 param  l := len(pnames)  if *e.maxParam < l {    *e.maxParam = l  }
cn := r.tree // Current node as root if cn == nil { panic("echo => invalid method") } search := path
for { sl := len(search) pl := len(cn.prefix) l := 0
// LCP max := pl if sl < max { max = sl } for ; l < max && search[l] == cn.prefix[l]; l++ { }
if l == 0 { // At root node cn.label = search[0] cn.prefix = search if h != nil { cn.kind = t cn.addHandler(method, h) cn.ppath = ppath cn.pnames = pnames cn.echo = e } } else if l < pl { // Split node n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames, cn.echo)
// Reset parent node cn.kind = skind cn.label = cn.prefix[0] cn.prefix = cn.prefix[:l] cn.children = nil cn.methodHandler = new(methodHandler) cn.ppath = "" cn.pnames = nil cn.echo = nil
cn.addChild(n)
if l == sl { // At parent node cn.kind = t cn.addHandler(method, h) cn.ppath = ppath cn.pnames = pnames cn.echo = e } else { // Create child node n = 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 deeper cn = c continue } // Create child node n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames, e) n.addHandler(method, h) cn.addChild(n) } else { // Node already exists if h != nil { cn.addHandler(method, h) cn.ppath = ppath cn.pnames = pnames cn.echo = e } } return }}


接着就是路由查找的过程

func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo) {  // r.tree.printTree("", true)  h = notFoundHandler  e = r.echo  cn := r.tree // Current node as root
var ( search = path c *node // Child node n int // Param counter nk kind // Next kind nn *node // Next node ns string // Next search )
// Search order static > param > any for { if search == "" { goto End }
pl := 0 // Prefix length l := 0 // LCP length
if cn.label != ':' { sl := len(search) pl = len(cn.prefix)
// LCP max := pl if sl < max { max = sl } for ; l < max && search[l] == cn.prefix[l]; l++ { } }
if l == pl { // Continue search search = search[l:] } else { cn = nn search = ns if nk == pkind { goto Param } else if nk == akind { goto Any } // Not found return }
if search == "" { goto End }
// Static node if c = cn.findChild(search[0], skind); c != nil { // Save next if cn.prefix[len(cn.prefix)-1] == '/' { nk = pkind nn = cn ns = search } cn = c continue }
// Param node Param: if c = cn.findChildByKind(pkind); c != nil { // Issue #378 if len(ctx.pvalues) == n { continue }
// Save next if cn.prefix[len(cn.prefix)-1] == '/' { nk = akind nn = cn ns = search } cn = c
i, l := 0, len(search) for ; i < l && search[i] != '/'; i++ { } ctx.pvalues[n] = search[:i] n++ search = search[i:] continue }
// Any node Any: if cn = cn.findChildByKind(akind); cn == nil { if nn != nil { cn = nn nn = nil // Next search = ns if nk == pkind { goto Param } else if nk == akind { goto Any } } // Not found return } ctx.pvalues[len(cn.pnames)-1] = search goto End }
End: ctx.path = cn.ppath ctx.pnames = cn.pnames h = 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 = v exists = true break } } }
if !exists { continue }
// Call this first, in case we're dealing with an alias to an array type if 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        *Echo    beforeFuncs []func()    afterFuncs  []func()    Writer      http.ResponseWriter    Status      int    Size        int64    Committed   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 = code  r.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     string    middleware []MiddlewareFunc    echo       *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 {  // Defaults  if 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 credentials valid, err := config.Validator(cred[:i], cred[i+1:], c) if err != nil { return err } else if valid { return next(c) } break } } }
realm := defaultRealm if 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.RemoteAddr if 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.Method path := req.URL.Path if 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)    }  }}


推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。

浏览 34
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报