1. 路由
gin 框架中采用的路優酷是基于httprouter做的
HttpRouter 是一個高性能的 HTTP 請求路由器,適用于 Go 語言。它的設計目標是提供高效的路由匹配和低內存占用,特別適合需要高性能和簡單路由的應用場景。
主要特點
- 顯式匹配:與其他路由器不同,HttpRouter 只支持顯式匹配,即一個請求只能匹配一個或不匹配任何路由。這避免了意外匹配,提高了 SEO 和用戶體驗。
- 自動處理尾部斜杠:HttpRouter 會自動重定向缺少或多余尾部斜杠的請求,前提是新路徑有處理程序。如果不需要這種行為,可以關閉2。
- 路徑自動校正:除了處理尾部斜杠,HttpRouter 還可以修正錯誤的大小寫和多余的路徑元素(如 …/ 或 //)。
- 路由參數:支持在路由模式中使用命名參數和捕獲所有參數,簡化了 URL 路徑的解析。
- 零垃圾:匹配和分發過程不會產生垃圾,只有在路徑包含參數時才會進行堆分配。
- 最佳性能:使用壓縮動態 trie(基數樹)結構進行高效匹配,性能優異。
1.1 基本路由
package oneimport ("github.com/gin-gonic/gin""net/http"
)func index() {r := gin.Default()r.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "Hello Gin")})r.POST("/", func(c *gin.Context) {})r.DELETE("/", func(c *gin.Context) {})r.PUT("/", func(c *gin.Context) {})r.Run(":8081")
}
- gin支持Restful風格的API
- 即Representational State Transfer的縮寫。直接翻譯的意思是"表現層狀態轉化",是一種互聯網應用程序的API設計理念:URL定位資源,用HTTP描述操作
1.獲取文章:/blog/getXxx Get blog/Xxx
2.添加:/blog/addXxx POST blog/Xxx
3.修改:/blog/updateXxx PUT blog/Xxx
4.刪除:/blog/delXxxx DELETE blog/Xxx
另一種方式
package mainimport ("github.com/gin-gonic/gin""net/http"
)func sayHello(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "hello",})
}func main() {r := gin.Default()r.GET("/test1", sayHello)r.Run(":8080")
}
1.2 API 參數
請求方式的方法可以通過函數來接受,從而獲得請求傳來的參數
package mainimport ("github.com/gin-gonic/gin""net/http"
)func sayHello(c *gin.Context) {name := c.Param("name")age := c.Param("age")c.JSON(http.StatusOK, gin.H{"name": name,"age": age,})
}func main() {r := gin.Default()// 請求:localhost/test1/xxx/ooo// 響應:{"name":xxx,"age":ooo}r.GET("/test1/:name/:age", sayHello)r.Run(":8080")
}
需要注意:gin默認開啟restful風格語法,所以可以直接在請求路徑上添加名稱,屬性名是擬定在請求的路徑上的
r.GET("/test1/:name/:age", sayHello)
這里請求路徑擬定了方法內
c *gin.Context
中的參數名稱;這里默認有 name 和 age 屬性
請求:http://localhost:8080/test1/張三/18
響應結果:
{"age": "18","name": "張三"
}
1.3 URL 參數
路徑參數,就不會使用restful風格語法進行接受請求參數
package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)func sayHello(c *gin.Context) {// Query方法,若不存在則返回空字符串name := c.Query("name")age := c.Query("age")fmt.Println("========================")fmt.Println("名稱", name)fmt.Println("年齡", name)fmt.Println("========================")name1 := c.DefaultQuery("name", "默認名稱")fmt.Println("defaultName", name1)c.JSON(http.StatusOK, gin.H{"name": name,"age": age,"name1": name1,})
}func main() {r := gin.Default()// 請求:localhost/test1/?xxx=xxx&ooo=000// 響應:{"name":xxx,"age":ooo}r.GET("/test1", sayHello)r.Run(":8080")
}
- URL參數可以通過DefaultQuery()或Query()方法獲取
- DefaultQuery()若參數不存在,返回默認值,Query()若不存在,返回空串
兩種請求:
-
請求1:
http://localhost:8080/test1?age=19
-
響應數據1:
{"age": "19","name": "","name1": "默認名稱"
}
- 請求2:
http://localhost:8080/test1?name=張三&age=19
- 響應數據2:
{"age": "19","name": "張三","name1": "張三"
}
1.4 表單參數
- 表單傳輸為post請求,http常見的傳輸格式為四種:
- application/json
- application/x-www-form-urlencoded
- application/xml
- multipart/form-data
- 表單參數可以通過PostForm()方法獲取,該方法默認解析的是x-www-form-urlencoded或from-data格式的參數
測試的html文件
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body>
<form action="http://localhost:8080/form" method="post" action="application/x-www-form-urlencoded">用戶名:<input type="text" name="username" placeholder="請輸入你的用戶名"> <br>密 碼:<input type="password" name="user_password" placeholder="請輸入你的密碼"> <br><input type="submit" value="提交">
</form>
</body>
</html>
- index.go
package mainimport ("github.com/gin-gonic/gin""net/http"
)func sayHello(c *gin.Context) {types := c.DefaultPostForm("type", "post")// 接受參數name := c.PostForm("username") // 對應頁面傳入的name值或者json的屬性password := c.PostForm("user_password")c.JSON(http.StatusOK, gin.H{"types": types,"name": name,"password": password,})
}func main() {r := gin.Default()r.POST("/form", sayHello)r.Run(":8080")
}
返回數據:
{"name": "rx","password": "123","types": "post"
}
1.5 上傳單個文件
multipart/form-data
格式用于文件上傳- gin文件上傳與原生的net/http方法類似,不同在于gin把原生的request封裝到c.Request中
demo2.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body>
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">上傳文件:<input type="file" name="file" ><input type="submit" value="提交">
</form>
</body>
</html>
- index.go
package __single_fileimport ("github.com/gin-gonic/gin""net/http"
)func DefaultFunction(c *gin.Context) {// 上傳單個文件file, _ := c.FormFile("file")if file == nil {c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError,"message": "文件為空",})return}c.SaveUploadedFile(file, file.Filename)c.JSON(http.StatusOK, gin.H{"file": file.Filename,})
}func main() {r := gin.Default()r.POST("/upload", DefaultFunction)r.Run(":8080")
}
數據響應:
{"file":"Picture3.png"}
1.6 上傳單個文件——添加限制
package __single_file_restrictimport ("fmt""github.com/gin-gonic/gin""log""net/http"
)func DefaultFunction(c *gin.Context) {// 上傳單個文件_攜帶限制_, file, err := c.Request.FormFile("file")if err != nil {log.Printf("Error when try to get file: %v", err)}//file.Size 獲取文件大小if file.Size > 1024*1024*2 {fmt.Println("文件太大了")return}//file.Header.Get("Content-Type")獲取上傳文件的類型if file.Header.Get("Content-Type") != "image/png" {fmt.Println("只允許上傳png圖片")return}c.SaveUploadedFile(file, file.Filename)c.JSON(http.StatusOK, gin.H{"file": file.Filename,})
}func main() {r := gin.Default()r.POST("/upload", DefaultFunction)r.Run(":8080")
}
數據響應:
{"file":"Picture3.png"}
1.7 上傳多個文件
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body><form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data">上傳文件:<input type="file" name="files" multiple><input type="submit" value="提交"></form>
</body>
</html>
package mainimport ("github.com/gin-gonic/gin""net/http""fmt"
)// gin的helloWorldfunc main() {// 1.創建路由// 默認使用了2個中間件Logger(), Recovery()r := gin.Default()// 限制表單上傳大小 8MB,默認為32MBr.MaxMultipartMemory = 8 << 20r.POST("/upload", func(c *gin.Context) {form, err := c.MultipartForm()if err != nil {c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))}// 獲取所有圖片files := form.File["files"]// 遍歷所有圖片for _, file := range files {// 逐個存if err := c.SaveUploadedFile(file, file.Filename); err != nil {c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))return}}c.String(200, fmt.Sprintf("upload ok %d files", len(files)))})//默認端口號是8080r.Run(":8000")
}
1.8 路由組-routers group
package mainimport ("github.com/gin-gonic/gin""net/http"
)func cat(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"cat": c.Query("cat"),})
}func dog(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"dog": c.Query("dog"),})
}func main() {r := gin.Default()animal := r.Group("/animal")// http://localhost:8080/animal/cat?cat=tomanimal.GET("/cat", cat)animal.GET("/dog", dog)// 第二種寫法// http://localhost:8080/animal2/cat?cat=tomanimal2 := r.Group("/animal2"){animal2.GET("/cat", cat)animal2.GET("/dog", dog)}r.Run(":8080")
}
路由組有兩種寫法
-
動態配置,將變量聲明出來可以在任意位置配置路由
animal := r.Group("/animal") // http://localhost:8080/animal/cat?cat=tom animal.GET("/cat", cat) animal.GET("/dog", dog)
-
對象配置,直接使用對象進行配置
// http://localhost:8080/animal2/cat?cat=tom animal2 := r.Group("/animal2") {animal2.GET("/cat", cat)animal2.GET("/dog", dog) }
注意路由組配置:組路徑下的請求配置,必須加上
/
例如: animal2.GET(“/cat”, cat)
1.9 httprotuer路由原理
1.9.1 Radix Tree(壓縮前綴樹)
基數樹(Radix Tree)又稱為PAT位樹(Patricia Trie or crit bit tree),是一種更節省空間的前綴樹(Trie Tree)。對于基數樹的每個節點,如果該節點是唯一的子樹的話,就和父節點合并
Radix Tree
可以被認為是一棵簡潔版的前綴樹。其注冊路由的過程就是構造前綴樹的過程,具有公共前綴的節點也共享一個公共父節點。假設注冊以下路由信息:
r := gin.Default()r.GET("/", func1)
r.GET("/search/", func2)
r.GET("/support/", func3)
r.GET("/blog/", func4)
r.GET("/blog/:post/", func5)
r.GET("/about-us/", func6)
r.GET("/about-us/team/", func7)
r.GET("/contact/", func8)
生成的路由樹信息如下:
Priority Path Handle
9 \ *<1>
3 ├s nil
2 |├earch\ *<2>
1 |└upport\ *<3>
2 ├blog\ *<4>
1 | └:post nil
1 | └\ *<5>
2 ├about-us\ *<6>
1 | └team\ *<7>
1 └contact\ *<8>
最右列每個***<數字>表示Handle處理函數的內存地址(一個指針)**。
- 從根節點遍歷到葉子節點就能得到完整的路由表。
例如:blog/:post其中:post只是實際文章名稱的占位符(參數)。
與hash-maps不同,Radix Tree結構還允許使用像:post參數這種動態部分,因為Radix Tree實際上是根據路由模式進行匹配,而不僅僅是比較哈希值。
由于URL路徑具有層次結構,并且只使用有限的一組字符(字節值),所以很可能有許多常見的前綴。這使開發者可以很容易地將路由簡化為更小的問題。
- 此外,路由器為每種請求方法管理一棵單獨的樹。
- 一方面,它比在每個節點中都保存一個method-> handle map更加節省空間,而且還使開發者甚至可以在開始在前綴樹中查找之前大大減少路由問題。
為了獲得更好的可伸縮性,每個樹級別上的子節點都按Priority(優先級)排序,其中優先級(最左列)就是在子節點(子節點、子子節點等等)中注冊的句柄的數量。
這樣做有兩個好處:
優先匹配被大多數路由路徑包含的節點
。這樣可以讓盡可能多的路由快速被定位。- 類似于成本補償。最長的路徑可以被優先匹配,補償體現在最長的路徑需要花費更長的時間來定位,如果最長路徑的節點能被優先匹配(即每次拿子節點都命中),那么路由匹配所花的時間不一定比短路徑的路由長。
下面展示了節點(每個-可以看做一個節點)匹配的路徑:從左到右,從上到下。
├------------
├---------
├-----
├----
├--
├--
└-
1.9.2 httproute 特點
路由器支持路由模式中的變量,并與請求方法進行匹配。其伸縮性相較于原生net/http庫更好。
明確的路由匹配,一個path對應一個Handler。
不用關心/,例如當請求/foo/時,此path不存在,如果有/foo會自動302轉發
path自動修正,例如//foo轉化成/foo
httprouter使用的數據結構就是壓縮字典樹。
典型的壓縮字典樹結構如下:
1.9 參考文章:gin框架httprouter路由原理
2. ??GoGin框架——前文鏈接
Gin框架學習參考網站:gin框架·Go語言中文文檔
(Go Gin)基于Go的WEB開發框架,GO Gin是什么?怎么啟動?本文給你答案
3. 💕👉博客專欄
- Golang專欄-包含基礎、Gin、Goam等知識
- 云原生專欄-包含k8s、docker等知識
- 從0開始學習云計算-華為HCIP證書
- JUC專欄-帶你快速領悟JUC的知識!
- JVM專欄-深入Java虛擬機,理解JVM的原理
- 基于Java研究 數據結構與算法-包含貪心算法、加權圖、最短路徑算法等知識
- Docker專欄-上手熱門容器技術Docker
- SpringBoot專欄-學習SpringBoot快速開發后端
- 項目管理工具的學習-設計技術:Maven、Git、Gradle等相關管理工具
- JavaSE-全面了解Java基礎
- JS專欄-使用JS作的一部分實例~
- 使用CSS所作的一部分案例