Gin教程(1):快速开始+路由+获取、绑定参数+各种响应方式

  • 108
  • 2022年6月11日21:31:43

Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json,在go web领域使用广泛。【本文来自凡蜕博客

快速开始

go get -u github.com/gin-gonic/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(":8080")   // 可以用 http.ListenAndServe(":8080", router) 替代
}

自定义http服务器配置:

func main() {
    router := gin.Default()

    s := &http.Server{
        Addr:           ":8080",
        Handler:        router,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    s.ListenAndServe()
}

路由

func main() {
    r := gin.Default()  //返回已添加Logger和Recovery中间件的Engine实例。
    r.GET("/", func(c *gin.Context) { //r.GET 是 router.Handle("GET", path, handle) 的快捷方式。
        c.String(http.StatusOK, "hello word")
    })
 r.GET("/someGet", getting)
    r.POST("/somePost", posting)
    r.PUT("/somePut", putting)
    r.DELETE("/someDelete", deleting)
    r.PATCH("/somePatch", patching)
    r.HEAD("/someHead", head)
    r.OPTIONS("/someOptions", options)

    r.Run(":8000")
}

路由组

func main() {
    router := gin.Default()

    v1 := router.Group("/v1")  //前缀都是v1
    {
        v1.GET("/login", loginEndpoint)
        v1.GET("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }

    v2 := router.Group("/v2")
    {
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
    }

    router.Run(":8080")
}
func loginEndpoint(c *gin.Context) {
    name := c.DefaultQuery("name", "Guest") //可设置默认值
    c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}

func submitEndpoint(c *gin.Context) {
    name := c.DefaultQuery("name", "Guest") //可设置默认值
    c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}

func readEndpoint(c *gin.Context) {
    name := c.DefaultQuery("name", "Guest") //可设置默认值
    c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}

路由拆分

https://www.topgoer.com/gin%E6%A1%86%E6%9E%B6/gin%E8%B7%AF%E7%94%B1/%E8%B7%AF%E7%94%B1%E6%8B%86%E5%88%86%E4%B8%8E%E6%B3%A8%E5%86%8C.html

获取参数

用于需要单独获取参数来操作。

获取API参数

  • 冒号:加上一个参数名组成路由参数,可以使用c.Params的方法读取其值

  • 除了:,gin还提供了*号处理参数,*号能匹配的规则就更多

    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)
    })

获取URL参数

  • URL参数可以通过DefaultQuery()或Query()方法获取

  • DefaultQuery()若参数不存在,返回指定的默认值;Query()若不存在,返回空字符串串""

    func main() {
      r := gin.Default()
      r.GET("/user", func(c *gin.Context) {
          name := c.DefaultQuery("name", "枯藤")
          c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
      })
      r.Run()
    }

获取表单参数

  • 表单传输为post请求,http常见的传输格式为四种:

    • application/json
    • application/x-www-form-urlencoded,form无非就是把query string的内容,放到了body体里,同样也需要urlencode
    • application/xml
    • multipart/form-data,用于文件上传
  • 表单参数可以通过PostForm()方法获取(DefaultPostForm()能指定默认值),默认情况下,c.PostFROM解析的是x-www-form-urlencodedfrom-data的参数。

    func main() {
      r := gin.Default()
      r.POST("/form", func(c *gin.Context) {
          types := c.DefaultPostForm("type", "post") // 可设置默认值
          username := c.PostForm("username")
          password := c.PostForm("password")
    
          c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))
      })
      r.Run()
    }

绑定数据

直接将前端的数据绑定到结构体。
【本文来自凡蜕博客

Gin提供了两套绑定方法:

  • Should bind(推荐)

    • 方法:ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML

    • 行为:这些方法使用ShouldBindWith。如果存在绑定错误,则返回错误,开发人员有责任适当地处理请求和错误。

  • Must bind(不推荐,别看)

    • 方法:Bind, BindJSON, BindXML, BindQuery, BindYAML
    • 行为:这些方法使用MustBindWith。如果存在绑定错误,则用c终止请求,使用c.AbortWithError (400) .SetType (ErrorTypeBind)即可。将响应状态代码设置为400,Content-Type header设置为text/plain;charset = utf - 8。请注意,如果在此之后设置响应代码,将会受到警告:[GIN-debug][WARNING] Headers were already written. Wanted to override status code 400 with 422将导致已经编写了警告[GIN-debug][warning]标头。如果想更好地控制行为,可以考虑使用ShouldBind等效方法。

注意,使用绑定方法时,Gin 会根据请求头中 Content-Type 来自动判断需要解析的类型。如果你明确绑定的类型,你可以不用自动推断,而用 BindWith 方法。 你也可以指定某字段是必需的。如果一个字段被 binding:"required" 修饰而值却是空的,请求会失败并返回错误。

JSON绑定

将前端post过来的json数据绑定到结构体里。Content-Type是 application/json

type Login struct {
    User     string `form:"username" json:"user" uri:"user" xml:"user"  binding:"required"`
    Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
    router := gin.Default()

    // 示例 JSON ({"user": "hanru", "password": "hanru123"})
    router.POST("/loginJSON", func(c *gin.Context) {
        var login Login
        // 其实就是将request中的Body中的数据按照JSON格式解析到json变量中
        if err := c.ShouldBindJSON(&login); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        if login.User != "hanru" || login.Password != "hanru123" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    })

    router.Run(":8080")
}

对于嵌套json的实现,嵌套gin.H即可

Form绑定

将前端post过来的form数据绑定到结构体里

type Login struct {
    User     string `form:"username" json:"user" uri:"user" xml:"user"  binding:"required"`
    Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}   

router.POST("/loginForm", func(c *gin.Context) {
        var form Login
        // 方法一:对于FORM数据直接使用Bind函数, 默认使用使用form格式解析,if c.Bind(&form) == nil
        // 根据请求头中 content-type 自动推断.
        if err := c.Bind(&form); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        if form.User != "hanru" || form.Password != "hanru123" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})

        //方法二: 使用BindWith函数,如果你明确知道数据的类型
        // 你可以显式声明来绑定多媒体表单:
        // c.BindWith(&form, binding.Form)
        // 或者使用自动推断:
        if c.BindWith(&form, binding.Form) == nil {
            if form.User == "user" && form.Password == "password" {
                c.JSON(200, gin.H{"status": "you are logged in ..... "})
            } else {
                c.JSON(401, gin.H{"status": "unauthorized"})
            }
        }
    })

URL绑定

将url里的参数绑定到结构体里

// 定义接收数据的结构体
type Login struct {
    // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
    User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // JSON绑定
    r.GET("/:user/:password", func(c *gin.Context) {
        // 声明接收的变量
        var login Login
        // Bind()默认解析并绑定form格式
        // 根据请求头中content-type自动推断
        if err := c.ShouldBindUri(&login); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        // 判断用户名密码是否正确
        if login.User != "root" || login.Pssword != "admin" {
            c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "200"})
    })
    r.Run(":8000")
}

响应

通常响应会有html,text,plain,json和xml等

json、结构体响应、XML、YAML响应、protobuf格式响应:

// 多种响应方式
func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()

    // 1.json
    r.GET("/someJSON", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "someJSON", "status": 200})
    })

    // 2. 结构体响应
    r.GET("/someStruct", func(c *gin.Context) {
        var msg struct {
            Name    string
            Message string
            Number  int
        }
        msg.Name = "root"
        msg.Message = "message"
        msg.Number = 123
        c.JSON(200, msg)
    })

    // 3.XML
    r.GET("/someXML", func(c *gin.Context) {
        c.XML(200, gin.H{"message": "abc"})
    })

    // 4.YAML响应
    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(200, gin.H{"name": "zhangsan"})
    })

    // 5.protobuf格式,谷歌开发的高效存储读取的工具
    // 数组?切片?如果自己构建一个传输格式,应该是什么格式?
    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        // 定义数据
        label := "label"
        // 传protobuf格式数据
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        c.ProtoBuf(200, data)
    })

    r.Run(":8080")
}

模板渲染

func main() {
    router := gin.Default()
    //加载模板。先要使用 LoadHTMLGlob() 或者 LoadHTMLFiles()方法
    router.LoadHTMLGlob("templates/*")
    //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
    //定义路由
    router.GET("/index", func(c *gin.Context) {
        //根据完整文件名渲染模板,并传递参数
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
            "title": "Main website",
        })
    })
    router.Run(":8080")
}

不同文件夹下模板名字可以相同,此时需要 LoadHTMLGlob() 加载两层模板路径。

router.LoadHTMLGlob("templates/**/*")
    router.GET("/posts/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
            "title": "Posts",
        })
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "Users",
        })
    })

文件响应

func main() {
    router := gin.Default()
    // 下面测试静态文件服务
    // 显示当前文件夹下的所有文件/或者指定文件
    router.StaticFS("/showDir", http.Dir("."))
    router.StaticFS("/files", http.Dir("/bin"))
    //Static提供给定文件系统根目录中的文件。
    //router.Static("/files", "/bin")
    router.StaticFile("/image", "./assets/miao.jpg")

    router.Run(":8080")
}

重定向

func main() {
    r := gin.Default()
    r.GET("/redirect", func(c *gin.Context) {
        //支持内部和外部的重定向
        c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
    })

    r.Run(":8080")
}

同步异步

  • goroutine机制可以方便地实现异步处理
  • 在启动新的goroutine时,不应该使用原始上下文,必须使用它的只读副本
func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // 1.异步
    r.GET("/long_async", func(c *gin.Context) {
        // 需要搞一个副本
        copyContext := c.Copy()
        // 异步处理
        go func() {
            time.Sleep(3 * time.Second)
            log.Println("异步执行:" + copyContext.Request.URL.Path)
        }()
    })
    // 2.同步
    r.GET("/long_sync", func(c *gin.Context) {
        time.Sleep(3 * time.Second)
        log.Println("同步执行:" + c.Request.URL.Path)
    })

    r.Run(":8000")
}

    80%的人都看过的文章

本文来自凡蜕博客(https://blog.ysboke.cn), 转载请带上地址.。
匿名

发表评论

匿名网友