1. Cookie介绍
- HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出
- Cookie就是解决HTTP协议无状态的方案之一,中文是小甜饼的意思
- Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求
- Cookie由服务器创建,并发送给浏览器,最终由浏览器保存
1.1 Cookie的用途
- 测试服务端发送cookie给客户端,客户端请求时携带cookie
1.2 Cookie的使用
- 测试服务端发送cookie给客户端,客户端请求时携带cookie
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 局部中间件
r.GET("/demo", func(c *gin.Context) {
// 获得内容
res, err := c.Cookie("demo")
// 如果没有获得cookie,err则会接收到除nil以外的内容;
if err != nil {
res = "noSet"
// 当不存在cookie,则存储
c.SetCookie("demo", "弄他", 3600, "", "", false, false)
}
c.JSON(200, gin.H{"cookie:": res})
})
r.Run(":8080")
}
SetCookie() 函数内部:
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
if path == "" {
path = "/"
}
http.SetCookie(c.Writer, &http.Cookie{
Name: name,
Value: url.QueryEscape(value),
MaxAge: maxAge,
Path: path,
Domain: domain,
SameSite: c.sameSite,
Secure: secure,
HttpOnly: httpOnly,
})
}
- name:cookie的键
- value:cookie的值
- maxAge:存储cookie最大的时间(seconds)
- path:cookie详细路径
- domain:域名
- secure:是否支持能通过https访问
- httpOnly:是否允许别人通过js获取自己的cookie
函数内部调用的是
http包下的SetCookie()
方法并且,path为空情况下,详细路径默认是根目录
func SetCookie(w ResponseWriter, cookie *Cookie) {
if v := cookie.String(); v != "" {
w.Header().Add("Set-Cookie", v)
}
}
可以看到内部,是在请求头中添加了Set-Cookie
属性,为它进行了设置
1.3 小练习
package main
import (
"github.com/gin-gonic/gin"
"math/rand"
"net/http"
"strconv"
"time"
)
func handler() gin.HandlerFunc {
return func(c *gin.Context) {
_, err := c.Cookie("token")
if err != nil {
return
}
}
}
func main() {
r := gin.Default()
// 设置cookie
r.GET("/login", func(c *gin.Context) {
rand.NewSource(time.Now().UnixNano())
cook := strconv.Itoa(rand.Int())
c.SetCookie("token", cook, 86400, "/", "localhost", false, true)
c.JSON(200, gin.H{"msg": "success"})
})
// 检查登录
r.GET("/home", handler(), func(c *gin.Context) {
cookie, err := c.Cookie("token")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": http.StatusBadRequest})
return
}
c.JSON(200, gin.H{"msg": cookie})
})
r.Run(":8080")
}
1.4 Cookie的缺点
- 不安全,明文
- 增加带宽消耗
- 可以被禁用
cookie有上限
1. Sessions
gorilla/sessions为自定义session后端提供cookie和文件系统session以及基础结构。
主要功能是:
- 简单的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)
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)
}
3. 结构体验证
gin自带了数据验证的功能,可以不用解析数据,减少if else
,在代码观赏性上会好看很多
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"time"
)
type Person struct {
//不能为空并且大于10
Age int `form:"age" binding:"required,gt=10"`
Name string `form:"name" binding:"required"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
func main() {
r := gin.Default()
// // 检查登录
r.GET("/home", func(c *gin.Context) {
var person Person
if err := c.ShouldBind(&person); err != nil {
c.String(500, fmt.Sprint(err))
return
}
c.String(200, fmt.Sprintf("%#v", person))
})
r.Run(":8080")
}
-
Age int `form:“age” binding:“required,gt=10”
binding标签属性就表示验证内容
- required——必填
- gt——值必须大于xx
存在多个验证器时,应当以 “ , ” 隔开
验证器 | 说明 | 示例 |
---|---|---|
- | 忽略字段 | binding:“-” |
required | 必填字段 | binding:“required” |
min | 最小长度 | binding:“min=10” |
max | 最大长度 | binding:“max=10” |
| | 或 | binding:“rgb” |
structonly | 如果有嵌套,可以决定只验证结构体上的 | binding:“structonly” |
omitempty | 省略空,如果为空,则不会继续验证该字段上其他的规则,只有不为空才会继续验证其他的 | |
len | 长度 | binding:“len=10” |
eq | 等于 | binding:“eq=10” |
ne | 不等于 | binding:“ne=10” |
gt | 大于 | binding:“gt=10” |
lt | 小于 | binding:“lt=10” |
lte | 小于等于 | binding:“lte=10” |
eqfield | 等于其他字段的值 | Password string binding:"eqfield=ConfirmPassword" |
nefield | 不等于其他字段的值 | |
eqcsfield | 类似eqfield,它会验证相对于顶层结构提供的字段 | binding:"eqcsfield = InnerStructField.Field |
url | 字符串值包含有效的网址,必须包含http://等 | |
uri | 字符串值包含有效的uri. 它将接受golang请求uri接受的任何uri | |
alpha | 字符串值仅包含字母字符 | |
alphanum | 字符串值仅包含字母数字字符 | |
numeric | 字符串值包含基本数字值。基本不包括指数等… | |
email | 字符串值包含有效的电子邮件 | |
contains | 字符串值包含子字符串值 | contains=@ |
excludes | 字符串值不包含子字符串值 | excludes = @ |
containsany | 包含所有 | containsany =!@#? |
excluderune | 字符串值不包含提供的符号 | excluderune = @ |
注意:gt、gte、lt、lte等都可以用于时间的比较,后面不需要跟值,直接binding:“gt”,表示大于当前utc时间
4. 自定义验证
package main
import (
"net/http"
"reflect"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v8"
)
/*
对绑定解析到结构体上的参数,自定义验证功能
比如我们要对 name 字段做校验,要不能为空,并且不等于 admin ,类似这种需求,就无法 binding 现成的方法
需要我们自己验证方法才能实现 官网示例(https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Custom_Functions)
这里需要下载引入下 gopkg.in/go-playground/validator.v8
*/
type Person struct {
Age int `form:"age" binding:"required,gt=10"`
// 2、在参数 binding 上使用自定义的校验方法函数注册时候的名称
Name string `form:"name" binding:"NotNullAndAdmin"`
Address string `form:"address" binding:"required"`
}
// 1、自定义的校验方法
func nameNotNullAndAdmin(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
if value, ok := field.Interface().(string); ok {
// 字段不能为空,并且不等于 admin
return value != "" && !("5lmh" == value)
}
return true
}
func main() {
r := gin.Default()
// 3、将我们自定义的校验方法注册到 validator中
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
v.RegisterValidation("NotNullAndAdmin", nameNotNullAndAdmin)
}
/*
curl -X GET "http://127.0.0.1:8080/testing?name=&age=12&address=beijing"
curl -X GET "http://127.0.0.1:8080/testing?name=lmh&age=12&address=beijing"
curl -X GET "http://127.0.0.1:8080/testing?name=adz&age=12&address=beijing"
*/
r.GET("/5lmh", func(c *gin.Context) {
var person Person
if e := c.ShouldBind(&person); e == nil {
c.String(http.StatusOK, "%v", person)
} else {
c.String(http.StatusOK, "person bind err:%v", e.Error())
}
})
r.Run()
}
5. ❤️GoGin框架——前文链接
Gin框架学习参考网站:gin框架·Go语言中文文档
- (Go Gin)基于Go的WEB开发框架,GO Gin是什么?怎么启动?本文给你答案
- (Go Gin)Gin学习笔记(二):路由配置、基本路由、表单参数、上传单个文件、上传多个文件、浅扒路由原理
- (Go Gin)Gin学习笔记(三):数据解析和绑定,结构体分析,包括JSON解析、form解析、URL解析,区分绑定的Bind方法
- (Go Gin)Gin学习笔记(四):数据渲染、返回JSON、浅.JSON()源码、中间件、Next()方法