(Go Gin)Gin学习笔记(五)会话控制与参数验证:Cookie使用、Sessions使用、结构体验证参数、自定义验证参数

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语言中文文档

6. 💕👉博客专栏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值