golang echo框架基本用法


1 基本框架

基础模型如下图所示:

image

echo框架内部基于标准的http.Server完成端口监听和连接收发包逻辑。简单来说,主协程负责监听套接口;对每条连接,创建一个单独的goroutine来负责处理请求。

echo框架比较核心的,其实是它自己实现了 http.Handler接口:

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

这个 interface 只有一个方法,那就是 ServeHTTP:根据传入的 请求 参数 *Request,实现请求处理逻辑,并将返回结果写入到 ResponseWriter

具体到echo框架,它的ServeHTTP实现逻辑大体如下图所示:

image

上图中有4个关键要素:

  • 业务自定义实现的特定path的Handler,需要事先注册到Router;
  • 对业务Handler可以自定义行为的MiddleWare
  • 可以支持在Router之前对请求进行自定义处理的 Pre MiddleWare
  • 在Handler返回error时候的统一错误处理函数 HttpErrprHandler

这里,先说下最核心的Handler,它的类型是 HandlerFunc

type HandlerFunc func(Context) error

当我们使用echo.GETecho.POST添加业务逻辑代码时,其实就是将一个 HandlerFunc 绑定到了一个 Path 上。echo内部维护这个关系的组件叫 Router 。 Router 会为每个 path 创建一个 Route 对象:

type Route struct {
	Method string `json:"method"`
	Path   string `json:"path"`
	Name   string `json:"name"`
}

2 MiddleWare机制

echo里面有两个 Middleware:

type Echo struct {
	premiddleware    []MiddlewareFunc
	middleware       []MiddlewareFunc
}

每个middleware其实是一个由多个 MiddlewareFunc组成的slice。两种MiddleWare的区别,其实是执行的顺序问题:一个在Router之前,一个在Router之后。

这里的MiddlewareFunc的类型定义比较有意思:

type MiddlewareFunc func(HandlerFunc) HandlerFunc

一个MiddlewareFunc其实就是一个 Handle加工函数:给它一个 handler,然后经过改造加工之后,返回一个新的handler。

以 middleware为例,当echo收到请求后,先从Router里面找到绑定的Handler,然后把Handler从后到前遍历一遍middleware中的加工函数。最后再调用最终的Handler获得回包,再返回给客户端。

再来看看MiddlewareFunc的实现,因为是对传入的Handler进行加工,所以必须不能把传入的Handler给丢掉:

func mid1(h HandlerFunc) HandlerFunc {
	fmt.Println("mid1 done")
	return func(Context) error {}
}

func main() {
	e := echo.New()
	e.USE(mid1)
	e.GET("/", func(c echo.Context) error { fmt.Println(c.Request())})

	e.Start(":1323")
}

上面的mid1函数一旦被加入到middleware,那么它就把e.GET时传入的handler给丢掉了,导致业务逻辑无法被处理。

所以正常的MiddleWareFunc一般这么写:

func mid1(h HandlerFunc) HandlerFunc {
	return func(c Context) error {
		// a.实现自己的逻辑
		// b.调用传入的handler
		err := h(c)
		// c.实现自己的逻辑
	}
}

当然,具体到实际的场景,a 和 c可以省略其中一个,但是b一定不能省略。

3 日志

默认情况下,echo框架不会打印任何跟具体请求有关的日志。为了调试方便或调查问题方便,我们可以这样做:

记录每条请求信息

e.Use(middleware.Logger())

这里我们使用echo自带的logger中间件来打日志。默认情况下,它会打印下面的日志:

{"time":"2021-09-10T15:58:24.768646+08:00","id":"","remote_ip":"192.168.56.1","host":"192.168.56.1:1323","method":"GET","uri":"/user/123","user_agent":"curl/7.58.0","status":401,"error":"code=401, message=Please provide valid token","latency":92819,"latency_human":"92.819µs","bytes_in":0,"bytes_out":41}

当然,也可以对打印的字段和时间格式进行自定义:

e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
	Format:`{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
    `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
    `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
    `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
	CustomTimeFormat: "2006-01-02 15:04:05.00000",
	Output: os.Stdout,
}))

我们可以对其中各字端进行自定义的增加或删除。

日志输出到文件

默认情况下echo框架会把日志打印到标准输出,我们可以对齐进行修改,比如输出到文件。

以比较常用的 日志框架 zap + lumberjack 为例:

// 初始化logger
var wcore zapcore.WriteSyncer
rollingLogger := &lumberjack.Logger{
	Filename:   "/path/to/file",
	MaxSize:    500,  // megabytes
	MaxAge:     1,   // days
	MaxBackups: 31,   // the maximum number of old log files to retain
	Compress:   true, // use gzip to compress all rotated log files
}
wcore = zapcore.AddSync(rollingLogger)

zaplevel := zap.NewAtomicLevel()
zaplevel.UnmarshalText([]byte(cfg.Log.Level))
zapencCfg := zap.NewProductionEncoderConfig()
zapencCfg.EncodeTime = zapcore.ISO8601TimeEncoder

core := zapcore.NewCore(
	zapcore.NewJSONEncoder(zapencCfg),
	wcore,
	zaplevel.Level(),
)

// append "caller", "pid" to log
opts := []zap.Option{}
opts = append(opts, zap.AddCaller())
initFields := []zapcore.Field{
	zap.Any("pid", os.Getpid()),
}
opts = append(opts, zap.Fields(initFields...))
logger := zap.New(core, opts...)

// 设置echo框架日志
e.Logger.SetOutput(wcore)

4 错误处理

//TODO

5 panic

//TODO


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM