因为 Echo 框架本身是没有 Main 入口的,我们只能通过官方给的一个小的基础 Demo,作为源码阅读的起点

Demo - Hello World

package main

import (
	"net/http"
	
	"github.com/labstack/echo/v4"
)

func main() {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})
	e.Logger.Fatal(e.Start(":1323"))
}

解析

入口 - New

func New() (e *Echo) {
	e = &Echo{
		Server:    new(http.Server),
		TLSServer: new(http.Server),
		AutoTLSManager: autocert.Manager{
			Prompt: autocert.AcceptTOS,
		},
		Logger:          log.New("echo"),
		colorer:         color.New(),
		maxParam:        new(int),
		ListenerNetwork: "tcp",
	}
	e.Server.Handler = e
	e.TLSServer.Handler = e
	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)
	e.routers = map[string]*Router{}
	return
}

该函数是整个框架的入口,也是用户调用的第一步,它返回了一个 Echo 接口,并同时初始化了几个参数

  • Server
  • TLSServer
  • AutoTLSManager
  • Logger
  • colorer
  • maxParam
  • ListenerNetwork

Server

Echo 其实是基于 Go Http 标准库上层开发的一个框架,因此 Server 直接 New 了一个 http.Server,我们直接查看 http.Server 的底层代码可能理解更方便些,而从源码中能发现,Handler 是一个接口,只要实现了 ServeHTTP 方法,所有 HTTP 请求都将会被交给这个实例处理

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
    Addr           string        // TCP address to listen on, ":http" if empty
    Handler        Handler       // handler to invoke, http.DefaultServeMux if nil
    ......
}

type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.  If
// srv.Addr is blank, ":http" is used.
func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
} 

Server 保存了运行 HTTP 服务需要的参数,也正因为如此,像启动 Echo Server 的时候完全能和标准库语法一致

e.Server.Addr = ":80"
e.Server.ListenAndServe()

路由 - Method

// CONNECT registers a new CONNECT route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
	return e.Add(http.MethodConnect, path, h, m...)
}

// DELETE registers a new DELETE route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
	return e.Add(http.MethodDelete, path, h, m...)
}

// GET registers a new GET route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
	return e.Add(http.MethodGet, path, h, m...)
}

关于 Echo 的 Method URL 声明,从代码中可以明显看出是对 Add 函数的一个封装

// Add registers a new route for an HTTP method and path with matching handler
// in the router with optional route-level middleware.
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
	return e.add("", method, path, handler, middleware...)
}

func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
	name := handlerName(handler)
	router := e.findRouter(host)
	router.Add(method, path, func(c Context) error {
		h := applyMiddleware(handler, middleware...)
		return h(c)
	})
	r := &Route{
		Method: method,
		Path:   path,
		Name:   name,
	}
	e.router.routes[method+path] = r
	return r
}

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()
}

func (e *Echo) findRouter(host string) *Router {
	if len(e.routers) > 0 {
		if r, ok := e.routers[host]; ok {
			return r
		}
	}
	return e.router
}

首先使用 runtime.FuncForPC 反射拿到 Handler 的函数名,后面的 e.routers 的类型实际为 map[string]*RouterfindRouter 函数保证了我们即使定义了重复的 Router 也只会生效第一个,并在之后将传入的路由路径放到 Router 结构体当中,关于更深入的机制,就需要详细查看 Router 结构体的声明和实现,这里为了保证 Demo 解读的通顺,暂时先不深追具体的细节

在我们调用了 e.AnyMethod 函数之后,例如 GET、POST 等,echo 会在 Router.router.routes 这个 map 中追加 method+path:r 这对 key:valuer*Route ,里面包含了方法、路径和调用的函数名

而作为执行函数的 HandlerFunc,则会被抛到和中间件一起归拢的函数 applyMiddleware ,而这个函数的作用,就是将所在 URL 的 middleware 列表压入一个执行函数

func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {
	for i := len(middleware) - 1; i >= 0; i-- {
		h = middleware[i](h)
	}
	return h
}

Start

func (e *Echo) Start(address string) error {
	e.startupMutex.Lock()
	e.Server.Addr = address
	if err := e.configureServer(e.Server); err != nil {
		e.startupMutex.Unlock()
		return err
	}
	e.startupMutex.Unlock()
	return e.serve()
}

Demo 的最后一行就是整个框架的启动语句了,在我们配置好了路由之后,它的启动依赖了 http.Server{}.Serve() 这个函数,剩下在互斥锁中的语句其实就是启动 Web 框架之前的行为了,例如显示 Logo、隐藏监听端口这些

模仿

在了解完基本概念后,我们可以模仿着实现一个 Web 框架,就叫 beegin 好了

package beegin

import (
	"fmt"
	"log"
	"net/http"
)

type (
	HandlerFunc func(http.ResponseWriter, *http.Request)
	routerMap   map[string]HandlerFunc

	Engine struct {
		router                  routerMap
		DefaultErrorHandlerFunc HandlerFunc
	}
)

func New() *Engine {
	return &Engine{
		router:                  make(routerMap),
		DefaultErrorHandlerFunc: defaultErrorHandlerFunc,
	}
}

func defaultErrorHandlerFunc(w http.ResponseWriter, r *http.Request) {
	_, err := fmt.Fprintln(w, "404 Not Found")
	if err != nil {
		log.Println(err)
	}
}

func (e *Engine) addRoute(method string, pattern string, h HandlerFunc) {
	key := fmt.Sprintf("%s-%s", method, pattern)
	log.Printf("Route %4s - %s", method, pattern)
	e.router[key] = h
}

func (e *Engine) GET(pattern string, h HandlerFunc) {
	e.addRoute(http.MethodGet, pattern, h)
}

func (e *Engine) POST(pattern string, h HandlerFunc) {
	e.addRoute(http.MethodPost, pattern, h)
}

func (e *Engine) Run(addr string) error {
	return http.ListenAndServe(addr, e)
}

func (e *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	key := fmt.Sprintf("%s-%s", req.Method, req.URL.Path)
	if handler, ok := e.router[key]; ok {
		handler(w, req)
	} else {
		e.DefaultErrorHandlerFunc(w, req)
	}
}

其实核心的思路就是模拟一个 http.Handler 的实现

main.go

package main

import (
	"beegin"
	"fmt"
	"log"
	"net/http"
)

func main() {
	r := beegin.New()
	r.GET("/hello", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w,"hello World")
	})

	log.Fatal(r.Run(":80"))
}