Go语言web框架 gin
Go語言web框架 GIN
gin是go語言環境下的一個web框架, 它類似于Martini, 官方聲稱它比Martini有更好的性能, 比Martini快40倍, Ohhhh….看著不錯的樣子, 所以就想記錄一下gin的學習. gin的github代碼在這里: gin源碼. gin的效率獲得如此突飛猛進, 得益于另一個開源項目httprouter, 項目地址: httprouter源碼. 下面主要記錄一下gin的使用.- 1
1. 安裝gin
使用命令go get github.com/gin-gonic/gin就可以. 我們使用gin的時候引入相應的包就OKimport "github.com/gin-gonic/gin".- 1
2. 使用方法
<1> 一種最簡單的使用GET/POST方法
gin服務端代碼是:
/ func1: 處理最基本的GET func func1 (c *gin.Context) {// 回復一個200OK,在client的http-get的resp的body中獲取數據c.String(http.StatusOK, "test1 OK") } // func2: 處理最基本的POST func func2 (c *gin.Context) { // 回復一個200 OK, 在client的http-post的resp的body中獲取數據 c.String(http.StatusOK, "test2 OK") } func main(){ // 注冊一個默認的路由器 router := gin.Default() // 最基本的用法 router.GET("/test1", func1) router.POST("/test2", func2) // 綁定端口是8888 router.Run(":8888") }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
客戶端代碼是:
func main(){ // 調用最基本的GET,并獲得返回值resp,_ := http.Get("http://0.0.0.0:8888/test1")helpRead(resp) // 調用最基本的POST,并獲得返回值resp,_ = http.Post("http://0.0.0.0:8888/test2", "",strings.NewReader("")) helpRead(resp) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
在服務端, 實例化了一個router, 然后使用GET和POST方法分別注冊了兩個服務, 當我們使用HTTP GET方法的時候會使用GET注冊的函數, 如果使用HTTP POST的方法, 那么會使用POST注冊的函數. gin支持所有的HTTP的方法例如: GET, POST, PUT, PATCH, DELETE 和 OPTIONS等. 看客戶端中的代碼, 當調用http.Get(“http://0.0.0.0:8888/test1“)的時候, 服務端接收到請求, 并根據/test1將請求路由到func1函數進行 處理. 同理, 調用http.Post(“http://0.0.0.0:8888/test2“, “”,strings.NewReader(“”))時候, 會使用func2函數處理. 在func1和func2中, 使用gin.Context填充了一個String的回復. 當然也支持JSON, XML, HTML等其他一些格式數據. 當執行c.String或者c.JSON時, 相當于向http的回復緩沖區寫入了 一些數據. 最后調用router.Run(“:8888”)開始進行監聽,Run的核心代碼是:
func (engine *Engine) Run(addr string) (err error) {debugPrint("Listening and serving HTTP on %s\n", addr)defer func() { debugPrintError(err) }() // 核心代碼 err = http.ListenAndServe(addr, engine) return }- 1
- 2
- 3
- 4
- 5
- 6
- 7
其本質就是http.ListenAndServe(addr, engine).?
注意: helpRead函數是用于讀取response的Body的函數, 你可以自己定義, 本文中此函數定義為:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
<2> 傳遞參數
傳遞參數有幾種方法, 對應到gin使用幾種不同的方式來解析.
第一種:?使用gin.Context中的Param方法解析
對應的服務端代碼為:
// func3: 處理帶參數的path-GET func func3(c *gin.Context) {// 回復一個200 OK// 獲取傳入的參數name := c.Param("name")passwd := c.Param("passwd") c.String(http.StatusOK, "參數:%s %s test3 OK", name, passwd) } // func4: 處理帶參數的path-POST func func4(c *gin.Context) { // 回復一個200 OK // 獲取傳入的參數 name := c.Param("name") passwd := c.Param("passwd") c.String(http.StatusOK, "參數:%s %s test4 OK", name, passwd) } // func5: 注意':'和'*'的區別 func func5(c *gin.Context) { // 回復一個200 OK // 獲取傳入的參數 name := c.Param("name") passwd := c.Param("passwd") c.String(http.StatusOK, "參數:%s %s test5 OK", name, passwd) } func main(){ router := gin.Default() // TODO:注意':'必須要匹配,'*'選擇匹配,即存在就匹配,否則可以不考慮 router.GET("/test3/:name/:passwd", func3) router.POST("/test4/:name/:passwd", func4) router.GET("/test5/:name/*passwd", func5) router.Run(":8888") }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
客戶端測試代碼是:
func main() { // GET傳參數,使用gin的Param解析格式: /test3/:name/:passwdresp,_ = http.Get("http://0.0.0.0:8888/test3/name=TAO/passwd=123")helpRead(resp) // POST傳參數,使用gin的Param解析格式: /test3/:name/:passwdresp,_ = http.Post("http://0.0.0.0:8888/test4/name=PT/passwd=456", "",strings.NewReader("")) helpRead(resp) // 注意Param中':'和'*'的區別 resp,_ = http.Get("http://0.0.0.0:8888/test5/name=TAO/passwd=789") helpRead(resp) resp,_ = http.Get("http://0.0.0.0:8888/test5/name=TAO/") helpRead(resp) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
注意上面定義參數的方法有兩個輔助符號: ‘:’和’’. 如果使用’:’參數方法, 那么這個參數是必須要匹配的, 例如上面的router.GET(“/test3/:name/:passwd”, func3), 當請求URL是 類似于http://0.0.0.0:8888/test3/name=TAO/passwd=123這樣的參會被匹配, 如果是http://0.0.0.0:8888/test3/name=TAO?或者http://0.0.0.0:8888/test3/passwd=123是不能匹配的. 但是如果使用’‘參數, 那么這個參數是可選的. router.GET(“/test5/:name/*passwd”, func5) 可以匹配http://0.0.0.0:8888/test5/name=TAO/passwd=789, 也可以匹配http://0.0.0.0:8888/test5/name=TAO/. 需要注意的一點是, 下面這個URL是不是能夠 匹配呢??http://0.0.0.0:8888/test5/name=TAO, 注意TAO后面沒有’/’, 這個其實就要看有沒有一個路由是到http://0.0.0.0:8888/test5/name=TAO路徑的, 如果有, 那么指定的那個函數進行處理, 如果沒有http://0.0.0.0:8888/test5/name=TAO會被重定向到http://0.0.0.0:8888/test5/name=TAO/, 然后被當前注冊的函數進行處理.
第二種:?使用gin.Context中的Query方法解析
這個類似于正常的URL中的參數傳遞, 先看服務端代碼:
// 使用Query獲取參數 func func6(c *gin.Context) {// 回復一個200 OK// 獲取傳入的參數name := c.Query("name")passwd := c.Query("passwd") c.String(http.StatusOK, "參數:%s %s test6 OK", name, passwd) } // 使用Query獲取參數 func func7(c *gin.Context) { // 回復一個200 OK // 獲取傳入的參數 name := c.Query("name") passwd := c.Query("passwd") c.String(http.StatusOK, "參數:%s %s test7 OK", name, passwd) } func main(){ router := gin.Default() // 使用gin的Query參數形式,/test6?firstname=Jane&lastname=Doe router.GET("/test6", func6) router.POST("/test7", func7) router.Run(":8888") }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
客戶端測試代碼是:
func main() { // 使用Query獲取參數形式/test6?firstname=Jane&lastname=Doeresp,_ = http.Get("http://0.0.0.0:8888/test6?name=BBB&passwd=CCC")helpRead(resp)resp,_ = http.Post("http://0.0.0.0:8888/test7?name=DDD&passwd=EEE", "",strings.NewReader("")) helpRead(resp) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
這種方法的參數也是接在URL后面, 形如http://0.0.0.0:8888/test6?name=BBB&passwd=CCC.?服務器可以使用name := c.Query(“name”)這種 方法來解析參數.
第三種:?使用gin.Context中的PostForm方法解析
我們需要將參數放在請求的Body中傳遞, 而不是URL中. 先看服務端代碼:
// 參數是form中獲得,即從Body中獲得,忽略URL中的參數 func func8(c *gin.Context) {message := c.PostForm("message")extra := c.PostForm("extra") nick := c.DefaultPostForm("nick", "anonymous") c.JSON(200, gin.H{ "status": "test8:posted", "message": message, "nick": nick, "extra": extra, }) } func main(){ router := gin.Default() // 使用post_form形式,注意必須要設置Post的type, // 同時此方法中忽略URL中帶的參數,所有的參數需要從Body中獲得 router.POST("/test8", func8) router.Run(":8888") }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
客戶端代碼是:
func main() {// 使用post_form形式,注意必須要設置Post的type,同時此方法中忽略URL中帶的參數,所有的參數需要從Body中獲得resp,_ = http.Post("http://0.0.0.0:8888/test8", "application/x-www-form-urlencoded",strings.NewReader("message=8888888&extra=999999")) helpRead(resp) }- 1
- 2
- 3
- 4
- 5
由于我們使用了request Body, 那么就需要指定Body中數據的形式, 此處是form格式, 即application/x-www-form-urlencoded. 常見的幾種http提交數據方式有: application/x-www-form-urlencoded; multipart/form-data; application/json; text/xml. 具體使用請google.?
在服務端, 使用message := c.PostForm(“message”)方法解析參數, 然后進行處理.
<3> 傳輸文件?
下面測試從client傳輸文件到server. 傳輸文件需要使用multipart/form-data格式的數據, 所有需要設定Post的類型是multipart/form-data.?
首先看服務端代碼:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
客戶端代碼是:
func main() {// 上傳文件POST// 下面構造一個文件buf作為POST的BODYbuf := new(bytes.Buffer)w := multipart.NewWriter(buf)fw,_ := w.CreateFormFile("uploadFile", "images.png") //這里的uploadFile必須和服務器端的FormFile-name一致 fd,_ := os.Open("images.png") defer fd.Close() io.Copy(fw, fd) w.Close() resp,_ = http.Post("http://0.0.0.0:8888/upload", w.FormDataContentType(), buf) helpRead(resp) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
首先客戶端本地需要有一張”images.png”圖片, 同時需要創建一個Form, 并將field-name命名為”uploadFile”, file-name命名為”images.png”. 在服務端, 通過”uploadFile”可以得到文件信息. 客戶端繼續將圖片數據copy到創建好的Form中, 將數據數據Post出去, 注意數據的類型指定! 在服務端, 通過file, header , err := c.Request.FormFile(“uploadFile”)獲得文件信息, file中就是文件數據, 將其拷貝到本地文件, 完成文件傳輸.
<4> binding數據
gin內置了幾種數據的綁定例如JSON, XML等. 簡單來說, 即根據Body數據類型, 將數據賦值到指定的結構體變量中. (類似于序列化和反序列化)?
看服務端代碼:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
客戶端代碼:
func main() { // 下面測試binding數據 // 首先測試binding-JSON, // 注意Body中的數據必須是JSON格式resp,_ = http.Post("http://0.0.0.0:8888/bindJSON", "application/json", strings.NewReader("{\"user\":\"TAO\", \"password\": \"123\"}")) helpRead(resp) // 下面測試bind FORM數據 resp,_ = http.Post("http://0.0.0.0:8888/bindForm", "application/x-www-form-urlencoded", strings.NewReader("user=TAO&password=123")) helpRead(resp) // 下面測試接收JSON和XML數據 resp,_ = http.Get("http://0.0.0.0:8888/someJSON") helpRead(resp) resp,_ = http.Get("http://0.0.0.0:8888/moreJSON") helpRead(resp) resp,_ = http.Get("http://0.0.0.0:8888/someXML") helpRead(resp) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
客戶端發送請求, 在服務端可以直接使用c.BindJSON綁定到Json結構體上. 或者使用BindWith函數也可以, 但是需要指定綁定的數據類型, 例如JSON, XML, HTML等. Bind*函數的本質是讀取request中的body數據, 拿BindJSON為例, 其核心代碼是:
func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {// 核心代碼: decode請求的body到obj中decoder := json.NewDecoder(req.Body)if err := decoder.Decode(obj); err != nil { return err } return validate(obj) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
<5> router group
router group是為了方便前綴相同的URL的管理, 其基本用法如下.?
首先看服務端代碼:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
客戶端測試代碼:
func main() {// 下面測試router 的GROUPresp,_ = http.Get("http://0.0.0.0:8888/g1/read1")helpRead(resp)resp,_ = http.Get("http://0.0.0.0:8888/g1/read2")helpRead(resp)resp,_ = http.Post("http://0.0.0.0:8888/g2/write1", "", strings.NewReader("")) helpRead(resp) resp,_ = http.Post("http://0.0.0.0:8888/g2/write2", "", strings.NewReader("")) helpRead(resp) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
在服務端代碼中, 首先創建了一個組group1 := router.Group(“/g1”), 并在這個組下注冊了兩個服務, group1.GET(“/read1”, func10) 和group1.GET(“/read2”, func11), 那么當使用http://0.0.0.0:8888/g1/read1和http://0.0.0.0:8888/g1/read2訪問時, 是可以路由 到上面注冊的位置的. 同理對于group2 := router.Group(“/g2”)也是一樣的.
<6> 靜態文件服務
可以向客戶端展示本地的一些文件信息, 例如顯示某路徑下地文件. 服務端代碼是:
func main(){router := gin.Default() // 下面測試靜態文件服務 // 顯示當前文件夾下的所有文件/或者指定文件 router.StaticFS("/showDir", http.Dir(".")) router.Static("/files", "/bin") router.StaticFile("/image", "./assets/1.png") router.Run(":8888") }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
首先你需要在服務器的路徑下創建一個assert文件夾, 并且放入1.png文件. 如果已經存在, 請忽略.?
測試代碼: 請在瀏覽器中輸入0.0.0.0:8888/showDir, 顯示的是服務器當前路徑下地文件信息:
1
輸入0.0.0.0:8888/files, 顯示的是/bin目錄下地文件信息:
2
輸入0.0.0.0:8888/image, 顯示的是服務器下地./assets/1.png圖片:
3
<7> 加載模板templates
gin支持加載HTML模板, 然后根據模板參數進行配置并返回相應的數據.?
看服務端代碼
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
客戶端測試代碼是:
func main() { // 測試加載HTML模板resp,_ = http.Get("http://0.0.0.0:8888/index")helpRead(resp) }- 1
- 2
- 3
- 4
- 5
在服務端, 我們需要加載需要的templates, 這里有兩種方法: 第一種使用LoadHTMLGlob加載所有的正則匹配的模板, 本例中使用的是*, 即匹配所有文件, 所以加載的是 templates文件夾下所有的模板. 第二種使用LoadHTMLFiles加載指定文件. 在本例服務器路徑下有一個templates目錄, 下面有一個index.tmpl模板, 模板的 內容是:
<html><h1>{ { .title } }</h1> </html>- 1
- 2
- 3
- 4
- 5
當客戶端請求/index時, 服務器使用這個模板, 并填充相應的參數, 此處參數只有title, 然后將HTML數據返回給客戶端.?
你也可以在瀏覽器請求0.0.0.0:8888/index, 效果如下圖所示:?
<8> 重定向
重定向相對比較簡單, 服務端代碼是:
func main(){router := gin.Default() // 下面測試重定向 router.GET("/redirect", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://shanshanpt.github.io/") }) router.Run(":8888") }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
客戶端測試代碼是:
func main() { // 下面測試重定向resp,_ = http.Get("http://0.0.0.0:8888/redirect")helpRead(resp) }- 1
- 2
- 3
- 4
- 5
當我們請求http://0.0.0.0:8888/redirect的時候, 會重定向到http://shanshanpt.github.io/這個站點.
<9> 使用middleware
這里使用了兩個例子, 一個是logger, 另一個是BasiAuth, 具體看服務器代碼:
func Logger() gin.HandlerFunc {return func(c *gin.Context) {t := time.Now()// 設置example變量到Context的Key中,通過Get等函數可以取得c.Set("example", "12345") // 發送request之前 c.Next() // 發送request之后 latency := time.Since(t) log.Print(latency) // 這個c.Write是ResponseWriter,我們可以獲得狀態等信息 status := c.Writer.Status() log.Println(status) } } func main(){ router := gin.Default() // 1 router.Use(Logger()) router.GET("/logger", func(c *gin.Context) { example := c.MustGet("example").(string) log.Println(example) }) // 2 // 下面測試BasicAuth()中間件登錄認證 // var secrets = gin.H{ "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, "austin": gin.H{"email": "austin@example.com", "phone": "666"}, "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, } // Group using gin.BasicAuth() middleware // gin.Accounts is a shortcut for map[string]string authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{ "foo": "bar", "austin": "1234", "lena": "hello2", "manu": "4321", })) // 請求URL: 0.0.0.0:8888/admin/secrets authorized.GET("/secrets", func(c *gin.Context) { // get user, it was set by the BasicAuth middleware user := c.MustGet(gin.AuthUserKey).(string) if secret, ok := secrets[user]; ok { c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) } else { c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) } }) router.Run(":8888") }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
客戶端測試代碼是:
func main() { // 下面測試使用中間件resp,_ = http.Get("http://0.0.0.0:8888/logger")helpRead(resp) // 測試驗證權限中間件BasicAuthresp,_ = http.Get("http://0.0.0.0:8888/admin/secrets") helpRead(resp) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
服務端使用Use方法導入middleware, 當請求/logger來到的時候, 會執行Logger(), 并且我們知道在GET注冊的時候, 同時注冊了匿名函數, 所有請看Logger函數中存在一個c.Next()的用法, 它是取出所有的注冊的函數都執行一遍, 然后再回到本函數中, 所以, 本例中相當于是先執行了 c.Next()即注冊的匿名函數, 然后回到本函數繼續執行. 所以本例的Print的輸出順序是:?
log.Println(example)?
log.Print(latency)?
log.Println(status)?
如果將c.Next()放在log.Print(latency)后面, 那么log.Println(example)和log.Print(latency)執行的順序就調換了. 所以一切都取決于c.Next()執行的位置. c.Next()的核心代碼如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
它其實是執行了后面所有的handlers.?
關于使用gin.BasicAuth() middleware, 可以直接使用一個router group進行處理, 本質和logger一樣.
<10> 綁定http server
之前所有的測試中, 我們都是使用router.Run(“:8888”)開始執行監聽, 其實還有兩種方法:
// 方法二 http.ListenAndServe(":8888", router) // 方法三: server := &http.Server{Addr: ":8888", Handler: router, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } server.ListenAndServe()- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
至此, gin最基本的一些應用都整理完了, 下面就具體看看代碼中的一些實現. 有時間再記錄吧.?
寫幾個注意事項把:?
1.中間件的使用插拔可以這樣寫:router.Use(MymiddleWare),寫在該語句后面的路由轉發,都會經過中間件的過濾,之前的不受影響,對單個路由轉發使用一個或者多個中間件可以這樣寫 router.GET(“/”,MymiddleWare1,MymiddleWare2,HandleFunc)?
2.對c *gin.Context的c.Query(),c.Param()都只適用于GET請求的地址后的參數,如果要接收POST數據,必須使用c.Bind(&buf)
3.參考:
gin-github
轉載于:https://www.cnblogs.com/ExMan/p/10100093.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Go语言web框架 gin的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode7——Reverse I
- 下一篇: 渠道生意宝android版,生意宝app