-----------
cookie大家应该都熟悉,比如说登录某些网站一段时间后,就要求你重新登录;再比如有的同学很喜欢玩爬虫技术,有时候网站就是可以拦截住你的爬虫,这些都和cookie有关。如果你明白了服务器后端对于cookie和session的处理逻辑,就可以解释这些现象,甚至钻一些空子无限白嫖,待我慢慢道来。
一、session和cookie简介cookie的出现是因为HTTP是无状态的一种协议,换句话说,服务器记不住你,可能你每刷新一次网页,就要重新输入一次账号密码进行登录。这显然是让人无法接受的,cookie的作用就好比服务器给你贴个标签,然后你每次向服务器再发请求时,服务器就能够cookie认出你。
抽象地概括一下:一个cookie可以认为是一个「变量」,形如name=value,存储在浏览器;一个session可以理解为一种数据结构,多数情况是「映射」(键值对),存储在服务器上。
注意,我说的是「一个」cookie可以认为是一个变量,但是服务器可以一次设置多个cookie,所以有时候说cookie是「一组」键值对儿,这也可以说得通。
cookie可以在服务器端通过HTTP的SetCookie字段设置cookie,比如我用Go语言写的一个简单服务:
funccookie(,r*){//设置了两个(w,{Name:"name1",Value:"value1",})(w,{Name:"name2",Value:"value2",})//将字符串写入网页(w,"页面内容")}当浏览器访问对应网址时,通过浏览器的开发者工具查看此次HTTP通信的细节,可以看见服务器的回应发出了两次SetCookie命令:
在这之后,浏览器的请求中的Cookie字段就带上了这两个cookie:
cookie的作用其实就是这么简单,无非就是服务器给每个客户端(浏览器)打的标签,方便服务器辨认而已。当然,HTTP还有很多参数可以设置cookie,比如过期时间,或者让某个cookie只有某个特定路径才能使用等等。
但问题是,我们也知道现在的很多网站功能很复杂,而且涉及很多的数据交互,比如说电商网站的购物车功能,信息量大,而且结构也比较复杂,无法通过简单的cookie机制传递这么多信息,而且要知道cookie字段是存储在HTTPheader中的,就算能够承载这些信息,也会消耗很多的带宽,比较消耗网络资源。
session就可以配合cookie解决这一问题,比如说一个cookie存储这样一个变量sessionID=xxxx,仅仅把这一个cookie传给服务器,然后服务器通过这个ID找到对应的session,这个session是一个数据结构,里面存储着该用户的购物车等详细信息,服务器可以通过这些信息返回该用户的定制化网页,有效解决了追踪用户的问题。
session是一个数据结构,由网站的开发者设计,所以可以承载各种数据,只要客户端的cookie传来一个唯一的sessionID,服务器就可以找到对应的session,认出这个客户。
当然,由于session存储在服务器中,肯定会消耗服务器的资源,所以session一般都会有一个过期时间,服务器一般会定期检查并删除过期的session,如果后来该用户再次访问服务器,可能就会面临重新登录等等措施,然后服务器新建一个session,将sessionID通过cookie的形式传送给客户端。
那么,我们知道cookie和session的原理,有什么切实的好处呢?除了应对面试,我给你说一个鸡贼的用处,就是可以白嫖某些服务。
有些网站,你第一次使用它的服务,它直接免费让你试用,但是用一次之后,就让你登录然后付费继续使用该服务。而且你发现网站似乎通过某些手段记住了你的电脑,除非你换个电脑或者换个浏览器才能再白嫖一次。
那么问题来了,你试用的时候没有登录,网站服务器是怎么记住你的呢?这就很显然了,服务器一定是给你的浏览器打了cookie,后台建立了对应的session记录你的状态。你的浏览器在每次访问该网站的时候都会听话地带着cookie,服务器一查session就知道这个浏览器已经免费使用过了,得让它登录付费,不能让它继续白嫖了。
以上就是关于cookie和session的简单介绍,cookie是HTTP协议的一部分,不算复杂,而session是可以定制的,所以下面详细看一下实现session管理的代码架构吧。
二、session的实现session的原理不难,但是具体实现它可是很有技巧的,一般需要三个组件配合完成,它们分别是Manager、Provider和Session三个类(接口)。
1、浏览器通过HTTP协议向服务器请求路径/content的网页资源,对应路径上有一个Handler函数接收请求,解析HTTPheader中的cookie,得到其中存储的sessionID,然后把这个ID发给Manager。
2、Manager充当一个session管理器的角色,主要存储一些配置信息,比如session的存活时间,cookie的名字等等。而所有的session存在Manager内部的一个Provider中。所以Manager会把sid(sessionID)传递给Provider,让它去找这个ID对应的具体是哪个session。
3、Provider就是一个容器,最常见的应该就是一个散列表,将每个sid和对应的session一一映射起来。收到Manager传递的sid之后,它就找到sid对应的session结构,也就是Session结构,然后返回它。
4、Session中存储着用户的具体信息,由Handler函数中的逻辑拿出这些信息,生成该用户的HTML网页,返回给客户端。
那么你也许会问,为什么搞这么麻烦,直接在Handler函数中搞一个哈希表,然后存储sid和Session结构的映射不就完事儿了?
这就是设计层面的技巧了,下面就来说说,为什么分成Manager、Provider和Session。
先从最底层的Session说。既然session就是键值对,为啥不直接用哈希表,而是要抽象出这么一个数据结构呢?
第一,因为Session结构可能不止存储了一个哈希表,还可以存储一些辅助数据,比如sid,访问次数,过期时间或者最后一次的访问时间,这样便于实现想LRU、LFU这样的算法。
第二,因为session可以有不同的存储方式。如果用编程语言内置的哈希表,那么session数据就是存储在内存中,如果数据量大,很容易造成程序崩溃,而且一旦程序结束,所有session数据都会丢失。所以可以有很多种session的存储方式,比如存入缓存数据库Redis,或者存入MySQL等等。
因此,Session结构提供一层抽象,屏蔽不同存储方式的差异,只要提供一组通用接口操纵键值对:
typeSessioninterface{//设置键值对Set(key,valinterface{})//获取key对应的值Get(keyinterface{})interface{}//删除键keyDelete(keyinterface{})}再说Provider为啥要抽象出来。我们上面那个图的Provider就是一个散列表,保存sid到Session的映射,但是实际中肯定会更加复杂。我们不是要时不时删除一些session吗,除了设置存活时间之外,还可以采用一些其他策略,比如LRU缓存淘汰算法,这样就需要Provider内部使用哈希链表这种数据结构来存储session。
PS:关于LRU算法的奥妙,参见前文「LRU算法详解」。
因此,Provider作为一个容器,就是要屏蔽算法细节,以合理的数据结构和算法组织sid和Session的映射关系,只需要实现下面这几个方法实现对session的增删查改:
typeProviderinterface{//新增并返回一个sessionSessionCreate(sidstring)(Session,error)//删除一个sessionSessionDestroy(sidstring)//查找一个sessionSessionRead(sidstring)(Session,error)//修改一个sessionSessionUpdate(sidstring)//通过类似LRU的算法回收过期的sessionSessionGC(maxLifeTimeint64)}最后说Manager,大部分具体工作都委托给Session和Provider承担了,Manager主要就是一个参数集合,比如session的存活时间,清理过期session的策略,以及session的可用存储方式。Manager屏蔽了操作的具体细节,我们可以通过Manager灵活地配置session机制。
综上,session机制分成几部分的最主要原因就是解耦,实现定制化。我在Github上看过几个Go语言实现的session服务,源码都很简单,有兴趣的朋友可以学习学习:





