## 使用使用中間件
Go程序的中間件是一個被廣泛探索的領域。有許多用于處理中間件的包。 本節將從頭開始創建中間件,并實現一個ApplyMiddleware函數將一堆中間件鏈接在一起。此外還會在請求上下文對象中設置值,并使用中間件檢索它們。以演示如何將中間件邏輯與處理程序分離。
### 實踐
1. 建立 middleware.go:
```
package middleware
import (
"log"
"net/http"
"time"
)
// Middleware是所有的中間件函數都會返回的
type Middleware func(http.HandlerFunc) http.HandlerFunc
// ApplyMiddleware 將應用所有中間件,最后一個參數將是用于上下文傳遞目的的外部包裝
func ApplyMiddleware(h http.HandlerFunc, middleware ...Middleware) http.HandlerFunc {
applied := h
for _, m := range middleware {
applied = m(applied)
}
return applied
}
// Logger 記錄請求日志 這會通過SetID()傳遞id
func Logger(l *log.Logger) Middleware {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
l.Printf("started request to %s with id %s", r.URL, GetID(r.Context()))
next(w, r)
l.Printf("completed request to %s with id %s in %s", r.URL, GetID(r.Context()), time.Since(start))
}
}
}
```
2. 建立 context.go:
```
package middleware
import (
"context"
"net/http"
"strconv"
)
// ContextID 是自定義類型 用于檢索context
type ContextID int
// ID是我們定義的唯一ID
const ID ContextID = 0
// SetID 使用自增唯一id更新context
func SetID(start int64) Middleware {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), ID, strconv.FormatInt(start, 10))
start++
r = r.WithContext(ctx)
next(w, r)
}
}
}
// GetID 如果設置,則從上下文中獲取ID,否則返回空字符串
func GetID(ctx context.Context) string {
if val, ok := ctx.Value(ID).(string); ok {
return val
}
return ""
}
```
3. 建立 handler.go:
```
package middleware
import (
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))
}
```
4. 建立 main.go:
```
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/agtorre/go-cookbook/chapter7/middleware"
)
func main() {
// We apply from bottom up
h := middleware.ApplyMiddleware(
middleware.Handler,
middleware.Logger(log.New(os.Stdout, "", 0)),
middleware.SetID(100),
)
http.HandleFunc("/", h)
fmt.Println("Listening on port :3333")
err := http.ListenAndServe(":3333", nil)
panic(err)
}
```
5. 運行:
```
$ go run main.go
Listening on port :3333
$curl "http://localhost:3333
success
$curl "http://localhost:3333
success
$curl "http://localhost:3333
success
```
此外在運行main.go的命令行你還會看到:
```
Listening on port :3333
started request to / with id 100
completed request to / with id 100 in 52.284μs
started request to / with id 101
completed request to / with id 101 in 40.273μs
started request to / with id 102
```
### 說明
中間件可用于執行簡單操作,例如日志記錄,度量標準收集和分析。它還可用于在每個請求上動態填充變量。例如,可以用于從請求中收集X-Header以設置ID或生成ID,就像我們在示例中所做的那樣。另一個ID策略可能是為每個請求生成一個UUID,這樣我們可以輕松地將日志消息關聯在一起,并在構建響應時跟蹤請求。
使用上下文值時,考慮中間件的順序很重要。通常,最好不要讓中間件相互依賴。 例如,最好在日志記錄中間件本身中生成UUID。
* * * *
學識淺薄,錯誤在所難免。歡迎在群中就本書提出修改意見,以饗后來者,長風拜謝。
Golang中國(211938256)
beego實戰(258969317)
Go實踐(386056972)
- 前言
- 第一章 I/O和文件系統
- 常見 I/O 接口
- 使用bytes和strings包
- 操作文件夾和文件
- 使用CSV格式化數據
- 操作臨時文件
- 使用 text/template和HTML/templates包
- 第二章 命令行工具
- 解析命令行flag標識
- 解析命令行參數
- 讀取和設置環境變量
- 操作TOML,YAML和JSON配置文件
- 操做Unix系統下的pipe管道
- 處理信號量
- ANSI命令行著色
- 第三章 數據類型轉換和解析
- 數據類型和接口轉換
- 使用math包和math/big包處理數字類型
- 貨幣轉換和float64注意事項
- 使用指針和SQL Null類型進行編碼和解碼
- 對Go數據編碼和解碼
- Go中的結構體標簽和反射
- 通過閉包實現集合操作
- 第四章 錯誤處理
- 錯誤接口
- 使用第三方errors包
- 使用log包記錄錯誤
- 結構化日志記錄
- 使用context包進行日志記錄
- 使用包級全局變量
- 處理恐慌
- 第五章 數據存儲
- 使用database/sql包操作MySQL
- 執行數據庫事務接口
- SQL的連接池速率限制和超時
- 操作Redis
- 操作MongoDB
- 創建存儲接口以實現數據可移植性
- 第六章 Web客戶端和APIs
- 使用http.Client
- 調用REST API
- 并發操作客戶端請求
- 使用OAuth2
- 實現OAuth2令牌存儲接口
- 封裝http請求客戶端
- 理解GRPC的使用
- 第七章 網絡服務
- 處理Web請求
- 使用閉包進行狀態處理
- 請求參數驗證
- 內容渲染
- 使用中間件
- 構建反向代理
- 將GRPC導出為JSON API
- 第八章 測試
- 使用標準庫進行模擬
- 使用Mockgen包
- 使用表驅動測試
- 使用第三方測試工具
- 模糊測試
- 行為驅動測試
- 第九章 并發和并行
- 第十章 分布式系統
- 第十一章 響應式編程和數據流
- 第十二章 無服務器編程
- 第十三章 性能改進