前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >echo源码分析

echo源码分析

作者头像
golangLeetcode
发布2022-08-02 19:09:28
3.7K0
发布2022-08-02 19:09:28
举报

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个文件和三个目录组成。

代码语言:javascript
复制
binder.go
context.go
echo.go
group.go
response.go
router.go
middleware
_fixture
website

其中middleware里面定义了最基本最常用的四个中间件

代码语言:javascript
复制
auth.go
compress.go
logger.go
recover.go

_fixture是一些网页资源

代码语言:javascript
复制
 % ls _fixture
favicon.ico  folder    images    index.html

website 是说明文档,中间有个Dockerfile 可以在本地编译镜像,跑起来

代码语言:javascript
复制
 % ls website
Dockerfile  config.json  layouts
argo.json  content    static

首先我们看下如何使用echo

代码语言:javascript
复制
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 文件里面

代码语言:javascript
复制
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文件里,后面介绍

代码语言:javascript
复制
  Binder interface {
    Bind(i interface{}, c Context) error
  }

接着设置了日志级别为ERROR,设置了标准输出Logger,然后初始化了一个Context 对象

代码语言:javascript
复制
// 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定义如下

代码语言:javascript
复制
  Map map[string]interface{}

context里面存储了

代码语言:javascript
复制
r *http.Request, w http.ResponseWriter

这也就是为什么写echo的controller的时候只用传 context对象就行,而标准的http包需要顶替包含参数r *http.Request, w http.ResponseWriter的HandleFunc

最后初始化了路由(router.go)里面实现的

代码语言:javascript
复制
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的定义如下

代码语言:javascript
复制
  Router struct {
    tree   *node
    routes map[string]*Route
    echo   *Echo
  }

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

代码语言:javascript
复制
  node struct {
    kind          kind
    label         byte
    prefix        string
    parent        *node
    children      children
    ppath         string
    pnames        []string
    methodHandler *methodHandler
  }

接着我们看下如何定义路由的,已get请求为例

代码语言:javascript
复制
  e.GET("/stats", func(c echo.Context) error {
    return c.JSON(200, s.Data())
  })

它的定义如下

代码语言:javascript
复制
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请求的方法,请求路径,对应的处理函数和中间件参数。

代码语言:javascript
复制
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定义如下

代码语言:javascript
复制
  Route struct {
    Method string `json:"method"`
    Path   string `json:"path"`
    Name   string `json:"name"`
  }

Method 存的是通过反射获取handler的名字

代码语言:javascript
复制
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函数

代码语言:javascript
复制
e.Logger.Fatal(e.Start(":1323"))
代码语言:javascript
复制
func (e *Echo) Start(address string) error {
  e.Server.Addr = address
  return e.StartServer(e.Server)
}
代码语言:javascript
复制
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的执行流程

代码语言:javascript
复制
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)

看下echo是怎么实现ServeHTTP方法的

代码语言:javascript
复制
// 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的核心逻辑基本讲完了,里面还定义了一系列的辅助类型和方法

代码语言:javascript
复制

  // 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
  }
代码语言:javascript
复制
// HTTP methods
// NOTE: Deprecated, please use the stdlib constants directly instead.
const (
  CONNECT = http.MethodConnect
  DELETE  = http.MethodDelete
代码语言:javascript
复制
// MIME types
const (
  MIMEApplicationJSON                  = "application/json"
  MIMEApplicationJSONCharsetUTF8       = MIMEApplicationJSON + "; " + charsetUTF8
代码语言:javascript
复制
// Headers
const (
  HeaderAccept              = "Accept"
  HeaderAcceptEncoding      = "Accept-Encoding"
代码语言:javascript
复制
// Errors
var (
  ErrUnsupportedMediaType        = NewHTTPError(http.StatusUnsupportedMediaType)
代码语言:javascript
复制
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
// with status code.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
代码语言:javascript
复制
// 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中

代码语言:javascript
复制
// 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文件

首先定义了

代码语言:javascript
复制
Context interface {}

和对应的对象

代码语言:javascript
复制
  context struct {
    request  *http.Request
    response *Response
    path     string
    pnames   []string
    pvalues  []string
    query    url.Values
    handler  HandlerFunc
    store    Map
    echo     *Echo
  }

获取参数值

代码语言:javascript
复制
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 ""
}
代码语言:javascript
复制
func (c *context) QueryParam(name string) string {
  if c.query == nil {
    c.query = c.request.URL.Query()
  }
  return c.query.Get(name)
}
代码语言:javascript
复制
func (c *context) FormValue(name string) string {
  return c.request.FormValue(name)
}

参数绑定

代码语言:javascript
复制
func (c *context) Bind(i interface{}) error {
  return c.echo.Binder.Bind(i, c)
}

参数校验

代码语言:javascript
复制
func (c *context) Validate(i interface{}) error {
  if c.echo.Validator == nil {
    return ErrValidatorNotRegistered
  }
  return c.echo.Validator.Validate(i)
}

输出json

代码语言:javascript
复制
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的一系列结构体

代码语言:javascript
复制
  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
  }

一系列处理方法

代码语言:javascript
复制
  methodHandler struct {
    connect HandlerFunc
    delete  HandlerFunc
    get     HandlerFunc
    head    HandlerFunc
    options HandlerFunc
    patch   HandlerFunc
    post    HandlerFunc
    put     HandlerFunc
    trace   HandlerFunc
  }

初始化一个router对象

代码语言:javascript
复制
func NewRouter(e *Echo) *Router {
  return &Router{
    tree: &node{
      methodHandler: new(methodHandler),
    },
    routes: []Route{},
    echo:   e,
  }
}

添加路由,构建tire树

代码语言:javascript
复制
// 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树

代码语言:javascript
复制
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
  }
}

接着就是路由查找的过程

代码语言:javascript
复制
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,然后调用的。

代码语言:javascript
复制
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来实现绑定

代码语言:javascript
复制
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实现的

代码语言:javascript
复制
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做了一个包装

代码语言:javascript
复制
  Response struct {
    echo        *Echo
    beforeFuncs []func()
    afterFuncs  []func()
    Writer      http.ResponseWriter
    Status      int
    Size        int64
    Committed   bool
  }

写响应头

代码语言:javascript
复制
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

代码语言:javascript
复制
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的复杂路由场景很有用

代码语言:javascript
复制
  Group struct {
    prefix     string
    middleware []MiddlewareFunc
    echo       *Echo
  }

和普通路由的区别是path存储变成了prefix+path

代码语言:javascript
复制
// 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

代码语言:javascript
复制
  // 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)
代码语言:javascript
复制
// 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压缩

代码语言:javascript
复制
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等等

代码语言:javascript
复制
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操作

代码语言:javascript
复制
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)
    }
  }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档