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