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