GoWeb——Session和Cookie

Session和Cookie简介

HTTP协议是一种无状态协议,即服务器端每次接收到客户端的请求都是一个全新的请求,服务器端并不知道客户端的历史请求记录。session和cookie的主要目的就是为了弥补HTTP的无状态特性。

session是什么

客户端请求服务器端,服务器端会为这次请求开辟一块内存空间,这个对象便是session对象,存储结构为ConcurrentHashMap。

session弥补了HTTP的无状态特性,服务器端可以利用session存储客户端在同一个会话期间的一些操作记录。

session如何判断是否为同一个会话

服务器端在第一次接收到请求时,会开辟一块session空间(创建了session对象),同时生成一个sessionld,并通过响应头的“Set-Cookie:JSESSIONID=XXXXXXX”命令,向客户端发送要求设置cookie的响应。

客户端在收到响应后,在本机客户端设置了一个“JSESSIONID=XXXXXXX”的cookie信息,该cookie的过期时间为浏览器会话结束。

在这里插入图片描述

接下来,在客户端每次向同一个服务器端发送请求时,请求头中都会有该cooke信息(包含sessionld)。服务器端通过读取请求头中的cookie信息,获取名称为JSESSIONID的值,得到此次请求的sessionld。

session的缺点

session机制有一个缺点:如果A服务器存储了session(即做了负载均衡),假如一段时间内A的访问量激增,则访问会被转发到B服务器,但是B服务器并没有存储A服务器的session,从而导致session失效。

cookie是什么

HTTP协议中的cookie是服务器端发送到客户端Web浏览器的一小块数据,包括Web cookie和浏览器cookie。服务器端发送到客户端浏览器的cookie,浏览器会进行存储,并与下一个请求一起发送到服务器端。通常,它用于判断两个请求是否来自同一个客户端浏览器,例如用户保持登
录状态。
cookie主要用于以下3个方面:

  1. 会话管理:在登录、购物车、游戏得分或者服务器里常需要用会话管理来记住其内容。
  2. 实现个性化:个性化是指用户偏好、主题或者其他设置。
  3. 追踪:记录和分析用户行为。

cooke曾经用作一般的客户端存储,那时这是合法的,因为它们是在客户端上存储数据的唯方法。但如今建议使用现代存储API。cookie随每个请求一起被发送,因此它们可能会降低性能(尤其是对于移动数据连接而言)。

session和cookie的区别。

首先,无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用cookie,但session仍然是能够工作的,因为客户端无法禁用服务器端的session。

其次,在存储的数据量方面,session和cookie也是不一样的。session能够存储任意类型的对象,cookie只能存储String类型的对象。

创建cookie

当接收到客户端发出的HTTP请求时,服务器端可以发送带有响应的Set-Cookie标头。cookie通常由浏览器存储,浏览器将cookie与HTTP标头组合在一起向服务器端发送请求。

  1. Set-Cookie标头和cookie。
    Set-Cookie HTTP响应标头的作用是将cookie从服务器端发送到用户代理。
  2. 会话cookie
    会话cookie有一个特征一客户端关闭时cookie会被删除,因为它没有指定Expires或Max-Age指令。但是,Web浏览器可能会使用会话还原,这会使得大多数会话cookie保持“永久”状态,就像从未关闭过浏览器。
  3. 永久性cookie。
    永久性cookie不会在客户端关闭时过期,而是在到达特定日期(Expires)或特定时间长度(Max-Age)后过期。例如“Set-Cookie:id=b8gNc;Expires=Sun,21Dec202007:28:00
    GMT;”表示设置一个id为b8gNc、过期时间为2020年12月21日07:28:00、格林尼治时间的cookie。
  4. 安全的cookie。
    安全的cookie需要HTTPS协议通过加密的方式发送到服务器。即使是安全的,也不应该将敏感信息存储在Cooke中,因为它们本质上是不安全的,并且此标志不能提供真正的保护。
cookie的作用域

Domain和Path标识定义了cookie的作用域,即cookie应该被发送给哪些URL。Domain标识指定了哪些主机可以接受cookie。如果不指定Domain,则默认为当前主机(不包含子域名);如果指定了Domain,则一般包含子域名。例如,如果设置Domain=baidu.com,则cookie也包含在子域名中(如news.baidu.coml)。

例如,设置Path=test,则以下地址都会匹配:

  • /test
  • /test/news/
  • /test/news/id

Go与cookie

在Go标准库的net/http包中定义了名为Cookie的结构体。Cookie结构体代表一个出现在HTTP响应头中的Set-Cookie的值,或者HTTP请求头中的cookie的值。

Cookie结构体的定义如下:

type Cookie struct {
	Name  string
	Value string
	Path       string    
	Domain     string    
	Expires    time.Time 
	RawExpires string   

	//MaxAge=0标识未设置Max-Age属性
	//MaxAge<0标识立刻删除该cookie,等价于Max-Age:0
	//MaxAge>0标识存在Max-Age属性,单位是s
	MaxAge   int
	Secure   bool
	HttpOnly bool
	Raw      string
	Unparsed []string //未解析的"属性-值"对的原始文本
}
设置cookie

在Go语言的net/http包中提供了SetCookie()函数来设置cookie。
SetCookie()函数的定义如下:

func SetCookie(w ResponseWriter,cookie *Cookie)

SetCookie()函数的使用示例如下。

func main() {
	http.HandleFunc("/", testHandle)
	http.ListenAndServe(":8080", nil)
}

func testHandle(w http.ResponseWriter, r *http.Request) {
	c, err := r.Cookie("test_cookie")
	fmt.Printf("cookie:%#v, err:%v\n", c, err)

	cookie := &http.Cookie{
		Name:   "test_cookie",
		Value:  "DKLJFDSKIOfddFDSFIDSP",
		MaxAge: 3600,
		Domain: "localhost",
		Path:   "/",
	}

	http.SetCookie(w, cookie)
	//应在具体数据返回之前设置cookie,否则cookie设置不成功
	w.Write([]byte("hello"))
}
获取cookie

Go语言net/http包中的Request对象一共拥有3个处理cookie的方法:2个获取cookie的方法和1个添加cookie的方法。获取cookie,使用Cookies()或Cookie()方法。

  1. Cookies()方法。Cookies()方法的定义如下:
func (r *Request)Cookies() []*Cookie

Cookies()方法用于解析并返回该请求的所有cookie。

  1. Cookie()方法。Cookie()方法的定义如下:
func (r *Request)Cookie(name string)(*Cookie, error)

Cookie()方法用于返回请求中名为name的cookie,如果未找到该cookie,则返回“nil,ErrNoCookie”。

  1. AddCookie()方法。AddCookie()方法用于向请求中添加一个cookie。
    AddCookie()方法的定义如下:
func (r *Request)AddCookie(c *Cookie)

Cookie()方法和AddCookie()方法的使用示例如下。

func main() {
	CopeHandle("GET", "http://www.baidu.com", "")
}

func CopeHandle(method, urlVal, data string) {
	client := &http.Client{}
	var req *http.Request

	if data == "" {
		urlArr := strings.Split(urlVal, "?")
		if len(urlArr) == 2 {
			urlVal = urlArr[0] + "?" + getParseParam(urlArr[1])
		}
		req, _ = http.NewRequest(method, urlVal, nil)
	} else {
		req, _ = http.NewRequest(method, urlVal, strings.NewReader(data))
	}

	cookie := &http.Cookie{
		Name:     "X-Xsrftoken",
		Value:    "adfsf233lkj29432mdvmsdfdfdsfd",
		HttpOnly: true,
	}
	req.AddCookie(cookie)
	req.Header.Add("X-Xsrftoken", "werierpoweip2342tuiou543543")

	resp, err := client.Do(req)

	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	b, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(b))

}

//将GEt请求的参数进行转义
func getParseParam(param string) string {
	return url.PathEscape(param)
}

Go使用session

Go的标准库并没有提供实现session的方法,但很多Web框架都提供了。下面通过Go语言的具体示例来简单实现一个session的功能。

1. 定义一个名为Session的接口

Session结构体只有4种操作:设置值、获取值、删除值和获取当前的sessionld,因此Session接口应该有4种方法来执行这种操作:

//session接口
type Session interface {
	Set(key, value interface{}) error //设置session
	Get(key interface{}) interface{}  //获取session
	Delete(key interface{}) error     //删除session
	SessionID() string                //返回sessionId
}
2. 创建session管理器

由于session是被保存在服务器端数据中的,因此可以抽象出一个Provider接口来表示session管理器的底层结构。Provider接口将通过sessionld来访问和管理session:

//session管理器底层结构
type Provider interface {
	//session初始化
	SessionInit(sessionId string) (Session, error)
	//返回sessionId对应的session对象
	SessionRead(sessionId string) (Session, error)
	//删除给定的sessionId对应的session
	SessionDestroy(sessionId string) error 
	//根据maxLifeTime删除过期的session
	GarbageCollector(maxLifeTime int64)
}

在定义好Provider接口后,我们再写一个注册方法,以便可以根据provider管理器的名称来找到其对应的provider管理器:

var providers = make(map[string]Provider)

//注册一个能通过名称来获取的session provider管理器
func RegisterProvider(name string, provider Provider) {
	if provider == nil {
		panic("session: Register provider is nil")
	}
	if _, p := providers[name]; p {
		panic("session: Register provider is existed")
	}
	providers[name] = provider
}

接着把provider管理器封装一下,定义一个全局的session管理器:

//全局session管理器
type SessionManager struct {
	cookieName string //cookie的名称
	lock sync.Mutex //锁,保证并发时数据的一致性
	provider Provider //管理session
	maxLifeTime int64 //超时时间
}

func NewSessionManager(providerName, cookieName string, maxLifetime int64) (*SessionManager, error) {
	provider, ok := providers[providerName]
	if !ok {
		return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", providerName)
	}
	
	//返回一个SessionManager对象
	return &SessionManager{
		cookieName: cookieName,
		maxLifeTime: maxLifetime,
		provider: provider,
	}, nil
}

然后在main包中创建一个全局的session管理器:

var globalSession *SessionManager

func init() {
	globalSession, _ = NewSessionManager("memory", "sessionId", 3600)
}
3. 创建获取sessionId的方法GetSessionId()

sessionld是用来识别访问Web应用的每一个用户的,因此需要保证它是全局唯一的。示例代码如下:

func (manager *SessionManager) GetSessionId() string {
	b := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		return ""
	}
	return base64.URLEncoding.EncodeToString(b)
}
4. 创建SessionBegin()方法来创建session

需要为每个来访的用户分配或者获取与它相关连的session,以便后面能根据session信息来进行验证操作。SessionBegin()函数就是用来检测是否已经有某个session与当前来访用户发生了关联,如果没有则创建它。

//根据当前请求的cookie来判断是否存在有效的session,如果不存在则创建它
func (manager *SessionManager) SessionBegin(w http.ResponseWriter, r *http.Request) (session Session) {
	manager.lock.Lock()
	defer manager.lock.Unlock()
	cookie, err := r.Cookie(manager.cookieName)
	if err != nil || cookie.Value == "" {
		sessionId := manager.GetSessionId()
		session, _ = manager.provider.SessionInit(sessionId)
		cookie := http.Cookie{
			Name: manager.cookieName,
			Value: url.QueryEscape(sessionId),
			Path: "/",
			HttpOnly: true,
			MaxAge: int(manager.maxLifeTime),
		}
		http.SetCookie(w, &cookie)
	} else {
		sessionId, _ := url.QueryUnescape(cookie.Value)
		session, _ = manager.provider.SessionRead(sessionId)
	}
	return session
}

现在已经可以通过SessionBegin()方法返回一个满足Session接口的变量了。
下面通过一个例子来展示一下session的读写操作:

//根据用户名判断是否存在该用户的session,如果不存在则创建它
func login(w http.ResponseWriter, r *http.Request) {
	session := globalSession.SessionBegin(w, r);
	r.ParseForm();
	name := session.Get("username")
	if name != nil {
		//将表单提交的username值设置到session中
		session.Set("username", r.Form["username"])
	}
}
5.创建SessionDestroy()方法来注销session

在Wb应用中,通常有用户退出登录的操作。当用户退出应用时,我们就可以对该用户的session数据进行注销。下面创建一个名为SessionDestroy0的方法来注销session:

//创建SessionDestroy()方法来注销session
func (manager *SessionManager) SessiionDestroy(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie(manager.cookieName)
	if err != nil || cookie.Value == "" {
		return
	}
	manager.lock.Lock()
	defer manager.lock.Unlock()
	
	manager.provider.SessionDestroy(cookie.Value)
	expiredTime := time.Now()
	newCookie := http.Cookie{
		Name: manager.cookieName,
		Path: "/", 
		HttpOnly: true,
		Expires: expiredTime,
		MaxAge: -1,
	}
	http.SetCookie(w, &newCookie)
}
6.创建GarbageCollector()方法来删除session

接下来看看如何让session管理器删除session,示例代码如下:

func (manager *SessionManager) GarbageCollector() {
	manager.lock.Lock()
	defer manager.lock.Unlock()
	manager.provider.GarbageCollector(manager.maxLifeTime)
	
	time.AfterFunc(time.Duration(manager.maxLifeTime), func() {
		manager.GarbageCollector()
	})
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值