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案例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>.路由分组
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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方法参数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 ", user, passwd)) }) //启动路由并指定监听的地址及端口,若不指定默认监听0.0.0.0:8080 router.Run("172.30.100.101:9000") }
3>.获取路径中的参数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 ", user, passwd)) }) //启动路由并指定监听的地址及端口,若不指定默认监听0.0.0.0:8080 router.Run("172.30.100.101:9000") }
4>.获取POST方法参数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>.单文件上传
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>.多文件上传
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>.模型绑定
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 ", }) } else { context.JSON(http.StatusUnauthorized, gin.H{ "Status": "Login failed ", }) } }) //启动路由并指定监听的地址及端口,若不指定默认监听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>.自定义中间件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 ") }) //启动路由并指定监听的地址及端口,若不指定默认监听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>.自定义日志中间件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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" ", //客户端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, "自定义日志中间件 ") }) //启动路由并指定监听的地址及端口,若不指定默认监听0.0.0.0:8080 router.Run("172.30.100.101:9000") }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[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" "