Go語言的Gin框架快速入門篇
作者:尹正傑
版權聲明:原創作品,謝絕轉載!否則將追究法律責任。
一.Gin框架概述
1>.Go語言的Web框架概述
框架就是別人寫好的代碼我們可以直接使用,這個代碼是專門針對一個開發方向定制的。例如,我們要做一個網站,利用框架就能非常塊的完成網站的開發,如果沒有框架,每個細節都需要我們處理,開發時間會大大降低。 Go語言常見的web框架有beego,gin,echo,iris等等。值得一提的是,beego框架是咱們國人謝孟軍開發的,其地位和Python的Django有點類似,而gin框架的地位和python的flask有點類似。 綜上所述,如果你要做純web開發推薦大家使用beego,如果你僅僅是為了寫一些后端API接口推薦大家使用gin框架,今天我們就一起來學習一下Gin框架。 博主推薦閱讀: https://beego.me/ https://github.com/gin-gonic/gin
2>.Gin框架概述
gin是使用golang開發的web框架.簡單易用,高性能(是httprouter的40倍),適用於生產環境
gin特點如下:
快:
路由使用基數樹,低內存,不使用反射;
中間件注冊:
一個請求可以被一系列的中間件和最后的action處理
奔潰處理:
gin可以捕獲panic使應用程序可用
JSON校驗:
將請求的數據轉換為JSON並校驗
路由組:
更好的組織路由的方式,無限制嵌套而不影響性能
錯誤管理:
可以收集所有的錯誤
內建渲染方式:
JSON,XML和HTML渲染方式
可繼承:
簡單的去創建中間件
3>.Gin框架運行原理
MVC模型如下所示:
模型(Model):
數據庫管理與設計。
控制器(Controller):
處理用戶輸入的信息,負責從視圖讀取數據,控制用戶輸入,並向模型發送數據源,是應用程序中處理用戶交互的部分,負責管理與用戶交互控制。
視圖(View):
將信息顯示給用戶。
Gin框架的運行流程如下圖所示。
4>.Gin和Beego框架對比
MVC:
Gin框架不完全支持,而beego完全支持。
Web功能:
Gin框架支持的不全面,比如Gin框架不是支持正則路由,不支持session。而beego支持的很全面。
使用場景:
Gin適合使用在封裝API接口,而beego適合web項目。
5>.安裝Gin組件
go get github.com/gin-gonic/gin
6>.Hello World案例

package main import "github.com/gin-gonic/gin" func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() //定義路由的GET方法及響應的處理函數 router.GET("/hello", func(c *gin.Context) { //將發送的消息封裝成JSON發送給瀏覽器 c.JSON(200, gin.H{ //這是咱們定義的數據 "message": "Hello World!", }) }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("127.0.0.1:9000") }
二.Gin框架快速入門案例
1>.路由分組

package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() /** 路由分組: 在大型項目中,會經常使用到路由分組技術。 路由分組有點類似於Django創建各種app,其目的就是將項目有組織的划分成多個模塊。 */ //定義group1路由組 group1 := router.Group("group1") { group1.GET("/login", func(context *gin.Context) { context.String(http.StatusOK, "<h1>Login successful</h1>") }) } //定義group2路由組 group2 := router.Group("group2") { group2.GET("/logout", func(context *gin.Context) { context.String(http.StatusOK, "<h3>Logout</h3>") }) } //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("127.0.0.1:9000") }
2>.獲取GET方法參數

package main import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() router.GET("/blog", func(context *gin.Context) { //獲取GET方法參數 user := context.Query("user") //獲取GET方法帶默認值參數,如果沒有則返回默認值"yinzhengjie" passwd := context.DefaultQuery("passwd", "yinzhengjie") //將獲取到的數據返回給客戶端 context.String(http.StatusOK, fmt.Sprintf("%s:%s\n", user, passwd)) }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("172.30.100.101:9000") }
3>.獲取路徑中的參數

package main import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() /** ":user" 表示user字段必須存在,否則會報錯404。 "*passwd": 表示action字段可以存在或不存在。 */ router.GET("/blog/:user/*passwd", func(context *gin.Context) { //獲取路徑中的參數 user := context.Param("user") passwd := context.Param("passwd") //將獲取到的數據返回給客戶端 context.String(http.StatusOK, fmt.Sprintf("%s:%s\n", user, passwd)) }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("172.30.100.101:9000") }
4>.獲取POST方法參數

package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() router.POST("/blog", func(context *gin.Context) { //從POST方法獲取參數 user := context.PostForm("user") //獲取POST方法帶默認值參數,如果沒有則返回默認值"yinzhengjie" passwd := context.DefaultPostForm("passwd", "yinzhengjie") //將獲取到的數據返回給客戶端 context.JSON(http.StatusOK, gin.H{ "status": "POST", "USER": user, "PASSWD": passwd, }) }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("172.30.100.101:9000") }
5>.單文件上傳

package main import ( "fmt" "github.com/gin-gonic/gin" "log" "net/http" ) func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() // 給表單限制上傳大小 (默認 32 MiB) // router.MaxMultipartMemory = 8 << 20 // 配置8MiB router.POST("/upload", func(c *gin.Context) { // 單文件 file, _ := c.FormFile("file") log.Println(file.Filename) //底層采用流拷貝(io.Copy)技術,將上傳文件到指定的路徑 //c.SaveUploadedFile(file, dst) c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("172.30.100.101:9000") /** 使用curl命令測試: [root@yinzhengjie.com ~]# curl -X POST http://172.30.100.101:9000/upload -F "file=@/root/dpt" -H "Content-Type: multipart/form-data" */ }
6>.多文件上傳

package main import ( "fmt" "github.com/gin-gonic/gin" "log" "net/http" ) func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() // 給表單限制上傳大小 (默認 32 MiB) // router.MaxMultipartMemory = 8 << 20 // 配置8MiB router.POST("/upload", func(c *gin.Context) { // 多文件 form, _ := c.MultipartForm() files := form.File["upload[]"] for _, file := range files { log.Println(file.Filename) //底層采用流拷貝(io.Copy)技術,將上傳文件到指定的路徑 // c.SaveUploadedFile(file, dst) } c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("172.30.100.101:9000") /** 使用curl命令測試: [root@yinzhengjie.com ~]# curl -X POST http://172.30.100.101:9000/upload -F "upload[]=@/etc/issue" -F "upload[]=@/etc/passwd" -H "Content-Type: multipart/form-data" */ }
7>.模型綁定

package main import ( "github.com/gin-gonic/gin" "net/http" ) type Login struct { /** 模型綁定: 若要將請求主體綁定到結構體中,請使用模型綁定,目前支持JSON、XML、YAML和標准表單值(foo=bar&boo=baz)的綁定。 需要在綁定的字段上設置tag,比如,綁定格式為json,需要這樣設置 json:"fieldname" 。 你還可以給字段指定特定規則的修飾符,如果一個字段用binding:"required"修飾,並且在綁定時該字段的值為空,那么將返回一個錯。 程序通過tag區分你傳遞參數的數據格式,從而自動解析相關參數. */ User string `form:"user" json:"user" xml:"user" binding:"required"` Passwd string `form:"passwd" json:"passwd" xml:"passwd" binding:"required"` } func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() router.POST("/login", func(context *gin.Context) { //定義接收請求的數據 var login_user Login /** Gin還提供了兩套綁定方法: Must bind: Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML Behavior: 這些方法底層使用MustBindWith,如果存在綁定錯誤,請求將被以下指令中止 c.AbortWithError(400, err).SetType(ErrorTypeBind), 響應狀態代碼會被設置為400,請求頭Content-Type被設置為text/plain; charset=utf-8。 注意,如果你試圖在此之后設置響應代碼,將會發出一個警告 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422。 如果你希望更好地控制行為,請使用ShouldBind相關的方法。 Should bind Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML Behavior: 這些方法底層使用 ShouldBindWith,如果存在綁定錯誤,則返回錯誤,開發人員可以正確處理請求和錯誤。 當我們使用綁定方法時,Gin會根據Content-Type推斷出使用哪種綁定器,如果你確定你綁定的是什么,你可以使用MustBindWith或者BindingWith。 */ err := context.ShouldBind(&login_user) //如果綁定出錯了就將錯誤信息直接發送給前端頁面. if err != nil { context.JSON(http.StatusBadRequest, gin.H{ "Error": err.Error(), }) } //將結構體綁定后,如果沒有報錯就可以解析到相應數據,此時我們驗證用戶名和密碼,驗證成功返回200狀態碼,驗證失敗返回401狀態碼 if login_user.User == "yinzhengjie" && login_user.Passwd == "123" { context.JSON(http.StatusOK, gin.H{ "Status": "Login successful\n", }) } else { context.JSON(http.StatusUnauthorized, gin.H{ "Status": "Login failed\n", }) } }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("172.30.100.101:9000") /** 使用curl命令進行測試: [root@yinzhengjie.com ~]# curl -X POST http://172.30.100.101:9000/login -H 'content-type: application/json' -d '{ "user": "yinzhengjie","passwd":"123"}' */ }
三.response及中間件
1>.什么是Context
Context作為一個數據結構在中間件中傳遞本次請求的各種數據、管理流程,進行響應在請求來到服務器后,Context對象會生成用來串流程。
2>.響應(response)周期
整個響應(response)周期:
(1)路由,找到處理函數(handle) (2)將請求和響應用Context包裝起來供業務代碼使用 (3)依次調用中間件和處理函數 (4)輸出結果 因為golang原生為web而生而提供了完善的功能,用戶需要關注的東西大多數是業務邏輯本身了。
gin能做的事情也是去把ServeHTTP(ResponseWriter, *Request)做得高效、友好。一個請求來到服務器了,ServeHTTP會被調用。
3>.設置返回數據
4>.自定義中間件

package main import ( "github.com/gin-gonic/gin" "net/http" ) /** 自定義一個中間件功能: 返回的包頭(header)信息有咱們自定義的包頭信息。 */ func ResponseHeaders() gin.HandlerFunc { return func(context *gin.Context) { //自定義包頭信息 context.Header("Access-Control-Allow-Origin", "*") context.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CRSF-Token,Authorization,Token") context.Header("Access-Control-Allow-Methods", "POST,GET,DELETE,OPTIONS") context.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type") context.Header("Access-Control-Allow-Credentials", "true") //使用"context.Next()"表示繼續調用其它的內置中間件,也可以立即終止調用其它的中間件使用"context.Abort()" context.Next() } } func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.Default() //綁定咱們自己定義的中間件 router.Use(ResponseHeaders()) router.GET("/middle", func(context *gin.Context) { context.String(http.StatusOK, "Response OK\n") }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("172.30.100.101:9000") /** 使用curl命令測試: [root@yinzhengjie.com ~]# curl -v http://172.30.100.101:9000/middle */ }
5>.自定義日志中間件

package main import ( "fmt" "github.com/gin-gonic/gin" "io" "net/http" "os" "time" ) func main() { /** 所有的接口都要由路由來進行管理。 Gin的路由支持GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS等請求 同時還有一個Any函數,可以同時支持以上的所有請求。 創建路由(router)並引入默認中間件 router := gin.Default() 在源碼中,首先是New一個engine,緊接着通過Use方法傳入了Logger()和Recovery()這兩個中間件。 其中 Logger 是對日志進行記錄,而 Recovery 是對有 painc時, 進行500的錯誤處理。 創建路由(router)無中間件 router := gin.New() */ router := gin.New() //創建一個日志文件 f, _ := os.Create("gin.log") //默認數據寫入到終端控制台(os.Stdout),我們需要將日志寫到咱們剛剛創建的日志文件中 gin.DefaultWriter = io.MultiWriter(f) //自定義你的日志格式 logger := func(params gin.LogFormatterParams) string { return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", //客戶端IP params.ClientIP, //請求時間 params.TimeStamp.Format(time.RFC1123), //請求方法 params.Method, //請求路徑 params.Path, //請求協議 params.Request.Proto, //請求的狀態碼 params.StatusCode, //請求延遲(耗時) params.Latency, //請求的客戶端類型 params.Request.UserAgent(), //請求的錯誤信息 params.ErrorMessage, ) } //LoggerWithFormatter 中間件會將日志寫入 gin.DefaultWriter router.Use(gin.LoggerWithFormatter(logger)) router.GET("/log", func(context *gin.Context) { context.String(http.StatusOK, "自定義日志中間件\n") }) //啟動路由並指定監聽的地址及端口,若不指定默認監聽0.0.0.0:8080 router.Run("172.30.100.101:9000") }

[GIN-debug] GET /log --> main.main.func2 (2 handlers) [GIN-debug] Listening and serving HTTP on 172.30.100.101:9000 172.30.100.101 - [Fri, 15 May 2020 06:23:42 CST] "GET /log HTTP/1.1 200 0s "curl/7.29.0" " 172.30.100.101 - [Fri, 15 May 2020 06:23:43 CST] "GET /log HTTP/1.1 200 0s "curl/7.29.0" " 172.30.100.101 - [Fri, 15 May 2020 06:23:46 CST] "GET /log HTTP/1.1 200 0s "curl/7.29.0" " 172.30.100.101 - [Fri, 15 May 2020 06:23:47 CST] "GET /log HTTP/1.1 200 0s "curl/7.29.0" " 172.30.100.101 - [Fri, 15 May 2020 06:23:48 CST] "GET /log HTTP/1.1 200 0s "curl/7.29.0" "