通過上個章節我們已經學會了中間件的用法,在 gin 框架中路由的回調函數、中間件回調函數本質都是一樣的,我相信大家使用已經沒有任何問題了,那么在本章節我們就由淺入深,繼續深度學習中間件.
## 基礎用法
### 1.中間件的加載(注冊)
注意:Use 函數僅僅只是負責加載(注冊)中間件,不負責調用.
```
// 路由可以載入很多個中間件,很多個到底是多少個?
// 想知道答案,那我們繼續追蹤gin源碼去揭曉答案,這里請帶著你的疑問向后學習
backend.Use(authorization.CheckTokenAuth(), 下一個中間件,繼續下一個中間件, 省略很多個...)
```
## 進階學習
后續知識點對于初學者來說存在一定的難度,主要是因為在 gin 中,加載的中間件函數: `func ( *gin.Context){ }` 在后續執行時會和注冊的路由回調函數在同一個邏輯處執行,中間件函數、路由回調函數都是平行關系,不同的區別是先后順序不同,gin 對這塊邏輯處理是相同的方式,這就需要我們從路由開始追蹤,因此涉及到的代碼會比較多,過程比較復雜.
學完本章節,gin 最核心的主線邏輯也就徹底搞明白了。
這個過程其實就是對一個 request -> response 的全過程源代碼剖析,難度比較大,
### 2.gin 中間件加載過程
```
// Use 的作用就是首先載入中間件回調函數:func(*Context)
// 首先存儲起來,然后等著被后續邏輯調用
// 我們使用 goland ,ctrl+鼠標左鍵,點擊 Use 繼續追蹤gin源碼
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
// group.Handlers 定義的最終原型如下,本質上就是 func(*Context) 的切片,存儲很多個回調函數
type HandlersChain []HandlerFunc // type HandlerFunc func(*Context)
// 學習到這里,你必須知道的就是:
// gin的中間件回調函數全部被存儲在 group.Handlers 變量了
// 這里您對該變量有印象就行,后面還需要繼續使用該變量
```
習到這里,我們已經知道中間件處理函數被 `Use` 函數注冊了在了 `group.Handlers` 變量存儲起來了, `Use` 函數可以加載很多個中間件,究竟是多少個,這里我們依然不知道他的具體數量,還有它們什么時候執行,我們也不知道 ... ...
我們只看見 Use 函數最后返回了 `group.returnObj()` ,它是所有路由的處理接口:IRoutes ,已經結束了,我們無法向下追蹤了,那就只能從其他地方入手追蹤了,既然中間件是加載在具體的路由前面,那么它肯定在某個具體的路由被訪問時執行。
### 3.gin 中間件執行邏輯
這個過程很漫長,邏輯比較復雜,我們分步驟分析,去追蹤 .
#### 3.1 一個具體的路由地址被請求后的 gin 源碼究竟是什么
**3.1.1 定義一個具體的路由以及回調函數**
```
// 1.省略其它無關代碼
users.GET("list", func (c *gin.Context){
// 編寫該路由路徑對應的業務處理回調函數
// 我們省略具體過程 ... ...
})
```
**3.1.2** `**users.GET**` **背后的 gin 源碼追蹤分析**
```
// 2.在 goland 中,ctrl+鼠標左鍵 點擊 GET 函數,源代碼如下:
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
// 很明顯該函數是 GET + relativePath(開發者定義的路由路徑)對應的業務處理邏輯
// 與中間件一樣,他的請求回調函數也可以有很多個,具體數量不知道...
// 那么就需要繼續追蹤 group.handle 源代碼
return group.handle(http.MethodGet, relativePath, handlers)
}
// 3.group.handle 函數源碼
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
// 將定義的路由相對路徑拼接為完整的絕對路徑
// 因為一個完整的路徑往往和前面定義的路由組路徑有關系,因此需要一系列拼接
absolutePath := group.calculateAbsolutePath(relativePath)
// 針對完整路由路徑將關聯的回調函數全部組合出來
// 究竟如何組合,后續繼續追蹤源碼
handlers = group.combineHandlers(handlers)
// 將請求方式(GET、POST等)結合完整路徑作為key,處理函數作為 value
// 以 key => value 的形式注冊,value 可以是很多個回調函數
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
//4. group.combineHandlers 源代碼追蹤
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
// 看到這里還記得 group.Handlers 變量嗎?如果不記得請查看本章節 2 標題部分
// finalSize 表示 中間件回調函數的數量 + 具體路由的回調函數數量的總和
finalSize := len(group.Handlers) + len(handlers)
// 如果 finalSize >= abortIndex 就會發生panic,
// abortIndex 的定義值: math.MaxInt8 / 2 ,
// 在go語言中,官方定義: MaxInt8 = 1<<7 - 1, 表示 1*(2^7)-1,最終值:127
// 那么 abortIndex = 127/2 取整 = 63
// 至此我們終于知道, gin 的中間件函數數量 + 路由回調函數的數量總和最大允許 63 個.
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
// 以上條件檢查全部通過后,將中間件回調函數和路由回調函數全部合并在一起存儲
// HandlersChain 本質就是 [] func(*Context)
mergedHandlers := make(HandlersChain, finalSize)
// group.Handlers 是中間件函數,他在 mergedHandlers 中存儲的順序靠前,也就是索引比較小
copy(mergedHandlers, group.Handlers)
// handlers 是路由回調函數,他的存儲位置比中間函數靠后
copy(mergedHandlers[len(group.Handlers):], handlers)
// 最終返回中間件函數+路由函數組合在一起的全部回調函數
return mergedHandlers
}
//5. group.engine.addRoute 源碼分析
// gin的路由是一個很復雜的路由前綴樹算法模型,完整過程很復雜
// 這里我們主要追蹤路由以及回調函數的 存儲/注冊 過程
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path[0] == '/', "path must begin with '/'")
assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) > 0, "there must be at least one handler")
debugPrintRoute(method, path, handlers)
root := engine.trees.get(method)
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
// 在這里,gin 將我們定義的完整路徑和回調函數進行了注冊
// 源碼后續繼續追、分析
root.addRoute(path, handlers)
// Update maxParams
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
}
//6.root.addRoute(path, handlers) 函數在按照鍵 => 值進行注冊路由與回調函數時調用了很多其他函數
// 這里我將過程函數名字列舉如下
func (n *node) addRoute(path string, handlers HandlersChain) {
// 省略其他無關代碼
// 又繼續調用如下函數
n.insertChild(path, fullPath, handlers)
// ... ...
}
// 省略很多其他的代碼
}
// 7. n.insertChild
func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {
//省略其他無關代碼
child := &node{
priority: 1,
fullPath: fullPath, // 完整的路由路徑
}
// 最終所有的回調函數
n.handlers = handlers
}
// node 的結構體定義,發現就是自己嵌套自己的一個結構體
// handlers 成員的數據類型(HandlersChain)本質就是 []func(*gin.Context)
// 至此我們徹底明白:路由的存儲模型是一個樹形結構,每個節點都有自己路由路徑以及回調函數 handlers
type node struct {
path string
indices string
wildChild bool
nType nodeType
priority uint32
children []*node // child nodes, at most 1 :param style node at the end of the array
handlers HandlersChain
fullPath string
}
```
- Golang
- Beego框架
- Gin框架
- gin框架介紹
- 使用Gin web框架的知名開源線上項目
- go-admin-gin
- air 熱啟動
- 完整的form表單參數驗證語法
- Go 語言入門練手項目推薦
- Golang是基于多線程模型
- golang 一些概念
- Golang程序開發注意事項
- fatal error: all goroutines are asleep - deadlock
- defer
- Golang 的內建調試器
- go部署
- golang指針重要性
- 包(golang)
- Golang框架選型比較: goframe, beego, iris和gin
- GoFrame
- golang-admin-項目
- go module的使用方法及原理
- go-admin支持多框架的后臺系統(go-admin.cn)
- docker gocv
- go-fac
- MSYS2
- 企業開發框架系統推薦
- gorm
- go-zero
- 優秀系統
- GinSkeleton(gin web 及gin 知識)
- 一次 request -> response 的生命周期概述
- 路由與路由組以及gin源碼學習
- 中間件以及gin源碼學習
- golang項目部署
- 獨立部署golang
- 代理部署golang
- 容器部署golang
- golang交叉編譯
- goravel
- kardianos+gin 項目作為windows服務運行
- go env
- 適用在Windows、Linux和macOS環境下打包Go應用程序的詳細步驟和命令
- Redis
- Dochub
- Docker部署開發go環境
- Docker部署運行go環境
- dochub說明
- Vue
- i18n
- vue3
- vue3基本知識
- element-plus 表格單選
- vue3后臺模板
- Thinkphp
- Casbin權限控制中間件
- 容器、依賴注入、門面、事件、中間件
- tp6問答
- 偽靜態
- thinkphp-queue
- think-throttle
- thinkphp隊列queue的一些使用說明,queue:work和queue:listen的區別
- ThinkPHP6之模型事件的觸發條件
- thinkphp-swoole
- save、update、insert 的區別
- Socket
- workerman
- 介紹
- 從ThinkPHP6移植到Webman的一些技術和經驗(干貨)
- swoole
- swoole介紹
- hyperf
- hf官網
- Swoft
- swoft官網
- easyswoole
- easyswoole官網地址
- EASYSWOOLE 聊天室DEMO
- socket問答
- MySQL
- 聚簇索引與非聚簇索引
- Mysql使用max獲取最大值細節
- 主從復制
- 隨機生成20萬User表的數據
- MySQL進階-----前綴索引、單例與聯合索引
- PHP
- 面向切面編程AOP
- php是單線程的一定程度上也可以看成是“多線程”
- PHP 線程,進程、并發、并行 的理解
- excel數據畫表格圖片
- php第三方包
- monolog/monolog
- league/glide
- 博客-知識網站
- php 常用bc函數
- PHP知識點的應用場景
- AOP(面向切面編程)
- 注解
- 依賴注入
- 事件機制
- phpspreadsheet導出數據和圖片到excel
- Hyperf
- mineAdmin
- 微服務
- nacos注冊服務
- simps-mqtt連接客戶端simps
- Linux
- 切換php版本
- Vim
- Laravel
- RabbitMQ
- thinkphp+rabbitmq
- 博客
- Webman框架
- 框架注意問題
- 關于內存泄漏
- 移動端自動化
- 懶人精靈
- 工具應用
- render
- gitlab Sourcetree
- ssh-agent失敗 錯誤代碼-1
- 資源網站
- Git
- wkhtmltopdf
- MSYS2 介紹
- powershell curl 使用教程
- NSSM(windows服務工具)
- MinGW64
- 知識擴展
- 對象存儲系統
- minio
- 雪花ID
- 請求body參數類型
- GraphQL
- js 深拷貝
- window 共享 centos文件夾
- 前端get/post 請求 特殊符號 “+”傳參數問題
- 什么是SCM系統?SCM系統與ERP系統有什么區別?
- nginx 日志格式統一為 json
- 特殊符號怎么打
- 收藏網址
- 收藏-golang
- 收藏-vue3
- 收藏-php
- 收藏-node
- 收藏-前端
- 規劃ITEM
- 旅游類
- 人臉識別
- dlib
- Docker&&部署
- Docker-compose
- Docker的網絡模式
- rancher
- DHorse
- Elasticsearch
- es與kibana都docke連接
- 4種數據同步到Elasticsearch方案
- GPT
- 推薦系統
- fastposter海報生成
- elasticsearch+logstash+kibana
- beego文檔系統-MinDoc
- jeecg開源平臺
- Java
- 打包部署
- spring boot
- 依賴
- Maven 相關 命令
- Gradle 相關命令
- mybatis
- mybatis.plus
- spring boot 模板引擎
- SpringBoot+Maven多模塊項目(創建、依賴、打包可執行jar包部署測試)完整流程
- Spring Cloud
- Sentinel
- nacos
- Apollo
- java推薦項目
- gradle
- Maven
- Nexus倉庫管理器
- Python
- Masonite框架
- scrapy
- Python2的pip2
- Python3 安裝 pip3
- 安全攻防
- 運維技術
- 騰訊云安全加固建議
- 免費freessl證書申請
- ruby
- homeland
- Protobuf
- GIT
- FFMPEG
- 命令說明
- 音頻
- ffmpeg合并多個MP4視頻
- NODEJS
- 開發npm包
- MongoDB
- php-docker-mongodb環境搭建
- mongo基本命令
- Docker安裝MongoDB最新版并連接
- 少兒編程官網
- UI推薦
- MQTT
- PHP連接mqtt
- EMQX服務端
- php搭建mqtt服務端