Gin快速入门2

简介: Gin快速入门2

Gin模板渲染

       我们首先定义一个存放模板文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。 posts/index.html文件的内容如下:

package main
import (
  "github.com/gin-gonic/gin"
  "net/http"
)
func main() {
  //创建路由
  r := gin.Default()
  //定义模板
  //解析模板
  r.LoadHTMLGlob("template/**/*")
  //执行模板
  r.GET("posts/index", func(c *gin.Context) {
    //向页面返回一个html
    c.HTML(http.StatusOK, "post/index.html", gin.H{
      "title": "posts/index",
    })
  })
  r.GET("users/index", func(c *gin.Context) {
    //向页面返回一个html
    c.HTML(http.StatusOK, "users/index.html", gin.H{
      "title": "users/index",
    })
  })
  r.Run(":9090")
}

自定义函数模板

package main
import (
  "github.com/gin-gonic/gin"
  "html/template"
  "net/http"
)
func main() {
  //创建路由
  r := gin.Default()
  r.SetFuncMap(template.FuncMap{
    "safe": func(str string) template.HTML {
      return template.HTML(str)
    },
  })
  //定义模板
  //解析模板
  r.LoadHTMLGlob("./index.tmpl")
  r.GET("/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl", "<a href='https://liwenzhuo.com'>博客</a>")
  })
  r.Run(":9090")
}
<!DOCTYPE html>
<html land="zh-CN">
    <head>
        <title>aaaaa</title>
    </head>
    <body>
        <div>{{. | safe}}</div>
    </body>
</html>

当我们渲染的HTML文件中引用了静态文件时,我们只需要按照以下方式在渲染页面前调用gin.Static方法即可。

func main() {
  r := gin.Default()
  r.Static("/static", "./static")
  r.LoadHTMLGlob("templates/**/*")
   // ...
  r.Run(":8080")
}

获取querystring参数

querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

func main() {
  //Default返回一个默认的路由引擎
  r := gin.Default()
  r.GET("/user/search", func(c *gin.Context) {
    username := c.DefaultQuery("username", "小王子")
    //username := c.Query("username")
    address := c.Query("address")
    //输出json结果给调用方
    c.JSON(http.StatusOK, gin.H{
      "message":  "ok",
      "username": username,
      "address":  address,
    })
  })
  r.Run()
}

获取form参数

当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下:

func main() {
  //Default返回一个默认的路由引擎
  r := gin.Default()
  r.POST("/user/search", func(c *gin.Context) {
    // DefaultPostForm取不到值时会返回指定的默认值
    //username := c.DefaultPostForm("username", "小王子")
    username := c.PostForm("username")
    address := c.PostForm("address")
    //输出json结果给调用方
    c.JSON(http.StatusOK, gin.H{
      "message":  "ok",
      "username": username,
      "address":  address,
    })
  })
  r.Run(":8080")
}

获取json参数

当前端请求的数据通过JSON提交时,例如向/json发送一个POST请求,则获取请求参数的方式如下:

r.POST("/json", func(c *gin.Context) {
  // 注意:下面为了举例子方便,暂时忽略了错误处理
  b, _ := c.GetRawData()  // 从c.Request.Body读取请求数据
  // 定义map或结构体
  var m map[string]interface{}
  // 反序列化
  _ = json.Unmarshal(b, &m)
  c.JSON(http.StatusOK, m)
})

获取path参数

请求的参数通过URL路径传递,例如:/user/search/小王子/沙河。 获取请求URL路径中的参数的方式如下。

func main() {
  //Default返回一个默认的路由引擎
  r := gin.Default()
  r.GET("/user/search/:username/:address", func(c *gin.Context) {
    username := c.Param("username")
    address := c.Param("address")
    //输出json结果给调用方
    c.JSON(http.StatusOK, gin.H{
      "message":  "ok",
      "username": username,
      "address":  address,
    })
  })
  r.Run(":8080")
}

参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSONform表单QueryString类型的数据,并把值绑定到指定的结构体对象。

// Binding from JSON
type Login struct {
  User     string `form:"user" json:"user" binding:"required"`
  Password string `form:"password" json:"password" binding:"required"`
}
func main() {
  router := gin.Default()
  // 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
  router.POST("/loginJSON", func(c *gin.Context) {
    var login Login
    if err := c.ShouldBind(&login); err == nil {
      fmt.Printf("login info:%#v\n", login)
      c.JSON(http.StatusOK, gin.H{
        "user":     login.User,
        "password": login.Password,
      })
    } else {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
  })
  // 绑定form表单示例 (user=q1mi&password=123456)
  router.POST("/loginForm", func(c *gin.Context) {
    var login Login
    // ShouldBind()会根据请求的Content-Type自行选择绑定器
    if err := c.ShouldBind(&login); err == nil {
      c.JSON(http.StatusOK, gin.H{
        "user":     login.User,
        "password": login.Password,
      })
    } else {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
  })
  // 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
  router.GET("/loginForm", func(c *gin.Context) {
    var login Login
    // ShouldBind()会根据请求的Content-Type自行选择绑定器
    if err := c.ShouldBind(&login); err == nil {
      c.JSON(http.StatusOK, gin.H{
        "user":     login.User,
        "password": login.Password,
      })
    } else {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
  })
  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

  1. 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  2. 如果是 POST 请求,首先检查 content-type 是否为 JSONXML,然后再使用 Formform-data)。

文件上传

单个文件上传

文件上传前端页面代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>上传文件示例</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="f1">
    <input type="submit" value="上传">
</form>
</body>
</html>

后端gin框架部分代码:

func main() {
  router := gin.Default()
  // 处理multipart forms提交文件时默认的内存限制是32 MiB
  // 可以通过下面的方式修改
  // router.MaxMultipartMemory = 8 << 20  // 8 MiB
  router.POST("/upload", func(c *gin.Context) {
    // 单个文件
    file, err := c.FormFile("f1")
    if err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{
        "message": err.Error(),
      })
      return
    }
    log.Println(file.Filename)
    dst := fmt.Sprintf("C:/tmp/%s", file.Filename)
    // 上传文件到指定的目录
    c.SaveUploadedFile(file, dst)
    c.JSON(http.StatusOK, gin.H{
      "message": fmt.Sprintf("'%s' uploaded!", file.Filename),
    })
  })
  router.Run()
}

多个文件上传

func main() {
  router := gin.Default()
  // 处理multipart forms提交文件时默认的内存限制是32 MiB
  // 可以通过下面的方式修改
  // router.MaxMultipartMemory = 8 << 20  // 8 MiB
  router.POST("/upload", func(c *gin.Context) {
    // Multipart form
    form, _ := c.MultipartForm()
    files := form.File["file"]
    for index, file := range files {
      log.Println(file.Filename)
      dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
      // 上传文件到指定的目录
      c.SaveUploadedFile(file, dst)
    }
    c.JSON(http.StatusOK, gin.H{
      "message": fmt.Sprintf("%d files uploaded!", len(files)),
    })
  })
  router.Run()
}

重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

r.GET("/test", func(c *gin.Context) {
  c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
})

路由重定向

路由重定向,使用HandleContext

r.GET("/test", func(c *gin.Context) {
    // 指定重定向的URL
    c.Request.URL.Path = "/test2"
    r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"hello": "world"})
})

Gin路由

普通路由

r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})

此外,还有一个可以匹配所有请求方法的Any方法如下:

r.Any("/test", func(c *gin.Context) {...})

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

r.NoRoute(func(c *gin.Context) {
    c.HTML(http.StatusNotFound, "views/404.html", nil)
  })

路由组

func main() {
  r := gin.Default()
  userGroup := r.Group("/user")
  {
    userGroup.GET("/index", func(c *gin.Context) {...})
    userGroup.GET("/login", func(c *gin.Context) {...})
    userGroup.POST("/login", func(c *gin.Context) {...})
  }
  shopGroup := r.Group("/shop")
  {
    shopGroup.GET("/index", func(c *gin.Context) {...})
    shopGroup.GET("/cart", func(c *gin.Context) {...})
    shopGroup.POST("/checkout", func(c *gin.Context) {...})
  }
  r.Run()
}

路由组也是支持嵌套的,例如:

shopGroup := r.Group("/shop")
  {
    shopGroup.GET("/index", func(c *gin.Context) {...})
    shopGroup.GET("/cart", func(c *gin.Context) {...})
    shopGroup.POST("/checkout", func(c *gin.Context) {...})
    // 嵌套路由组
    xx := shopGroup.Group("xx")
    xx.GET("/oo", func(c *gin.Context) {...})
  }

路由原理

其基本原理就是构造一个路由地址的前缀树

Gin中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。

// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
  return func(c *gin.Context) {
    start := time.Now()
    c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
    // 调用该请求的剩余处理程序
    c.Next()
    // 不调用该请求的剩余处理程序
    // c.Abort()
    // 计算耗时
    cost := time.Since(start)
    log.Println(cost)
  }
}

定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。

// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
  return func(c *gin.Context) {
    start := time.Now()
    c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
    // 调用该请求的剩余处理程序
    c.Next()
    // 不调用该请求的剩余处理程序
    // c.Abort()
    // 计算耗时
    cost := time.Since(start)
    log.Println(cost)
  }
}

注册中间件

在gin框架中,我们可以为每个路由添加任意数量的中间件。

为全局路由注册

func main() {
  // 新建一个没有任何默认中间件的路由
  r := gin.New()
  // 注册一个全局中间件
  r.Use(StatCost())
  r.GET("/test", func(c *gin.Context) {
    name := c.MustGet("name").(string) // 从上下文取值
    log.Println(name)
    c.JSON(http.StatusOK, gin.H{
      "message": "Hello world!",
    })
  })
  r.Run()
}

为某个路由单独注册

// 给/test2路由单独注册中间件(可注册多个)
  r.GET("/test2", StatCost(), func(c *gin.Context) {
    name := c.MustGet("name").(string) // 从上下文取值
    log.Println(name)
    c.JSON(http.StatusOK, gin.H{
      "message": "Hello world!",
    })
  })

为路由组注册中间件

为路由组注册中间件有以下两种写法。

写法1:

shopGroup := r.Group("/shop", StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

写法2:

shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

中间件注意事项

gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

相关文章
|
Go API 网络架构
Gin框架快速入门1
Gin框架快速入门1
130 0
|
SQL 关系型数据库 MySQL
gin框架学习-Gorm入门指南
Snake Case命名风格,就是各个单词之间用下划线(_)分隔,首字母大写区分一个单词,例如: CreateTime的Snake Case风格命名为create_time
482 0
gin框架学习-Gorm入门指南
|
6月前
|
Go
gin初体验
gin初体验
|
6月前
|
JSON 中间件 数据格式
Gin框架学习笔记(六)——gin中的日志使用
Gin框架学习笔记(六)——gin中的日志使用
285 0
|
6月前
|
JSON 中间件 API
Gin框架笔记(一) Gin框架的安装与Hello World
Gin框架笔记(一) Gin框架的安装与Hello World
230 0
|
6月前
|
JSON 前端开发 Java
|
6月前
|
前端开发 中间件 关系型数据库
|
SQL 开发框架 安全
【Gin】初识Gin框架,模板基本语法
1.Gin介绍 Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API,但性能比 Martini 快 40 倍。如果你需要极好的性能,使用 Gin 吧
620 1
【Gin】初识Gin框架,模板基本语法
|
Go API
gin框架学习-快速安装gin
Gin 是一个用 Go (Golang) 编写的 Web 框架,由于 httprouter,它具有 martini 的 API,性能提高了 40 倍。具有高性能的优点。
168 0
gin框架学习-快速安装gin
|
中间件
Gin框架学习(三)
来这里学习gin框架,目标明确不迷茫。
197 0
Gin框架学习(三)