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 個文件和三個目錄組成。

 1binder.go
 2context.go
 3echo.go
 4group.go
 5response.go
 6router.go
 7middleware
 8_fixture
 9website
10

其中 middleware 裏面定義了最基本最常用的四個中間件

1auth.go
2compress.go
3logger.go
4recover.go
5

_fixture 是一些網頁資源

1 % ls _fixture
2favicon.ico  folder    images    index.html
3

website 是說明文檔,中間有個 Dockerfile 可以在本地編譯鏡像,跑起來

1 % ls website
2Dockerfile  config.json  layouts
3argo.json  content    static
4

首先我們看下如何使用 echo

 1package main
 2import (
 3  "net/http"
 4  "github.com/labstack/echo/v4"
 5  "github.com/labstack/echo/v4/middleware"
 6)
 7func main() {
 8  // 創建一個echo實例
 9  e := echo.New()
10  // 註冊中間件
11  // 需要我們在入口文件手動注入基礎中間件
12  e.Use(middleware.Logger())
13  e.Use(middleware.Recover())
14  // 註冊路由
15  e.GET("/", hello)
16  // 啓動服務
17  e.Logger.Fatal(e.Start(":1323"))
18}
19// 路由handle提出來了而已
20// 匿名函數方式 不重要
21func hello(c echo.Context) error {
22  return c.String(http.StatusOK, "Hello, World!")
23}
24

1,echo.go 文件

New 函數定義在 echo.go 文件裏面

 1func New() (e *Echo) {
 2  e = &Echo{
 3    // 創建一個http Server指針
 4    Server:    new(http.Server),
 5    // 創建一個https的 Server指針
 6    TLSServer: new(http.Server),
 7    AutoTLSManager: autocert.Manager{
 8      Prompt: autocert.AcceptTOS,
 9    },
10    // 日誌實例
11    Logger:   log.New("echo"),
12    // 控制檯、日誌可以彩色輸出的實例
13    colorer:  color.New(),
14    maxParam: new(int),
15  }
16  // http server綁定實現了server.Handler的實例
17  // 也就是說Echo框架自身實現了http.Handler接口
18  e.Server.Handler = e
19  // https server綁定實現了server.Handler的實例
20  e.TLSServer.Handler = e
21  // 綁定http服務異常處理的handler
22  e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
23  // 
24  e.Binder = &DefaultBinder{}
25  // 設置日誌輸出級別
26  e.Logger.SetLevel(log.ERROR)
27  // 綁定標準日誌輸出實例
28  e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
29  // 綁定獲取請求上下文實例的閉包
30  e.pool.New = func() interface{} {
31    return e.NewContext(nil, nil)
32  }
33  // 綁定路由實例
34  e.router = NewRouter(e)
35  // 綁定路由map
36  // 注意這個屬性的含義:路由分組用的,key爲host,則按host分組
37  // 記住與Router.routes區別
38  // Router.routes存的路由的信息(不包含路由的handler)
39  e.routers = map[string]*Router{}
40  return
41}
42

手先初始化了一個 echo 對象,然後定義了一個 DefaultBinder,實現了 Binder 接口,這個接口定義在 bind.go 文件裏,後面介紹

1  Binder interface {
2    Bind(i interface{}, c Context) error
3  }
4

接着設置了日誌級別爲 ERROR,設置了標準輸出 Logger,然後初始化了一個 Context 對象

 1// NewContext returns a Context instance.
 2func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
 3  return &context{
 4    request:  r,
 5    response: NewResponse(w, e),
 6    store:    make(Map),
 7    echo:     e,
 8    pvalues:  make([]string, *e.maxParam),
 9    handler:  NotFoundHandler,
10  }
11}
12

context 對象和 interface 都定義在 context.go 文件中,後面講解 context .go 文件的時候詳細講解.

其中的 Map 定義如下

1  Map map[string]interface{}
2

context 裏面存儲了

1r *http.Request, w http.ResponseWriter
2

這也就是爲什麼寫 echo 的 controller 的時候只用傳 context 對象就行,而標準的 http 包需要頂替包含參數 r *http.Request, w http.ResponseWriter 的 HandleFunc

最後初始化了路由(router.go)裏面實現的

 1func NewRouter(e *Echo) *Router {
 2  // 初始化Router
 3  return &Router{
 4    // 路由樹
 5    // 路由的信息(包含路由的handler)
 6    // 查找路由用的LCP (最長公共前綴)算法
 7    tree: &node{
 8      // 節點對應的不同http method的handler
 9      methodHandler: new(methodHandler),
10    },
11    // Router.routes存的路由的信息(不包含路由的handler)
12    routes: map[string]*Route{},
13    // 框架實例自身
14    echo:   e,
15  }
16

Router 的定義如下

1  Router struct {
2    tree   *node
3    routes map[string]*Route
4    echo   *Echo
5  }
6

是一個棵多叉樹,其中 tree 存儲了當前節點裏面的值,它的定義如下

 1  node struct {
 2    kind          kind
 3    label         byte
 4    prefix        string
 5    parent        *node
 6    children      children
 7    ppath         string
 8    pnames        []string
 9    methodHandler *methodHandler
10  }
11

接着我們看下如何定義路由的,已 get 請求爲例

1  e.GET("/stats", func(c echo.Context) error {
2    return c.JSON(200, s.Data())
3  })
4

它的定義如下

1func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
2  return e.Add(http.MethodGet, path, h, m...)
3}
4

我們可以看到,它的內部調用了 Add 方法,其實 POST、PATCH、DELETE 等 http method 類似都是對 Add 方法進行了包裹

Add 方法的參數有 http 請求的方法,請求路徑,對應的處理函數和中間件參數。

 1func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
 2  // 獲取handler的名稱
 3  // 😨這個方法裏面盡然用了反射獲取name 只是個name有必要麼 沒別的辦法了嗎?
 4  name := handlerName(handler)
 5  // 註冊路由
 6  // 注意第三個參數是個閉包 匹配到路由就會執行這個閉包
 7  e.router.Add(method, path, func(c Context) error {
 8    h := handler
 9    // Chain middleware
10    for i := len(middleware) - 1; i >= 0; i-- {
11       // 注意這裏的中間件是這個路由專屬的
12      // 而Use、Pre註冊的中間件是全局公共的
13      // 遍歷中間件
14      // 注意返回值類型是HandlerFunc
15      //典型的洋蔥模式
16      h = middleware[i](h)
17    }
18     // 執行最後一箇中間件
19    return h(c)
20  })
21  // 本次註冊進來的路由的信息,只存放了handler的方法名
22  r := &Route{
23    Method: method,
24    Path:   path,
25    Name:   name,
26  }
27  // map存路由信息
28  e.router.routes[method+path] = r
29  return r
30}
31

注意這裏的 Route 不是 Router 定義如下

1  Route struct {
2    Method string `json:"method"`
3    Path   string `json:"path"`
4    Name   string `json:"name"`
5  }
6

Method 存的是通過反射獲取 handler 的名字

1func handlerName(h HandlerFunc) string {
2  t := reflect.ValueOf(h).Type()
3  if t.Kind() == reflect.Func {
4    return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
5  }
6  return t.String()
7}
8

最後看下 start 函數

1e.Logger.Fatal(e.Start(":1323"))
2
1func (e *Echo) Start(address string) error {
2  e.Server.Addr = address
3  return e.StartServer(e.Server)
4}
5
 1func (e *Echo) StartServer(s *http.Server) (err error) {
 2  // Setup
 3  e.colorer.SetOutput(e.Logger.Output())
 4  s.ErrorLog = e.StdLogger
 5  // 設置框架實例到http server的Handler
 6  // Echo框架結構體實現了http.Handler接口
 7  s.Handler = e
 8  if e.Debug {
 9    e.Logger.SetLevel(log.DEBUG)
10  }
11  if !e.HideBanner {
12    e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
13  }
14  if s.TLSConfig == nil {
15    if e.Listener == nil {
16     // 監聽ip+port
17      e.Listener, err = newListener(s.Addr)
18      if err != nil {
19        return err
20      }
21    }
22    if !e.HidePort {
23      e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
24    }
25    // 啓動http server
26    return s.Serve(e.Listener)
27  }
28  if e.TLSListener == nil {
29     // 設置https配置
30    l, err := newListener(s.Addr)
31    if err != nil {
32      return err
33    }
34    e.TLSListener = tls.NewListener(l, s.TLSConfig)
35  }
36  if !e.HidePort {
37    e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
38  }
39  return s.Serve(e.TLSListener)
40}
41

接下來的流程就是標準 httpserver 的執行流程

 1s.Serve()
 2⬇️
 3// accept網絡請求
 4rw, e := l.Accept()
 5⬇️
 6// goroutine處理請求
 7go c.serve(ctx)
 8⬇️
 9// 執行serverHandler的ServeHTTP
10serverHandler{c.server}.ServeHTTP(w, w.req)
11⬇️
12// 執行當前框架實例的ServeHTTP方法
13handler.ServeHTTP(rw, req)
14

看下 echo 是怎麼實現 ServeHTTP 方法的

 1// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
 2func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 3  // Acquire context
 4  // 獲取上下文實例
 5  c := e.pool.Get().(*context)
 6  c.Reset(r, w)
 7  h := NotFoundHandler
 8  // 不存在預執行中間件時
 9  // 說說這個預執行中間件的含義:
10  // 看源碼註釋的含義是在尋找到路由之前執行的中間件
11  // 簡單來說和普通中間件的的區別就是,還沒走到匹配路由的邏輯就會執行的中間件,從下面來看只是代碼邏輯的區別,實際的中間件執行順序還是誰先註冊誰先執行。所以無論是存在普通中間件還是預執行中間件,路由的handle總是最後執行。
12  // 個人感覺預執行中間件的意義不大
13  if e.premiddleware == nil {
14    // 先找當前host組的router
15    // LCP算法尋找當前path的handler
16    e.router.Find(r.Method, getPath(r), c)
17    h = c.Handler()
18    for i := len(e.middleware) - 1; i >= 0; i-- {
19      h = e.middleware[i](h)
20    }
21  } else {
22     // 看見這個預執行中間件的區別了吧
23    // 把註冊普通中間件的邏輯又包裝成了一個HandlerFunc註冊到中間件鏈中
24    h = func(c Context) error {
25      e.router.Find(r.Method, getPath(r), c)
26      h := c.Handler()
27      for i := len(e.middleware) - 1; i >= 0; i-- {
28        h = e.middleware[i](h)
29      }
30      return h(c)
31    }
32    for i := len(e.premiddleware) - 1; i >= 0; i-- {
33      h = e.premiddleware[i](h)
34    }
35  }
36  // Execute chain
37    // 執行中間件鏈
38  // 在applyMiddleware中所有中間件構成了一個鏈
39  if err := h(c); err != nil {
40    e.HTTPErrorHandler(err, c)
41  }
42  // Release context
43  // 釋放上下文
44  e.pool.Put(c)
45}
46

echo.go 的核心邏輯基本講完了,裏面還定義了一系列的輔助類型和方法

 1  // MiddlewareFunc defines a function to process middleware.
 2  MiddlewareFunc func(HandlerFunc) HandlerFunc
 3  // HandlerFunc defines a function to serve HTTP requests.
 4  HandlerFunc func(Context) error
 5  // HTTPErrorHandler is a centralized HTTP error handler.
 6  HTTPErrorHandler func(error, Context)
 7  // Validator is the interface that wraps the Validate function.
 8  Validator interface {
 9    Validate(i interface{}) error
10  }
11  // Renderer is the interface that wraps the Render function.
12  Renderer interface {
13    Render(io.Writer, string, interface{}, Context) error
14  }
15  // i is the interface for Echo and Group.
16  i interface {
17    GET(string, HandlerFunc, ...MiddlewareFunc) *Route
18  }
19
1// HTTP methods
2// NOTE: Deprecated, please use the stdlib constants directly instead.
3const (
4  CONNECT = http.MethodConnect
5  DELETE  = http.MethodDelete
6
1// MIME types
2const (
3  MIMEApplicationJSON                  = "application/json"
4  MIMEApplicationJSONCharsetUTF8       = MIMEApplicationJSON + "; " + charsetUTF8
5
1// Headers
2const (
3  HeaderAccept              = "Accept"
4  HeaderAcceptEncoding      = "Accept-Encoding"
5
1// Errors
2var (
3  ErrUnsupportedMediaType        = NewHTTPError(http.StatusUnsupportedMediaType)
4
1// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
2// with status code.
3func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
4
 1// Static registers a new route with path prefix to serve static files from the
 2// provided root directory.
 3func (e *Echo) Static(prefix, root string) *Route {
 4  if root == "" {
 5    root = "." // For security we want to restrict to CWD.
 6  }
 7  return static(e, prefix, root)
 8}
 9func static(i i, prefix, root string) *Route {
10  h := func(c Context) error {
11    p, err := url.PathUnescape(c.Param("*"))
12    if err != nil {
13      return err
14    }
15    name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
16    return c.File(name)
17  }
18  i.GET(prefix, h)
19  if prefix == "/" {
20    return i.GET(prefix+"*", h)
21  }
22  return i.GET(prefix+"/*", h)
23}
24

把 url 中的 namd/:id 中的 id 解析出來存到 url 中

 1// Reverse generates an URL from route name and provided parameters.
 2func (e *Echo) Reverse(name string, params ...interface{}) string {
 3  uri := new(bytes.Buffer)
 4  ln := len(params)
 5  n := 0
 6  for _, r := range e.router.routes {
 7    if r.Name == name {
 8      for i, l := 0, len(r.Path); i < l; i++ {
 9        if r.Path[i] == ':' && n < ln {
10          for ; i < l && r.Path[i] != '/'; i++ {
11          }
12          uri.WriteString(fmt.Sprintf("%v", params[n]))
13          n++
14        }
15        if i < l {
16          uri.WriteByte(r.Path[i])
17        }
18      }
19      break
20    }
21  }
22  return uri.String()
23}
24

2,context.go 文件

首先定義了

1Context interface {}
2

和對應的對象

 1  context struct {
 2    request  *http.Request
 3    response *Response
 4    path     string
 5    pnames   []string
 6    pvalues  []string
 7    query    url.Values
 8    handler  HandlerFunc
 9    store    Map
10    echo     *Echo
11  }
12

獲取參數值

 1func (c *context) Param(name string) string {
 2  for i, n := range c.pnames {
 3    if i < len(c.pvalues) {
 4      if n == name {
 5        return c.pvalues[i]
 6      }
 7    }
 8  }
 9  return ""
10}
11
1func (c *context) QueryParam(name string) string {
2  if c.query == nil {
3    c.query = c.request.URL.Query()
4  }
5  return c.query.Get(name)
6}
7
1func (c *context) FormValue(name string) string {
2  return c.request.FormValue(name)
3}
4

參數綁定

1func (c *context) Bind(i interface{}) error {
2  return c.echo.Binder.Bind(i, c)
3}
4

參數校驗

1func (c *context) Validate(i interface{}) error {
2  if c.echo.Validator == nil {
3    return ErrValidatorNotRegistered
4  }
5  return c.echo.Validator.Validate(i)
6}
7

輸出 json

 1func (c *context) json(code int, i interface{}, indent string) error {
 2  enc := json.NewEncoder(c.response)
 3  if indent != "" {
 4    enc.SetIndent("", indent)
 5  }
 6  c.writeContentType(MIMEApplicationJSONCharsetUTF8)
 7  c.response.WriteHeader(code)
 8  return enc.Encode(i)
 9}
10

3,router.go

主要是定義了 Router 的一系列結構體

 1  Router struct {
 2    tree   *node
 3    routes []Route
 4    echo   *Echo
 5  }
 6  node struct {
 7    kind          kind
 8    label         byte
 9    prefix        string
10    parent        *node
11    children      children
12    ppath         string
13    pnames        []string
14    methodHandler *methodHandler
15    echo          *Echo
16  }
17

一系列處理方法

 1  methodHandler struct {
 2    connect HandlerFunc
 3    delete  HandlerFunc
 4    get     HandlerFunc
 5    head    HandlerFunc
 6    options HandlerFunc
 7    patch   HandlerFunc
 8    post    HandlerFunc
 9    put     HandlerFunc
10    trace   HandlerFunc
11  }
12

初始化一個 router 對象

 1func NewRouter(e *Echo) *Router {
 2  return &Router{
 3    tree: &node{
 4      methodHandler: new(methodHandler),
 5    },
 6    routes: []Route{},
 7    echo:   e,
 8  }
 9}
10

添加路由,構建 tire 樹

 1// Add registers a new route with a matcher for the URL path.
 2func (r *Router) Add(method, path string, h HandlerFunc, e *Echo) {
 3  ppath := path        // Pristine path
 4  pnames := []string{} // Param names
 5  for i, l := 0, len(path); i < l; i++ {
 6    if path[i] == ':' {
 7      j := i + 1
 8      //提取路由中的參數前面部分,構建樹
 9      r.insert(method, path[:i], nil, skind, "", nil, e)
10      for ; i < l && path[i] != '/'; i++ {
11      }
12      //把參數名放到pnames裏面
13      pnames = append(pnames, path[j:i])
14      path = path[:j] + path[i:]
15      i, l = j, len(path)
16      if i == l {
17        r.insert(method, path[:i], h, pkind, ppath, pnames, e)
18        return
19      }
20      r.insert(method, path[:i], nil, pkind, ppath, pnames, e)
21    } else if path[i] == '*' {
22      r.insert(method, path[:i], nil, skind, "", nil, e)
23      pnames = append(pnames, "_*")
24      r.insert(method, path[:i+1], h, akind, ppath, pnames, e)
25      return
26    }
27  }
28  r.insert(method, path, h, skind, ppath, pnames, e)
29}
30

提取參數和正則以後,構建 tire 樹

 1func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string, e *Echo) {
 2  // Adjust max param
 3  l := len(pnames)
 4  if *e.maxParam < l {
 5    *e.maxParam = l
 6  }
 7  cn := r.tree // Current node as root
 8  if cn == nil {
 9    panic("echo => invalid method")
10  }
11  search := path
12  for {
13    sl := len(search)
14    pl := len(cn.prefix)
15    l := 0
16    // LCP
17    max := pl
18    if sl < max {
19      max = sl
20    }
21    for ; l < max && search[l] == cn.prefix[l]; l++ {
22    }
23    if l == 0 {
24      // At root node
25      cn.label = search[0]
26      cn.prefix = search
27      if h != nil {
28        cn.kind = t
29        cn.addHandler(method, h)
30        cn.ppath = ppath
31        cn.pnames = pnames
32        cn.echo = e
33      }
34    } else if l < pl {
35      // Split node
36      n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames, cn.echo)
37      // Reset parent node
38      cn.kind = skind
39      cn.label = cn.prefix[0]
40      cn.prefix = cn.prefix[:l]
41      cn.children = nil
42      cn.methodHandler = new(methodHandler)
43      cn.ppath = ""
44      cn.pnames = nil
45      cn.echo = nil
46      cn.addChild(n)
47      if l == sl {
48        // At parent node
49        cn.kind = t
50        cn.addHandler(method, h)
51        cn.ppath = ppath
52        cn.pnames = pnames
53        cn.echo = e
54      } else {
55        // Create child node
56        n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames, e)
57        n.addHandler(method, h)
58        cn.addChild(n)
59      }
60    } else if l < sl {
61      search = search[l:]
62      c := cn.findChildWithLabel(search[0])
63      if c != nil {
64        // Go deeper
65        cn = c
66        continue
67      }
68      // Create child node
69      n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames, e)
70      n.addHandler(method, h)
71      cn.addChild(n)
72    } else {
73      // Node already exists
74      if h != nil {
75        cn.addHandler(method, h)
76        cn.ppath = ppath
77        cn.pnames = pnames
78        cn.echo = e
79      }
80    }
81    return
82  }
83}
84

接着就是路由查找的過程

  1func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo) {
  2  // r.tree.printTree("", true)
  3  h = notFoundHandler
  4  e = r.echo
  5  cn := r.tree // Current node as root
  6  var (
  7    search = path
  8    c      *node  // Child node
  9    n      int    // Param counter
 10    nk     kind   // Next kind
 11    nn     *node  // Next node
 12    ns     string // Next search
 13  )
 14  // Search order static > param > any
 15  for {
 16    if search == "" {
 17      goto End
 18    }
 19    pl := 0 // Prefix length
 20    l := 0  // LCP length
 21    if cn.label != ':' {
 22      sl := len(search)
 23      pl = len(cn.prefix)
 24      // LCP
 25      max := pl
 26      if sl < max {
 27        max = sl
 28      }
 29      for ; l < max && search[l] == cn.prefix[l]; l++ {
 30      }
 31    }
 32    if l == pl {
 33      // Continue search
 34      search = search[l:]
 35    } else {
 36      cn = nn
 37      search = ns
 38      if nk == pkind {
 39        goto Param
 40      } else if nk == akind {
 41        goto Any
 42      }
 43      // Not found
 44      return
 45    }
 46    if search == "" {
 47      goto End
 48    }
 49    // Static node
 50    if c = cn.findChild(search[0], skind); c != nil {
 51      // Save next
 52      if cn.prefix[len(cn.prefix)-1] == '/' {
 53        nk = pkind
 54        nn = cn
 55        ns = search
 56      }
 57      cn = c
 58      continue
 59    }
 60    // Param node
 61  Param:
 62    if c = cn.findChildByKind(pkind); c != nil {
 63      // Issue #378
 64      if len(ctx.pvalues) == n {
 65        continue
 66      }
 67      // Save next
 68      if cn.prefix[len(cn.prefix)-1] == '/' {
 69        nk = akind
 70        nn = cn
 71        ns = search
 72      }
 73      cn = c
 74      i, l := 0, len(search)
 75      for ; i < l && search[i] != '/'; i++ {
 76      }
 77      ctx.pvalues[n] = search[:i]
 78      n++
 79      search = search[i:]
 80      continue
 81    }
 82    // Any node
 83  Any:
 84    if cn = cn.findChildByKind(akind); cn == nil {
 85      if nn != nil {
 86        cn = nn
 87        nn = nil // Next
 88        search = ns
 89        if nk == pkind {
 90          goto Param
 91        } else if nk == akind {
 92          goto Any
 93        }
 94      }
 95      // Not found
 96      return
 97    }
 98    ctx.pvalues[len(cn.pnames)-1] = search
 99    goto End
100  }
101End:
102  ctx.path = cn.ppath
103  ctx.pnames = cn.pnames
104  h = cn.findHandler(method)
105  if cn.echo != nil {
106    e = cn.echo
107  }
108  // NOTE: Slow zone...
109  if h == nil {
110    h = cn.check405()
111    // Dig further for any, might have an empty value for *, e.g.
112    // serving a directory. Issue #207.
113    if cn = cn.findChildByKind(akind); cn == nil {
114      return
115    }
116    ctx.pvalues[len(cn.pnames)-1] = ""
117    if h = cn.findHandler(method); h == nil {
118      h = cn.check405()
119    }
120  }
121  return
122}
123

最後是 serveHTTP 方法,這個方法是在 echo 的同名方法裏,把 router 轉化成 handleFunc,然後調用的。

 1func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 2  c := r.echo.pool.Get().(*Context)
 3  h, _ := r.Find(req.Method, req.URL.Path, c)
 4  c.reset(req, w, r.echo)
 5  if err := h(c); err != nil {
 6    r.echo.httpErrorHandler(err, c)
 7  }
 8  r.echo.pool.Put(c)
 9}
10

4,binder.go

defaultBinder 實現了 Bind 方法,通過 http 方法以及 header 裏面的 ContentType 來實現綁定

 1func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
 2  req := c.Request()
 3  if req.ContentLength == 0 {
 4    if req.Method == http.MethodGet || req.Method == http.MethodDelete {
 5      if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
 6        return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
 7      }
 8      return
 9    }
10    return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
11  }
12  ctype := req.Header.Get(HeaderContentType)
13  switch {
14  case strings.HasPrefix(ctype, MIMEApplicationJSON):
15    if err = json.NewDecoder(req.Body).Decode(i); err != nil {
16      if ute, ok := err.(*json.UnmarshalTypeError); ok {
17        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)
18      } else if se, ok := err.(*json.SyntaxError); ok {
19        return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
20      } else {
21        return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
22      }
23      return NewHTTPError(http.StatusBadRequest, err.Error())
24    }
25  case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
26    if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
27      if ute, ok := err.(*xml.UnsupportedTypeError); ok {
28        return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
29      } else if se, ok := err.(*xml.SyntaxError); ok {
30        return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
31      } else {
32        return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
33      }
34      return NewHTTPError(http.StatusBadRequest, err.Error())
35    }
36  case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
37    params, err := c.FormParams()
38    if err != nil {
39      return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
40    }
41    if err = b.bindData(i, params, "form"); err != nil {
42      return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
43    }
44  default:
45    return ErrUnsupportedMediaType
46  }
47  return
48}
49

綁定數據的過程是通過 reflect 實現的

 1func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
 2  typ := reflect.TypeOf(ptr).Elem()
 3  val := reflect.ValueOf(ptr).Elem()
 4  if typ.Kind() != reflect.Struct {
 5    return errors.New("binding element must be a struct")
 6  }
 7  for i := 0; i < typ.NumField(); i++ {
 8    typeField := typ.Field(i)
 9    structField := val.Field(i)
10    if !structField.CanSet() {
11      continue
12    }
13    structFieldKind := structField.Kind()
14    inputFieldName := typeField.Tag.Get(tag)
15    if inputFieldName == "" {
16      inputFieldName = typeField.Name
17      // If tag is nil, we inspect if the field is a struct.
18      if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
19        if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
20          return err
21        }
22        continue
23      }
24    }
25    inputValue, exists := data[inputFieldName]
26    if !exists {
27      // Go json.Unmarshal supports case insensitive binding.  However the
28      // url params are bound case sensitive which is inconsistent.  To
29      // fix this we must check all of the map values in a
30      // case-insensitive search.
31      inputFieldName = strings.ToLower(inputFieldName)
32      for k, v := range data {
33        if strings.ToLower(k) == inputFieldName {
34          inputValue = v
35          exists = true
36          break
37        }
38      }
39    }
40    if !exists {
41      continue
42    }
43    // Call this first, in case we're dealing with an alias to an array type
44    if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
45      if err != nil {
46        return err
47      }
48      continue
49    }
50    numElems := len(inputValue)
51    if structFieldKind == reflect.Slice && numElems > 0 {
52      sliceOf := structField.Type().Elem().Kind()
53      slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
54      for j := 0; j < numElems; j++ {
55        if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
56          return err
57        }
58      }
59      val.Field(i).Set(slice)
60    } else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
61      return err
62    }
63  }
64  return nil
65}
66

5,response.go

對 http response 做了一個包裝

 1  Response struct {
 2    echo        *Echo
 3    beforeFuncs []func()
 4    afterFuncs  []func()
 5    Writer      http.ResponseWriter
 6    Status      int
 7    Size        int64
 8    Committed   bool
 9  }
10

寫響應頭

 1func (r *Response) WriteHeader(code int) {
 2  if r.Committed {
 3    r.echo.Logger.Warn("response already committed")
 4    return
 5  }
 6  for _, fn := range r.beforeFuncs {
 7    fn()
 8  }
 9  r.Status = code
10  r.Writer.WriteHeader(code)
11  r.Committed = true
12}
13

寫響應 body

 1func (r *Response) Write(b []byte) (n int, err error) {
 2  if !r.Committed {
 3    r.WriteHeader(http.StatusOK)
 4  }
 5  n, err = r.Writer.Write(b)
 6  r.Size += int64(n)
 7  for _, fn := range r.afterFuncs {
 8    fn()
 9  }
10  return
11}
12

6,group.go

group 主要是定義了子路由,針對大家有一個共同 ns 的複雜路由場景很有用

1  Group struct {
2    prefix     string
3    middleware []MiddlewareFunc
4    echo       *Echo
5  }
6

和普通路由的區別是 path 存儲變成了 prefix+path

 1// Add implements `Echo#Add()` for sub-routes within the Group.
 2func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
 3  // Combine into a new slice to avoid accidentally passing the same slice for
 4  // multiple routes, which would lead to later add() calls overwriting the
 5  // middleware from earlier calls.
 6  m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
 7  m = append(m, g.middleware...)
 8  m = append(m, middleware...)
 9  return g.echo.Add(method, g.prefix+path, handler, m...)
10}
11

7,middleware

7.1auth.go

實現了 basic auth

 1  // BasicAuthConfig defines the config for BasicAuth middleware.
 2  BasicAuthConfig struct {
 3    // Skipper defines a function to skip middleware.
 4    Skipper Skipper
 5    // Validator is a function to validate BasicAuth credentials.
 6    // Required.
 7    Validator BasicAuthValidator
 8    // Realm is a string to define realm attribute of BasicAuth.
 9    // Default value "Restricted".
10    Realm string
11  }
12  // BasicAuthValidator defines a function to validate BasicAuth credentials.
13  BasicAuthValidator func(string, string, echo.Context) (bool, error)
14
 1// BasicAuthWithConfig returns an BasicAuth middleware with config.
 2// See `BasicAuth()`.
 3func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
 4  // Defaults
 5  if config.Validator == nil {
 6    panic("echo: basic-auth middleware requires a validator function")
 7  }
 8  if config.Skipper == nil {
 9    config.Skipper = DefaultBasicAuthConfig.Skipper
10  }
11  if config.Realm == "" {
12    config.Realm = defaultRealm
13  }
14  return func(next echo.HandlerFunc) echo.HandlerFunc {
15    return func(c echo.Context) error {
16      if config.Skipper(c) {
17        return next(c)
18      }
19      auth := c.Request().Header.Get(echo.HeaderAuthorization)
20      l := len(basic)
21      if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic {
22        b, err := base64.StdEncoding.DecodeString(auth[l+1:])
23        if err != nil {
24          return err
25        }
26        cred := string(b)
27        for i := 0; i < len(cred); i++ {
28          if cred[i] == ':' {
29            // Verify credentials
30            valid, err := config.Validator(cred[:i], cred[i+1:], c)
31            if err != nil {
32              return err
33            } else if valid {
34              return next(c)
35            }
36            break
37          }
38        }
39      }
40      realm := defaultRealm
41      if config.Realm != defaultRealm {
42        realm = strconv.Quote(config.Realm)
43      }
44      // Need to return `401` for browsers to pop-up login box.
45      c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm)
46      return echo.ErrUnauthorized
47    }
48  }
49}
50

7.2compress.go

進行 gzip 壓縮

 1unc Gzip() echo.MiddlewareFunc {
 2  scheme := "gzip"
 3  return func(h echo.HandlerFunc) echo.HandlerFunc {
 4    return func(c *echo.Context) error {
 5      c.Response().Header().Add(echo.Vary, echo.AcceptEncoding)
 6      if strings.Contains(c.Request().Header.Get(echo.AcceptEncoding), scheme) {
 7        w := writerPool.Get().(*gzip.Writer)
 8        w.Reset(c.Response().Writer())
 9        defer func() {
10          w.Close()
11          writerPool.Put(w)
12        }()
13        gw := gzipWriter{Writer: w, ResponseWriter: c.Response().Writer()}
14        c.Response().Header().Set(echo.ContentEncoding, scheme)
15        c.Response().SetWriter(gw)
16      }
17      if err := h(c); err != nil {
18        c.Error(err)
19      }
20      return nil
21    }
22  }
23}
24

7.3logger.go

記錄請求的一些基本信息,如 ip 等等

 1func Logger() echo.MiddlewareFunc {
 2  return func(h echo.HandlerFunc) echo.HandlerFunc {
 3    return func(c *echo.Context) error {
 4      req := c.Request()
 5      res := c.Response()
 6      logger := c.Echo().Logger()
 7      remoteAddr := req.RemoteAddr
 8      if ip := req.Header.Get(echo.XRealIP); ip != "" {
 9        remoteAddr = ip
10      } else if ip = req.Header.Get(echo.XForwardedFor); ip != "" {
11        remoteAddr = ip
12      } else {
13        remoteAddr, _, _ = net.SplitHostPort(remoteAddr)
14      }
15      start := time.Now()
16      if err := h(c); err != nil {
17        c.Error(err)
18      }
19      stop := time.Now()
20      method := req.Method
21      path := req.URL.Path
22      if path == "" {
23        path = "/"
24      }
25      size := res.Size()
26      n := res.Status()
27      code := color.Green(n)
28      switch {
29      case n >= 500:
30        code = color.Red(n)
31      case n >= 400:
32        code = color.Yellow(n)
33      case n >= 300:
34        code = color.Cyan(n)
35      }
36      logger.Printf(format, remoteAddr, method, path, code, stop.Sub(start), size)
37      return nil
38    }
39  }
40}
41

7.4recover.go

對 panic 進行 recover 操作

 1func Recover() echo.MiddlewareFunc {
 2  // TODO: Provide better stack trace `https://github.com/go-errors/errors` `https://github.com/docker/libcontainer/tree/master/stacktrace`
 3  return func(h echo.HandlerFunc) echo.HandlerFunc {
 4    return func(c *echo.Context) error {
 5      defer func() {
 6        if err := recover(); err != nil {
 7          trace := make([]byte, 1<<16)
 8          n := runtime.Stack(trace, true)
 9          c.Error(fmt.Errorf("panic recover\n %v\n stack trace %d bytes\n %s",
10            err, n, trace[:n]))
11        }
12      }()
13      return h(c)
14    }
15  }
16}
17
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nKuABiDTSfYP7OEuf-j9ZA