gin 中间件
全局中间件
- 所有请求都需要经过的中间件
package main import ( "fmt" "github.com/gin-gonic/gin" ) // 先定义一个中间件 func MiddleWare() gin.HandlerFunc { return func(c *gin.Context) { fmt.Println("中间件开始执行了") // 设置变量到Context的key中,可以通过Get()取 c.Set("request", "中间件") status := c.Writer.Status() fmt.Println("中间件执行完毕", status) } } func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // 注册中间件 r.Use(MiddleWare()) // {}为代码规范 { r.GET("/test", func(c *gin.Context) { // 取值 req, _ := c.Get("request") fmt.Println("request:", req) // 页面接收 c.JSON(200, gin.H{"request": req}) }) } r.Run(":8000") }
结果:
Next() 函数
源码:
// Next should be used only inside middleware. // Next 应该只在中间件中被使用 // It executes the pending handlers in the chain inside the calling handler. // 挂起现在正在执行的handlers // See example in GitHub. func (c *Context) Next() { c.index++ for s := int8(len(c.handlers)); c.index < s; c.index++ { c.handlers[c.index](c) } }
测试:
// 中间件中Next的函数的测试 func main() { r := gin.New() mid1 := func(c *gin.Context) { start := time.Now() fmt.Println("middleware1 start") // 注释 or 不注释,查看输入结果 //c.Next() fmt.Println(time.Since(start)) fmt.Println("middleware1 ending") } mid2 := func(c *gin.Context) { fmt.Println("middleware2 start") c.Next() fmt.Println("middleware2 ending") } r.Use(mid1) r.Use(mid2) r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, "hi") }) r.Run(":8000") }
结果:
# mid1中的Next()执行 middleware1 start middleware2 start middleware2 ending 758.6µs middleware1 ending # mid1中的Next()注释上 middleware1 start # 不加Next则不会出现响应时间 0s middleware1 ending middleware2 start middleware2 ending
可以看出,再加Next函数后mid1会先挂起,等其余的中间件(mid2)执行完再继续执行Next函数后的语句,并且这个过程是一个压栈的过程,也就是说先执行的Next后的语句后被执行。
Next之前的操作一般用来做验证处理,访问是否允许之类的。
Next之后的操作一般是用来做总结处理,比如格式化输出、响应结束时间,响应时长计算之类的。
局部中间件
- 在特殊的路由中指定中间件
示例:
package main import ( "fmt" "github.com/gin-gonic/gin" ) func MiddleWare() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() fmt.Println("中间件开始执行了") // 设置变量到Context的key中,可以通过Get()取 c.Set("request", "中间件") // 执行函数 c.Next() // 中间件执行完后续的一些事情 status := c.Writer.Status() fmt.Println("中间件执行完毕", status) t2 := time.Since(t) fmt.Println("time:", t2) } } func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() //局部中间键使用 r.GET("/ce", MiddleWare(), func(c *gin.Context) { // 取值 req, _ := c.Get("request") fmt.Println("request:", req) // 页面接收 c.JSON(200, gin.H{"request": req}) }) r.Run(":8000") }
结果:
只有在访问"/ce"会执行中间件
会话处理
Cookie
Cookie介绍
- HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出
- Cookie就是解决HTTP协议无状态的方案之一,中文是小甜饼的意思
- Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求
- Cookie由服务器创建,并发送给浏览器,最终由浏览器保存
cookie用途
- 测试服务端发送cookie给客户端,客户端请求时携带cookie
示例:
package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // 服务端要给客户端cookie r.GET("cookie", func(c *gin.Context) { // 获取客户端是否携带cookie cookie, err := c.Cookie("key_cookie") if err != nil { cookie = "NotSet" // 给客户端设置cookie // maxAge int, 单位为秒 // path,cookie所在目录 // domain string,域名 // secure 是否智能通过https访问 // httpOnly bool 是否允许别人通过js获取自己的cookie c.SetCookie("key_cookie", "value_cookie", 60, "/", "localhost", false, true) } fmt.Printf("cookie的值是: %s\n", cookie) }) r.Run(":8000") }
// 其中 SetCookie方法的参数的含义为 // maxAge int, 单位为秒 // path,cookie所在目录 // domain string,域名 // secure 是否智能通过https访问 // httpOnly bool 是否允许别人通过js获取自己的cookie SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
结果:
cookie的值是: NotSet
这样便有了cookie的值
Cookie缺点
- 不安全,明文(就像上面的示例的结果)
- 增加带宽消耗
- 可以被禁用
- cookie数量有上限(每个浏览器不同)
Sessions
- 简单的API:将其用作设置签名(以及可选的加密)cookie的简便方法。
- 内置的后端可将session存储在cookie或文件系统中。
- Flash消息:一直持续读取的session值。
- 切换session持久性(又称“记住我”)和设置其他属性的便捷方法。
- 旋转身份验证和加密密钥的机制。
- 每个请求有多个session,即使使用不同的后端也是如此。
- 自定义session后端的接口和基础结构:可以使用通用API检索并批量保存来自不同商店的session。
示例:
package main import ( "fmt" "net/http" "github.com/gorilla/sessions" ) // 初始化一个cookie存储对象 // something-very-secret应该是一个你自己的密匙,只要不被别人知道就行 var store = sessions.NewCookieStore([]byte("something-very-secret")) func main() { http.HandleFunc("/save", SaveSession) http.HandleFunc("/get", GetSession) http.HandleFunc("/del", DelSession) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println("HTTP server failed,err:", err) return } } func SaveSession(w http.ResponseWriter, r *http.Request) { // Get a session. We're ignoring the error resulted from decoding an // existing session: Get() always returns a session, even if empty. // 获取一个session对象,session-name是session的名字 session, err := store.Get(r, "session-name") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 在session中存储值 session.Values["foo"] = "bar" session.Values[42] = 43 // 保存更改 session.Save(r, w) } func GetSession(w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, "session-name") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } foo := session.Values["foo"] fmt.Println(foo) } func DelSession(w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, "session-name") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 将session的时间设为小于零的数就是删除 session.Options.MaxAge = -1 // 保存更改 session.Save(r, w) }
结果:
# 先save后get,可以取到值 bar # 然后del再get,就取不到值了 <nil>
参考: