05 获取参数
1、获取querystring参数
querystring 指的是URL中的 ? 后面携带的参数,例如: /user/search?username=小王子&address=天津 。获取请求querystring参数的方法如下:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// quertsting
//GET请求 URL ? 后面是querystring的参数
//key-value格式,多个key-value 用 & 连接
// eq : http://127.0.0.1:9090/user/search?query=小王子&age=18
// http://127.0.0.1:9090/user/search?query= 则默认返回为空
r.GET("/user/search", func(c *gin.Context){
//获取浏览器那边发请求携带的 query string 参数
name := c.Query("query") //通过Query获取请求中携带的querystring参数
age := c.Query("age")
//name := c.DefaultQuery("query","somebody")//取不到就用指定的默认值
/*name, ok := c.GetQuery("query")//渠道返回(值,true),取不到第二个参数就返回("", false)
//if !ok{
// //取不到
// name = "somebody"
}*/
//输出json结果给调用方
data := gin.H{
"name": name,
"age": age,
}
c.JSON(http.StatusOK, data)
})
r.Run(":9090")
}
2、获取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")
}
示例:
main.go
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 获取 form 表单提交与获取参数
func main(){
r := gin.Default()
r.LoadHTMLFiles("./login.html","./index.html")
r.GET("/login",func(c *gin.Context){
c.HTML(http.StatusOK, "login.html", nil)
})
r.Run(":9090")
}
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="/login" method="post" novalidate autocomplete="off">
<div>
<label for="username">username:</label>
<input type="text" name="username" id="username">
</div>
<div>
<label for="password">password:</label>
<input type="password" name="password" id="password">
</div>
<div>
<input type="submit" value="登录">
</div>
</form>
</body>
</html>
在浏览器用户的一次请求(127.0.0.1:9090/login),
对应后端代码的一次响应
(r.GET(“/login”,func(c *gin.Context){
c.HTML(http.StatusOK, “login.html”, nil)
}))
main.go
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 获取 form 表单提交与获取参数
func main(){
r := gin.Default()
r.LoadHTMLFiles("./login.html","./index.html")
//一个请求一个响应
r.GET("/login",func(c *gin.Context){
c.HTML(http.StatusOK, "login.html", nil)
})
// /login post请求
//一个请求一个响应
r.POST("/login", func( c *gin.Context){
// 第一种获取form表单的方法
//username := c.PostForm("username")
//password := c.PostForm("password") 取到就返回值,取不到就返回一个空字符串
// 第二种
//username := c.DefaultPostForm("username","somebody")
//password := c.DefaultPostForm("password","******")
// 第三种
username, ok := c.GetPostForm("username")
if !ok{
username = "somebody"
}
password, ok := c.GetPostForm("password")
if !ok{
username = "******"
}
c.HTML(http.StatusOK, "index.html", gin.H{
"Name": username,
"Password":password,
})
})
r.Run(":9090")
}
index.html 模版渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h1>hello, {{ .Name }}!</h1>
<p>你的密码是: {{ .Password }}</p>
</body>
</html>
在浏览器用户的一次请求(点击登录请求),
对应后端代码的一次响应 (返回一个index.html页面)
r.POST("/login", func( c *gin.Context){
username := c.PostForm("username")
password := c.PostForm("password")
c.HTML(http.StatusOK, "index.html", gin.H{
"Name": username,
"Password":password,
})
})
3、获取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)
})
更便利的获取请求参数的方式,参见下面的 参数绑定 小节。
4、获取path参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 获取请求中的path (URL) 参数 返回的都是字符串
// 注意URL的匹配不要冲突
func main(){
r := gin.Default()
r.GET("/user/:name/:age", func(c *gin.Context){
// 获取路径参数
name := c.Param("name")
age := c.Param("age") //返回的是string类型
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.GET("/blog/:year/:month", func(c *gin.Context) {
year := c.Param("year")
month := c.Param("month")
c.JSON(http.StatusOK, gin.H{
"year": year,
"month":month,
})
})
r.Run(":9090")
}
5、参数绑定
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求Content-Type识别请求数据类型并利用反射机制自动提取请求中 QuerString 、 form表单、 JSON、 XML 等参数到结构体中。下面的示例代码演示了 .ShouldBind() 强大的功能,它能够基于请求自动提取 JSON、form表单和QuerSting 类型的数据,并把值绑定到指定的结构体对象。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<form action="/form" method="post">
用户名:
<input type="text" name="username">
密码:
<input type="password" name="password">
<input type="submit" name="提交">
</form>
</body>
</html>
main.go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
type UserInfo struct{
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
}
func main() {
r := gin.Default()
r.LoadHTMLFiles("./index.html")
r.GET("/user", func(c *gin.Context){
//username := c.Query("username")
//password := c.Query("password")
//u := UserInfo{
// username: username,
// password: password,
//}
var u UserInfo //声明一个UserInfo类型的变量u
err := c.ShouldBind(&u)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}else{
fmt.Printf("%#v\n", u)
c.JSON(http.StatusOK, gin.H{
"status":"OK",
})
}
})
r.GET("/index", func (c *gin.Context){
c.HTML(http.StatusOK, "index.html", nil)
})
r.POST("/form", func(c *gin.Context) {
var u UserInfo
err := c.ShouldBind(&u)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
}else{
fmt.Printf("%#v\n",u)
c.JSON(http.StatusOK, gin.H{
"status": "ok",
})
}
})
r.Run(":9090")
}
ShouldBind 会按照下面的顺序解析请求中的数据完成绑定:
- 1、如果是GET请求,只使用Form绑定引擎(query)
- 2、如果是POST请求,首先检查 content-type 是否为 JSON或 XML,然后再使用Form( form-data)
06 文件上传
单个文件上传
文件上传前端页面代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f1">
<input type="submit" name="提交">
</form>
</body>
</html>
如果服务器端上传的文件过大内存不足以放下,就先将文件放入一个临时文件夹中,一点一点的去读,可以设置内存的最大限制,来控制文件上传的速度。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"path"
)
func main() {
r := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
r.LoadHTMLFiles("./index.html")
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
r.POST("/upload", func(c *gin.Context) {
// 1、从请求中读取文件
filereader, err := c.FormFile("f1") // 从请求中获取携带的参数是一样的
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
// 2、将读取到的文件保存在服务端的本地
//dst := fmt.Sprintf("./%s", filereader.Filename)
dst := path.Join("./", filereader.Filename)
c.SaveUploadedFile(filereader, dst)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
}
c.JSON(http.StatusOK, gin.H{
"status": "ok",
})
}
})
r.Run(":9090")
}
多个文件上传
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"path"
)
func main() {
router := gin.Default()
router.LoadHTMLFiles("./index.html")
router.GET("/index", func(c *gin.Context){
c.HTML(http.StatusOK, "index.html", nil)
})
// 处理multipart forms 提交文件时默认的内存限制是32MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory 8 << 20 //8 MiB
router.POST("/uploads", func(c *gin.Context){
// Multipart form
form, err := c.MultipartForm()
if err != nil {
log.Fatal(err)
}
// 通过字段名映射
files := form.File["file"]
// for range 遍历文件
for index, file := range files{
log.Println(file.Filename)
dst := path.Join("/Users/tianyi/Desktop/tmp", file.Filename, string(index))
// 上传文件到指定的目录
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("%v files uploaded!", len(files)),
})
})
router.Run(":9090")
}
07 重定向
HTTP重定向
r.GET("/test", func(c *gin.Context){
c.Redirect(http.StatusMovePermanently, "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{
"hellp" : "world!"
})
})
08 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)
})
路由组
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对 {} 包裹同组的路由,这只是为了看着清晰。用不用{} 包裹功能上没什么区别
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 访问/index的GET请求会走这一条处理逻辑
// 获取信息的路由
//r.HEAD()
r.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "GET",
})
})
// 创建信息的路由
r.POST("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "POST",
})
})
// 删除信息的路由
r.DELETE("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "DELETE",
})
})
// 更新部分信息的路由
r.PUT("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "PUT",
})
})
r.Any("/user", func(c *gin.Context) {
switch c.Request.Method{
case "GET":
c.JSON(http.StatusOK, gin.H{"method": "GET",})
case http.MethodPost:
c.JSON(http.StatusOK, gin.H{"method": "POST",})
}
c.JSON(http.StatusOK, gin.H{
"method": "Any",
})
})
//NoRoute
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"msg":"liwenzhou.com"})
})
//路由组
//把公用的前缀提取出来,创建一个路由组
// 视频的首页和详情页
videoGroup := r.Group("/video")
{
videoGroup.GET("index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/video/index",
})
})
videoGroup.GET("xx", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/video/xx",
})
})
videoGroup.GET("oo", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/video/oo",
})
})
}
// 商城的首页和详情页
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/shop/index",
})
})
shopGroup.GET("xx", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/shop/xx",
})
})
shopGroup.GET("oo", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/shop/oo",
})
})
//嵌套
q := shopGroup.Group("/oo")
{
q.GET("/xx", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "/shop/oo/xx",
})
})
}
}
r.Run(":9090")
}
Summry:
Any的用处:可以使用多种方法,注意要优先使用go语言定义好的常量
NoRoute的用处:用来引导用户进入到正确的路由
路由组的用处:用来区分不同的业务线或API版本
路由原理(进阶版会讲)
Gin框架中的路由使用的是httprouter这个库
其基本原理就是构造一个路由地址的前缀树
09 Gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
定义中间件
Gin中的中间件必须是一个 gin.HandlerFunc 类型。
记录接口耗时的中间件
例如我们像下面的代码一样定义一个统计请求耗时的中间件。
针对单个method使用中间件
demo01
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"time"
)
// handlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// StatCost 是一个统计耗时请求耗时的中间件
func m1(c *gin.Context) {
fmt.Println("m1..in")
start := time.Now()
c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
//调用该请求的剩余处理程序
c.Next()
//不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
cost := time.Since(start)
log.Printf("cost time = %v\n", cost)
fmt.Println("m1..out")
}
func main() {
r := gin.Default()
//GET(relativePath string, handlers ...HandlerFunc) IRoutes {
r.GET("/index", m1, indexHandler)
r.Run(":9090")
}
针对全局使用中间件
demo02
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"time"
)
// handlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// handlerFunc
func shopHandler(c *gin.Context) {
fmt.Println("shop")
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
}
// handlerFunc
func userHandler(c *gin.Context) {
fmt.Println("user")
c.JSON(http.StatusOK, gin.H{
"msg": "user",
})
}
// StatCost 是一个统计耗时请求耗时的中间件
func m1(c *gin.Context) {
fmt.Println("m1..in")
start := time.Now()
c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
//调用该请求的剩余处理程序
c.Next()
//不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
cost := time.Since(start)
log.Printf("cost time = %v\n", cost)
fmt.Println("m1..out")
}
func main() {
r := gin.Default()
// 全局使用中间件
r.Use(m1)
//GET(relativePath string, handlers ...HandlerFunc) IRoutes {
r.GET("/index", indexHandler)
r.GET("/shop", shopHandler)
r.GET("/user", userHandler)
r.Run(":9090")
}
r.User(中间件1, 中间件2)
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"time"
)
// handlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// handlerFunc
func shopHandler(c *gin.Context) {
fmt.Println("shop")
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
}
// handlerFunc
func userHandler(c *gin.Context) {
fmt.Println("user")
c.JSON(http.StatusOK, gin.H{
"msg": "user",
})
}
// m1 是一个统计耗时请求耗时的中间件
func m1(c *gin.Context) {
fmt.Println("m1..in")
start := time.Now()
c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
//调用该请求的剩余处理程序
c.Next()
//不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
cost := time.Since(start)
log.Printf("cost time = %v\n", cost)
fmt.Println("m1..out")
}
func m2(c *gin.Context) {
fmt.Println("m2..in")
//调用该请求的剩余处理程序
c.Next()
fmt.Println("m2..out")
}
func main() {
r := gin.Default()
// 全局使用中间件
r.Use(m1, m2)
//GET(relativePath string, handlers ...HandlerFunc) IRoutes {
r.GET("/index", indexHandler)
r.GET("/shop", shopHandler)
r.GET("/user", userHandler)
r.Run(":9090")
}
c.Abort()
在中间件中使用return结束当前中间件,return之后的代码不会被执行
通常将中间件写为一个闭包
doCheck 作为开关控制是否检查
记录响应体的中间件
我们有时可能会想记录下某些情况下返回给客户端的响应数据,这个时候就可以编写一个中间件来搞定。
type bodyLogWriter struct {
gin.ResponseWriter // 嵌入gin框架ResponseWriter
body *bytes.Buffer // 我们记录用的response
}
// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b) // 我们记录一份
return w.ResponseWriter.Write(b) // 真正写入响应
}
// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {
blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}
c.Writer = blw // 使用我们自定义的类型替换默认的
c.Next() // 执行业务逻辑
fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}
跨域中间件cors
推荐使用社区的https://github.com/gin-contrib/cors 库,一行代码解决前后端分离架构下的跨域问题。
在gin框架中,我们可以为每个路由添加任意数量的中间件。
这个库支持各种常用的配置项,具体使用方法如下。
package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// CORS for https://foo.com and https://github.com origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://foo.com"}, // 允许跨域发来请求的网站
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // 允许的请求方法
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool { // 自定义过滤源站的方法
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}))
router.Run()
}
当然你可以简单的像下面的示例代码那样使用默认配置,允许所有的跨域请求。
func main() {
router := gin.Default()
// same as
// config := cors.DefaultConfig()
// config.AllowAllOrigins = true
// router.Use(cors.New(config))
router.Use(cors.Default())
router.Run()
}
注意: 该中间件需要注册在业务处理函数前面。
这个库支持各种常用的配置项,具体使用方法如下。
package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// CORS for https://foo.com and https://github.com origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://foo.com"}, // 允许跨域发来请求的网站
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // 允许的请求方法
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool { // 自定义过滤源站的方法
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}))
router.Run()
}
当然你可以简单的像下面的示例代码那样使用默认配置,允许所有的跨域请求。
func main() {
router := gin.Default()
// same as
// config := cors.DefaultConfig()
// config.AllowAllOrigins = true
// router.Use(cors.New(config))
router.Use(cors.Default())
router.Run()
}
注册中间件
在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) {...})
...
}
从中间件设置值,并在上下文取值(跨中间件取值)
package main
import (
"bytes"
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"time"
)
// handlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("index")
name, ok := c.Get("name")// 从上下文中取值 (跨中间件存取值)
if !ok{
name = "匿名用户"
}
c.JSON(http.StatusOK, gin.H{
"msg": name,
})
}
// handlerFunc
func shopHandler(c *gin.Context) {
fmt.Println("shop")
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
}
// handlerFunc
func userHandler(c *gin.Context) {
fmt.Println("user")
c.JSON(http.StatusOK, gin.H{
"msg": "user",
})
}
// m1 是一个统计耗时请求耗时的中间件
func m1(c *gin.Context) {
fmt.Println("m1..in")
start := time.Now()
//c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
//调用该请求的剩余处理程序
c.Next()
//不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
name, ok := c.Get("name")// 从上下文中取值 (跨中间件存取值)
if !ok{
name = "匿名用户"
}
c.JSON(http.StatusOK, gin.H{
"msg": name,
})
cost := time.Since(start)
log.Printf("cost time = %v\n", cost)
fmt.Println("m1..out")
}
func m2(c *gin.Context) {
fmt.Println("m2..in")
c.Set("name","qimi")// 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
c.Next()
//阻止调用该请求的剩余处理程序
//c.Abort()
fmt.Println("m2..out")
}
func authMiddleware(doCheck bool)gin.HandlerFunc{
//连接数据库
// 或是一些其他准备工作
return func(c *gin.Context){
fmt.Println("authMiddleware..in")
if doCheck{
//存放具体的逻辑
// 是否登录的判断
//if 登录
c.Next()
//else
//c.Abort()
}else{
c.Next()
}
fmt.Println("authMiddleware..out")
}
}
// 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)
}
}
type bodyLogWriter struct{
gin.ResponseWriter // 嵌入gin框架ResponseWriter
body *bytes.Buffer //我们记录用的response
}
// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error){
w.body.Write(b) //我们记录一份
return w.ResponseWriter.Write(b)//真正写入响应
}
//ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
func ginBodyLogMiddleware(user bool) gin.HandlerFunc{
return func(c *gin.Context){
blw := &bodyLogWriter{
body: bytes.NewBuffer([]byte{}),
ResponseWriter: c.Writer,
}
c.Writer = blw // 使用我们自定义的类型替换默认的
c.Next() //业务逻辑
fmt.Println("Response body: " + blw.body.String()) //事后按需记录返回的响应
}
}
func main() {
r := gin.Default()
// 全局使用中间件
r.Use(m1, m2, authMiddleware(true), ginBodyLogMiddleware(true))
//GET(relativePath string, handlers ...HandlerFunc) IRoutes {
r.GET("/index", indexHandler)
r.GET("/shop", shopHandler)
r.GET("/user", userHandler)
//路由组注册中间件方法1:
//shopGroup := r.Group("/xx",StatCost())
//{
// shopGroup.GET("/shop", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{
// "msg": "shopGroup",
// })
// })
//}
路由组注册中间件方法2:
//indexGroup := r.Group("/xx2")
//indexGroup.Use(StatCost())
//{
// indexGroup.GET("/index", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{
// "msg": "indexGroup",
// })
// })
//}
r.Run(":9090")
}
中间件注意事项
gin默认中间件
gin.Default()默认使用了Logger和Recovery中间件,其中:
Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用**gin.New()**新建一个没有任何默认中间件的路由。
gin中间件中使用goroutine
当在中间件或handler中启动新的goroutine时,不能使用原始的上下文**(c *gin.Context),必须使用其只读副本(c.Copy())**。
10 运行多个服务
我们可以在多个端口启动服务,例如:
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
var (
g errgroup.Group
)
func router01() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
})
return e
}
func router02() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
})
return e
}
func main() {
server01 := &http.Server{
Addr: ":8080",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
server02 := &http.Server{
Addr: ":8081",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}