Gin框架入门|青训营

Gin

Gin是一个golang的微框架,基于 httprouter,封装比较优雅,API友好,源码注释比较明确。具有快速灵活,容错率高,高性能等特点。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

Gin 包括以下几个主要的部分:

设计精巧的路由/中间件系统; 简单好用的核心上下文 Context; 附赠工具集(JSON/XML 响应, 数据绑定与校验等)。 Gin 是一个 Golang 写的 web 框架,,它提供了类似martini但更好性能(路由性能约快40倍)的API服务。

最好的学习一个技术的方法就是阅读他的官方文档,像gin的文档有中文版本更适合我们更深一步的学习,在带领大家入门之后可以尝试着,自己阅读文档来学习了

官方地址:github.com/gin-gonic/g...

gin的安装

在go编译器的终端执行以下代码
$ go get -u github.com/gin-gonic/gin

这样就完成了gin依赖的导入了

快速入门

现在让我们来写一个简单的服务来了解下gin

go 复制代码
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服务的基础

javascript 复制代码
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用来删除资源。

javascript 复制代码
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"    
    })    
})

获取请求路径的参数

go 复制代码
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请求参数的方式

css 复制代码
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请求的方式

css 复制代码
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")
}

这种方式并不常见,一般会通过绑定的方式来获取前端提交的表单,后面会提到

单文件上传

go 复制代码
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")
}

多文件上传

go 复制代码
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

路由分组

通过路由分组可以让功能之间相互独立出来,使得操作起来更加简洁

go 复制代码
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

    1. Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML
    2. 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

    1. Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
    2. Behavior - 这些方法底层使用 ShouldBindWith,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。

当我们使用绑定方法时,Gin会根据Content-Type推断出使用哪种绑定器,如果你确定你绑定的是什么,你可以使用MustBindWith或者BindingWith

你还可以给字段指定特定规则的修饰符,如果一个字段用binding:"required"修饰,并且在绑定时该字段的值为空,那么将返回一个错误。

rust 复制代码
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的要求,这是最基础的参数验证方式,如果想要更严格的参数验证方式,需要我们利用自定义参数验证器,或者第三方的字段验证器来进行,后面的文章会提到

typescript 复制代码
// 绑定为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重定向很容易,支持内部和外部链接

外部连接

go 复制代码
r.GET("/test", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})

路由链接

javascript 复制代码
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类型

css 复制代码
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() 阻止调用后续的处理函数

scss 复制代码
//计算执行程序花费的时间
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框架与数据库连接起来,创建一个比较完整的服务端

相关推荐
CallBack8 个月前
Typora+PicGo+阿里云OSS搭建个人图床,纵享丝滑!
前端·青训营笔记
Taonce1 年前
站在Android开发者的角度认识MQTT - 源码篇
android·青训营笔记
AB_IN1 年前
打开抖音会发生什么 | 青训营
青训营笔记
monster1231 年前
结营感受(go) | 青训营
青训营笔记
翼同学1 年前
实践记录:使用Bcrypt进行密码安全性保护和验证 | 青训营
青训营笔记
hu1hu_1 年前
Git 的正确使用姿势与最佳实践(1) | 青训营
青训营笔记
星曈1 年前
详解前端框架中的设计模式 | 青训营
青训营笔记
tuxiaobei1 年前
文件上传漏洞 Upload-lab 实践(中)| 青训营
青训营笔记
yibao1 年前
高质量编程与性能调优实战 | 青训营
青训营笔记
小金先生SG1 年前
阿里云对象存储OSS使用| 青训营
青训营笔记