一、gin简介
Gin 是一个用 Go (Golang) 编写的 HTTP web 框架。 它是一个类似于 martini 但拥有更好性能的 API 框架,由于 httprouter,速度提高了近 40 倍,是最快的 http 路由器和框架。 如果你是性能和高效的追求者,你会爱上 Gin。
二、gin安装和使用
安装
- 下载并安装 gin:
$ go get -u github.com/gin-gonic/gin
2、将gin引入到项目中:
import "github.com/gin-gonic/gin"
3、如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:
import "net/http"
使用
// main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 当客户端以GET方法请求/路径时,会执行后面的匿名函数
r.GET("/", func(c *gin.Context) {
// 返回json格式的数据
c.JSON(http.StatusOK, gin.H{
"message": "hello world",
})
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run()
}
然后,执行 go run main.go 命令来运行代码,并且在浏览器中访问 0.0.0.0:8080/,页面显示:
{"message":"hello world"}
三、RESTful API
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
- GET用来获取资源
- POST用来新建资源
- PUT用来更新资源
- DELETE用来删除资源。
只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。
例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:
请求方法 | URL | 动作 |
---|---|---|
GET | /book | 查询书籍 |
POST | /create_book | 添加书籍 |
POST | /update_book | 更新书籍 |
POST | /delete_book | 删除书籍 |
同样的需求我们按照RESTful API设计如下:
请求方法 | URL | 动作 |
---|---|---|
GET | /book | 查询书籍 |
POST | /book | 添加书籍 |
PUT | /book | 更新书籍 |
DELETE | /book | 删除书籍 |
示例代码:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "GET",
})
})
r.POST("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "POST",
})
})
r.PUT("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "PUT",
})
})
r.DELETE("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "DELETE",
})
})
r.Run()
}
四、HTML渲染
1、模板解析与渲染
使用 LoadHTMLGlob () 或者 LoadHTMLFiles ()
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// r.LoadHTMLFiles("templates/posts/index.tmpl", "templates/users/index.tmpl")
r.LoadHTMLGlob("templates/**/*")
r.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"tittle": "posts",
})
})
r.GET("/users/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"tittle": "users",
})
})
r.Run()
}
templates/posts/index.tmpl
{{ define "posts/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{{ .tittle }}
</body>
</html>
{{ end }}
templates/users/index.tmpl
{{ define "users/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{{ .tittle }}
</body>
</html>
{{ end }}
2、自定义分隔符
r.Delims("{[", "]}")
3、自定义模板函数
package main
import (
"fmt"
"html/template"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func formatAsDate(t time.Time) string {
year, month, day := t.Date()
return fmt.Sprintf("%d-%02d-%02d", year, month, day)
}
func now() string {
year, month, day := time.Now().Date()
return fmt.Sprintf("%d年%02d月%02d日", year, month, day)
}
func main() {
r := gin.Default()
r.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
"now": now,
})
r.LoadHTMLFiles("index.tmpl")
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"now": time.Now(),
})
})
r.Run()
}
4、静态文件处理
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.Static("/statics", "./statics/")
router.LoadHTMLFiles("index.tmpl")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", nil)
})
router.Run()
}
<!-- index.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="//www.greatytc.com/statics/css/main.css">
<title>Document</title>
</head>
<body>
<h2>hello world</h2>
<img src="/statics/pictures/10.jpg" alt="">
<script src="/statics/js/main.js"></script>
</body>
</html>
五、获取参数
Gin框架将处理HTTP请求参数以及如何响应等操作都封装到了gin.Conetxt结构体,并为gin.Context提供了非常多的方法,因此了解gin.Context的结构定义与方法,对使用Gin框架编写Web项目非常重要。
// gin.Context
type Context struct {
Request *http.Request
Writer ResponseWriter
Params Params
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
// contains filtered or unexported fields
}
从上面的gin.Context的结构定义来看,gin.Context封装了http.Request和http.ResponseWriter。
1、获取路径中的参数(path)
path是指请求的url中域名之后从/开始的部分,如访问web地址:https://localhost:8080/user/jack
,/user/jack
部分便是path,可以使用gin.Context中提供的方法获取这部分参数。
获取路径中的参数有两种方法:
- 使用gin.Context的中Param()方法获取path中的参数
- 使用gin.Context中的Params字段获取path中的参数
func (c *Context) Param(key string) string {}
type Params []Param
func (ps Params) ByName(name string) (va string) {}
func (ps Params) Get(name string) (string, bool) {}
示例代码:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
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.GET("/user/:id", func(c *gin.Context) {
id, _ := c.Params.Get("id")
// id := c.Params.ByName("id")
c.JOSN(http.StatusOK, gin.H{
"id": id,
})
})
router.Run()
}
2、获取get请求参数(query)
query是指url请求地址中的问号后面的部,称为查询参数。如https://localhost:8080/index?name=jack&id=100
中name=jack&id=100
就是查询参数。
gin.Context提供了以下几个方法,用于获取Query部分的参数:
- 获取单个参数
func (c *Context) GetQuery(key string) (string, bool) {}
func (c *Context) Query(key string) string {}
func (c *Context) DefaultQuery(key, defaultValue string) string {}
上面三个方法用于获取单个数值,GetQuery比Query多返回一个error类型的参数,实际上Query方法只是封装了GetQuery方法,并忽略GetQuery方法返回的错误而已,而DefaultQuery方法则在没有获取相应参数值的返回一个默认值。
- 获取数组
func (c *Context) GetQueryArray(key string) ([]string, bool) {}
func (c *Context) QueryArray(key string) []string {}
- 获取map
func (c *Context) QueryMap(key string) map[string]string {}
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {}
示例代码:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/index", func(c *gin.Context) {
// name, _ := c.GetQuery("name")
name := c.Query("name")
id := c.DefaultQuery("id", "0000")
c.String(http.StatusOK, "Hello, name:%s, id:%v", name, id)
})
router.GET("/user", func(c *gin.Context) {
// ids, _ := c.GetQueryArray("id")
ids := c.QueryArray("id")
c.JSON(http.StatusOK, gin.H{
"ids": ids,
})
})
router.GET("/article", func(c *gin.Context) {
article := c.QueryMap("articles")
c.JSON(http.StatusOK, article)
})
router.Run()
}
请求:http://localhost:8080/index?name=jack&id=100
响应:Hello, name:jack, id:100
请求:http://localhost:8080/user?id=10&id=20&id=40
响应:{"ids":["10","20","40"]}
请求:http://localhost:8080/article?articles[tittle]=golang
响应:{"tittle":"golang"}
3、获取post请求参数(body)
一般HTTP的Post请求参数都是通过body部分传给服务器端的,尤其是数据量大或安全性要求较高的数据,如登录功能中的账号密码等参数。
gin.Context提供了以下四个方法让我们获取body中的数据,不过要说明的是,下面的四个方法,只能获取Content-type是application/x-www-form-urlencoded或multipart/form-data时body中的数据。
示例代码:
func (c *Context) PostForm(key string) string {}
func (c *Context) PostFormArray(key string) []string {}
func (c *Context) PostFormMap(key string) map[string]string {}
func (c *Context) DefaultPostForm(key, defaultValue string) string {}
func (c *Context) GetPostForm(key string) (string, bool) {}
func (c *Context) GetPostFormArray(key string) ([]string, bool) {}
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {}
func (c *Context) GetRawData() ([]byte, error) {}
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLFiles("index.tmpl")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", nil)
})
router.POST("/index", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
gender := c.DefaultPostForm("gender", "male")
c.JSON(http.StatusOK, gin.H{
"username": username,
"password": password,
"gender": gender,
})
})
router.Run()
}
<!-- index.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/index", method="POST">
<input type="text", name="username"><br>
<input type="password", name="password"><br>
<input type="radio", name="gender" value="male">male
<input type="radio", name="gender" value="female">female <br>
<input type="submit" value="提交">
</form>
</body>
</html>
六、数据绑定
我们直接使用gin.Context提供的方法获取请求中通过path、query、body带上来的参数,但使用前面的那些方法,并不能处理请求中比较复杂的数据结构,比如Content-type为application/json或application/xml时,其所带上的数据会很复杂,因此我们需要使用另外一种方法获取这些数据,这种方式叫数据绑定。
Gin框架将数据绑定的操作都封装在gin/binding这个包中,下面是gin/binding包定义的常量,说明gin/binding包所支持的Content-type格式。
const (
MIMEJSON = "application/json"
MIMEHTML = "text/html"
MIMEXML = "application/xml"
MIMEXML2 = "text/xml"
MIMEPlain = "text/plain"
MIMEPOSTForm = "application/x-www-form-urlencoded"
MIMEMultipartPOSTForm = "multipart/form-data"
MIMEPROTOBUF = "application/x-protobuf"
MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack"
MIMEYAML = "application/x-yaml"
)
gin.binding包也定义处理不同Content-type提交数据的处理结构体,并以变量的形式让其他包可以访问,如下:
var (
JSON = jsonBinding{}
XML = xmlBinding{}
Form = formBinding{}
Query = queryBinding{}
FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{}
)
但实际上,我们并不需要调用gin/binding包的代码来完成数据绑定的功能,因为gin.Context中已经在gin.Context的基础上封装了许多更加快捷的方法供我们使用,gin提供了两套绑定方法:
Must bind
Methods方法:Bind, BindJSON, BindXML, BindQuery, BindYAML
Behavior行为:这些方法底层使用 MustBindWith,如果存在绑定错误,请求将被中止,返回http状态为400的响应给客户端。Should bind
Methods方法:ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
Behavior行为:这些方法底层使用 ShouldBindWith,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。
1、以Bind为前缀的系列方法
- Path
func (c *Context) BindUri(obj interface{}) error {}
- Query
func (c *Context) BindQuery(obj interface{}) error {}
- Body
当我们在HTTP请求中Body设置不同数据格式,需要设置相应头部Content-Type的值,比较常用的为json、xml、yaml,gin.Context提供下面三个方法绑定对应Content-type时body中的数据。
func (c *Context) BindJSON(obj interface{}) error {}
func (c *Context) BindXML(obj interface{}) error {]
func (c *Context) BindYAML(obj interface{}) error {}
除了上面三个方法外,更常用的Bind()方法,Bind()方法会自动根据Content-Type的值选择不同的绑定类型。
func (c *Context) Bind(obj interface{}) error {}
上面几个方法都是获取固定Content-type或自动根据Content-type选择绑定类型,我们也可以使用下面两个方法自行选择绑定类型。
// 第二个参数值是gin.binding中定义好的常量
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {}
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {}
2、以ShouldBind为前缀的系列方法
- Path
func (c *Context) ShouldBindUri(obj interface{}) error {}
- Query
func (c *Context) ShouldBindQuery(obj interface{}) error {}
- Body
func (c *Context) ShouldBind(obj interface{}) error {}
func (c *Context) ShouldBindJSON(obj interface{}) error {}
func (c *Context) ShouldBindXML(obj interface{}) error {}
func (c *Context) ShouldBindYAML(obj interface{}) error {}
func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {}
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {}
示例代码:
// main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// User 结构体
type User struct {
Username string `form:"username" json:"username" uri:"username" binding:"required"`
Passwrod string `form:"password" json:"password" uri:"password" binding:"required"`
Hobbys []string `form:"hobbys" json:"bobbys" uri:"hobbys" binding:"required"`
}
func main() {
router := gin.Default()
router.LoadHTMLFiles("register.tmpl")
// Path
router.GET("/user/:username/:password", func(c *gin.Context) {
var user User
c.ShouldBindUri(&user)
c.JSON(http.StatusOK, user)
})
// Query
router.GET("/index", func(c *gin.Context) {
var user User
c.ShouldBind(&user)
c.JSON(http.StatusOK, user)
})
// Body
router.GET("/register", func(c *gin.Context) {
c.HTML(http.StatusOK, "register.tmpl", nil)
})
router.POST("/register", func(c *gin.Context) {
var user User
c.ShouldBind(&user)
c.JSON(http.StatusOK, user)
})
router.Run()
}
<!-- register.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/register" method="POST", enctype="multipart/form-data">
username: <input type="text" name="username"><br>
<p></p>
password: <input type="password" name="password"><br>
<p></p>
hobbys: <input type="checkbox" name="hobbys" value="football">football
<input type="checkbox" name="hobbys" value="basketball">basketball
<input type="checkbox" name="hobbys" value="volleyball">volleyball<br>
<p></p>
<input type="submit" value="register">
</form>
</body>
</html>
请求:http://localhost:8080/user/jack/123456
响应:{"username":"jack","password":"123456","bobbys":null}
请求:http://localhost:8080/index?username=jack&password=123456
响应:{"username":"jack","password":"123456","bobbys":null}
请求:http://localhost:8080/register 输入username和password、hobbys,提交
响应:{"username":"jack","password":"123456","bobbys":["football","basketball"]}
七、文件上传
1、单文件上传
package main
import (
"fmt"
"math/rand"
"net/http"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLFiles("index.tmpl")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", nil)
})
router.POST("/upload", func(c *gin.Context) {
file, _ := c.FormFile("file")
rand.Seed(time.Now().UnixNano())
fileName := fmt.Sprintf("%d%d%s", time.Now().Unix(), rand.Intn(99999-10000)+10000, filepath.Ext(file.Filename))
dst := "./upload/" + fileName
fmt.Println(dst)
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"message": "uploaded",
"fileName": fileName,
})
})
router.Run()
}
2、多文件上传
package main
import (
"fmt"
"math/rand"
"net/http"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLFiles("index.tmpl")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", nil)
})
router.POST("/upload", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["file"]
fmt.Println(files)
rand.Seed(time.Now().UnixNano())
for _, file := range files {
fileName := fmt.Sprintf("%d%d%s", time.Now().Unix(), rand.Intn(99999-10000)+10000, filepath.Ext(file.Filename))
dst := "./upload/" + fileName
fmt.Println(dst)
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"message": "uploaded",
"file": files,
})
})
router.Run()
}
八、重定向
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// 外部链接重定向
router.GET("/index", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
})
// 内部路由重定向
router.GET("/home", func(c *gin.Context) {
c.Request.URL.Path = "/"
router.HandleContext(c)
})
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello world")
})
router.Run()
}
九、gin路由
1、普通路由
router.GET("/index", func(c *gin.Context) {...})
router.GET("/login", func(c *gin.Context) {...})
router.POST("/login", func(c *gin.Context) {...})
还有一个可以匹配所有请求方法的Any方法如下:
router.Any("/test", func(c *gin.Context) {...})
为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。
router.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusNotFound, "views/404.html", nil)
})
2、路由组
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。
func main() {
router := gin.Default()
userGroup := router.Group("/user")
{
userGroup.GET("/index", func(c *gin.Context) {...})
userGroup.GET("/login", func(c *gin.Context) {...})
userGroup.POST("/login", func(c *gin.Context) {...})
}
shopGroup := router.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
}
router.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中间件的作用有两个:
Web请求到到达我们定义的HTTP请求处理方法之前,拦截请求并进行相应处理(比如:权限验证,数据过滤等),这个可以类比为 前置拦截器 或 前置过滤器 ,
在我们处理完成请求并响应客户端时,拦截响应并进行相应的处理(比如:添加统一响应部头或数据格式等),这可以类型为 后置拦截器 或 后置过滤器 。
1、内置中间件
Gin内置一些中间件,我们可以直接使用,下面是内置中间件列表:
func BasicAuth(accounts Accounts) HandlerFunc {}
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {}
func Bind(val interface{}) HandlerFunc {} //拦截请求参数并进行绑定
func ErrorLogger() HandlerFunc {} //错误日志处理
func ErrorLoggerT(typ ErrorType) HandlerFunc {} //自定义类型的错误日志处理
func Logger() HandlerFunc {} //日志记录
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {}
func LoggerWithFormatter(f LogFormatter) HandlerFunc {}
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {}
func Recovery() HandlerFunc {}
func RecoveryWithWriter(out io.Writer) HandlerFunc {}
func WrapF(f http.HandlerFunc) HandlerFunc {} //将http.HandlerFunc包装成中间件
func WrapH(h http.Handler) HandlerFunc {} //将http.Handler包装成中间件
2、自定义中间件
Gin中的中间件必须是一个gin.HandlerFunc类型。
// gin
type HandlerFunc func(*Context)
(1)定义一个gin.HandleFunc类型的函数作为中间件:
示例代码:
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// StatCost 是一个计算耗时的中间件
func StatCost(c *gin.Context) {
// 传递数据
c.Set("name", "jack")
start := time.Now()
// 调用该请求的剩余处理程序
c.Next()
// 不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
cost := time.Since(start)
fmt.Println(cost)
}
func main() {
router := gin.Default()
// 为/路由注册中间件StatCost
router.GET("/", StatCost, func(c *gin.Context) {
// 获取中间件传递的数据
name := c.MustGet("name").(string)
c.JSON(http.StatusOK, gin.H{
"name": name,
})
})
router.Run()
}
(2)通过自定义方法,返回一个中间件函数,这是Gin框架中更常用的方式:
示例代码:
//定义一个返回中间件的方法
func MyMiddleware(){
//自定义逻辑
//返回中间件
return func(c *gin.Context){
//中间件逻辑
}
}
3、注册中间件
在gin框架中,我们可以为每个路由添加任意数量的中间件。
- 全局使用中间件
直拉使用 gin.Engine 结构体的 Use() 方法便可以在所有请求应用中间件,这样做,中间件便会在全局起作用。
router.Use(gin.Recovery())//在全局使用内置中间件
- 为某个路由单独注册
单个请求路由,也可以应用中间件,如下:
router := gin.New()
router.GET("/test",gin.Recovery(),gin.Logger(),func(c *gin.Context){
c.JSON(200,"test")
})
- 为路由组注册中间件
根据业务不同划分不同 路由分组(RouterGroup ),不同的路由分组再应用不同的中间件,这样就达到了不同的请求由不同的中间件进行拦截处理。
为路由组注册中间件有以下两种写法。
routerGroup := router.Group("/", MyMiddleware)
{
routerGroup.GET("/user", func(c *gin.Context){})
routerGroup.POST("/user", func(c *gin.Context){})
...
}
routerGroup := router.Group("/")
routerGroup.Use(MyMiddleware)
{
routerGroup.GET("/user", func(c *gin.Context){})
routerGroup.POST("/user", func(c *gin.Context){})
...
}
4、中间件使用
(1)gin默认中间件
gin.Default()默认使用了Logger和Recovery中间件,其中:
Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。
(2)数据传递
当我们在中间件拦截并预先处理好数据之后,要如何将数据传递我们定义的处理请求的HTTP方法呢?可以使用 gin.Context 中的 Set() 方法,其定义如下, Set() 通过一个key来存储作何类型的数据,方便下一层处理方法获取。
func (c *Context) Set(key string, value interface{})
当我们在中间件中通过Set方法设置一些数值,在下一层中间件或HTTP请求处理方法中,可以使用下面列出的方法通过key获取对应数据。
其中,gin.Context的Get方法返回 interface{} ,通过返回exists可以判断key是否存在。
func (c *Context) Get(key string) (value interface{}, exists bool)
当我们确定通过Set方法设置对应数据类型的值时,可以使用下面方法获取应数据类型的值。
func (c *Context) GetBool(key string) (b bool)
func (c *Context) GetDuration(key string) (d time.Duration)
func (c *Context) GetFloat64(key string) (f64 float64)
func (c *Context) GetInt(key string) (i int)
func (c *Context) GetInt64(key string) (i64 int64)
func (c *Context) GetString(key string) (s string)
func (c *Context) GetStringMap(key string) (sm map[string]interface{})
func (c *Context) GetStringMapString(key string) (sms map[string]string)
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
func (c *Context) GetStringSlice(key string) (ss []string)
func (c *Context) GetTime(key string) (t time.Time)
(3)拦截请求与后置拦截
- 拦截请求
中间件的最大作用就是拦截过滤请求,比如我们有些请求需要用户登录或者需要特定权限才能访问,这时候便可以中间件中做过滤拦截,当用户请求不合法时,可以使用下面列出的 gin.Context 的几个方法中断用户请求:
下面三个方法中断请求后,直接返回200,但响应的body中不会有数据。
func (c *Context) Abort()
func (c *Context) AbortWithError(code int, err error) *Error
func (c *Context) AbortWithStatus(code int)
使用AbortWithStatusJSON()方法,中断用户请求后,则可以返回 json格式的数据.
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{})
- 后置拦截
前面我们讲的都是到达我们定义的HTTP处理方法前进行拦截,其实,如果在中间件中调用 gin.Context 的 Next() 方法,则可以请求到达并完成业务处理后,再经过中间件后置拦截处理, Next() 方法定义如下:
func (c *Context) Next()
在中间件调用 Next() 方法, Next() 方法之前的代码会在到达请求方法前执行, Next() 方法之后的代码则在请求方法处理后执行:
func MyMiddleware(c *gin.Context){
//请求前
c.Next()
//请求后
}
(4)gin中间件中使用goroutine
当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。