Gin
Gin是一个golang的微框架,基于 httprouter,封装比较优雅,API友好,源码注释比较明确。具有快速灵活,容错率高,高性能等特点。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。
Gin 包括以下几个主要的部分:
设计精巧的路由/中间件系统;
简单好用的核心上下文 Context;
附赠工具集(JSON/XML 响应, 数据绑定与校验等)。
Gin 是一个 Golang 写的 web 框架,,它提供了类似martini但更好性能(路由性能约快40倍)的API服务。
最好的学习一个技术的方法就是阅读他的官方文档,像gin的文档有中文版本更适合我们更深一步的学习,在带领大家入门之后可以尝试着,自己阅读文档来学习了
gin的安装
在go编译器的终端执行以下代码$ go get -u github.com/gin-gonic/gin
这样就完成了gin依赖的导入了
快速入门
现在让我们来写一个简单的服务来了解下gin
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
在浏览器访问http://localhost:8080/ping 如果返回pong则成功
让我们看看这段代码干了什么r := gin.Default()
这条代码,通过gin框架自带的方式,创建了一个默认的网络服务,r就是以后web服务的基础
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
上面这条语句可以分开看,先看前半部分,是由之前创建好的web服务r调用了它的GET方法,然后向里面传入了几个参数,先看第一个,”/ping”,这个和咱们访问浏览器的url后半段一致,他就是请求咱们构建的服务的路径,因为会有很多不同的请求,所以要规定每个方法的路径,第二个参数是一个方法,是调用这个请求后的返回结果,这里返回的结果可以有很多种类型,这里就是返回了最常见的一种Json类型,这种类型方便前端来接收和使用
RESTful API
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
GET用来获取资源
POST用来新建资源
PUT用来更新资源
DELETE用来删除资源。
r.GET("/book", func(c *gin.Context){
c.JSON(200, gin.H{
"method":"GET"
})
})
r.POST("/book",func(c *gin.Context){
c.JSON(200, gin.H{
"method":"POST"
})
})
r.PUT("/book",func(c *gin.Context){
c.JSON(200, gin.H{
"method":"PUT"
})
})
r.DELETE("/book",func(c *gin.Context){
c.JSON(200, gin.H{
"method":"DELETE"
})
})
获取请求路径的参数
func main() {
router := gin.Default()
// 此规则能够匹配/user/john这种格式,但不能匹配/user/ 或 /user这种格式
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
// 但是,这个规则既能匹配/user/john/格式也能匹配/user/john/send这种格式
// 如果没有其他路由器匹配/user/john,它将重定向到/user/john/
router.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
message := name + " is " + action
c.String(http.StatusOK, message)
})
router.Run(":8080")
}
这种方式能够获取到url里面的参数,一般用于restful的路径传参等等
获取Get请求参数的方式
func main() {
router := gin.Default()
// 匹配的url格式: /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // 是 c.Request.URL.Query().Get("lastname") 的简写
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}
可以通过c,Query(“参数名”)的方式来拿到前端的query参数
获取Post请求的方式
func main() {
router := gin.Default()
router.POST("/form_post", func(c *gin.Context) {
message := c.PostForm("message")
nick := c.DefaultPostForm("nick", "anonymous") // 此方法可以设置默认值
c.JSON(200, gin.H{
"status": "posted",
"message": message,
"nick": nick,
})
})
router.Run(":8080")
}
这种方式并不常见,一般会通过绑定的方式来获取前端提交的表单,后面会提到
单文件上传
func main() {
router := gin.Default()
// 给表单限制上传大小 (默认 32 MiB)
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
// 上传文件到指定的路径
// c.SaveUploadedFile(file, dst)
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
router.Run(":8080")
}
多文件上传
func main() {
router := gin.Default()
// 给表单限制上传大小 (默认 32 MiB)
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// 多文件
form, _ := c.MultipartForm()
files := form.File["upload[]"]
for _, file := range files {
log.Println(file.Filename)
// 上传文件到指定的路径
// c.SaveUploadedFile(file, dst)
}
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
})
router.Run(":8080")
}
关于文件上传,肯定不能够将文件存储到本地啦,后面的文章会讲到将文件存储到服务器中,通过Minio
路由分组
通过路由分组可以让功能之间相互独立出来,使得操作起来更加简洁
func main() {
router := gin.Default()
// 创建一个路由组,访问每个组内成员都要加上固定的前置url/v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// Simple group: v2
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
模型绑定和验证
若要将请求主体绑定到结构体中,请使用模型绑定,目前支持JSON、XML、YAML和标准表单值(foo=bar&boo=baz)的绑定。
Gin使用 go-playground/validator.v8 验证参数,查看完整文档。
需要在绑定的字段上设置tag,比如,绑定格式为json,需要这样设置 json:"fieldname"
。
此外,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
相关的方法
- Methods –
-
Should bind
- Methods –
ShouldBind
,ShouldBindJSON
,ShouldBindXML
,ShouldBindQuery
,ShouldBindYAML
- Behavior – 这些方法底层使用
ShouldBindWith
,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。
- Methods –
当我们使用绑定方法时,Gin会根据Content-Type
推断出使用哪种绑定器,如果你确定你绑定的是什么,你可以使用MustBindWith
或者BindingWith
。
你还可以给字段指定特定规则的修饰符,如果一个字段用binding:"required"
修饰,并且在绑定时该字段的值为空,那么将返回一个错误。
type User struct{
username string 'json:"Username" binding:"required"'
password string 'json:""Password bingding:"required,min=3,max=20"'
}
上面结构体的tag就表明username被声明为Username的json格式,并且不为空,password还有着最小为3,最大为20的要求,这是最基础的参数验证方式,如果想要更严格的参数验证方式,需要我们利用自定义参数验证器,或者第三方的字段验证器来进行,后面的文章会提到
// 绑定为json
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.Default()
// Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding XML (
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <user>user</user>
// <password>123</password>
// </root>)
router.POST("/loginXML", func(c *gin.Context) {
var xml Login
if err := c.ShouldBindXML(&xml); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if xml.User != "manu" || xml.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding a HTML form (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
最常见的参数绑定方式就是利用ShouldBind让gin框架自动的将表单中的数据与结构体对应上,这就要求结构体的tag必须严格按照规范来写,否则就有可能出现参数丢失的情况
重定向
gin框架支持重定向,即将访问本url的请求转到其他url上,发布HTTP重定向很容易,支持内部和外部链接
外部连接
r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})
路由链接
r.GET("/test", func(c *gin.Context) {
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(200, gin.H{"hello": "world"})
})
中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
定义中间件
Gin中的中间件必须是一个gin.HandlerFunc类型
func indexHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
func main(){
r := gin.Default()
r.GET("index", indexHandler)
r.Run()
}
注册中间件
在gin框架中,我们可以为每个路由添加任意数量的中间件。r.GET("index",m1,... indexHandler)
c.Next() 调用后续的处理函数
c.Abort() 阻止调用后续的处理函数
//计算执行程序花费的时间
func m1(c *gin.Context){
start := time.Now()
c.Next()
cost := time.Since(start)
fmt.Println("cost:%v\n",cost)
}
//
gin默认中间件
gin.Default()默认使用了Logger和Recovery中间件,其中:
Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。
如果你了解了以上知识,那么就可以试着自己来创建一个简单的服务,感受一下gin框架的功能吧,进一步的学习会在之后的文章中,我们会将gin框架与数据库连接起来,创建一个比较完整的服务端