1 前言最近看了一些同学的面经,发现无论什么技术岗位,还是会问到 Session 和 Cookie 的区别。所有学技术的同学都知道 Session 和 Cookie 函数怎么用,知道 Session 和 Cookie 的区别就是 Session 是储存在服务端的,Cookie 是存储在浏览器的。但是实际上是什么东西,一些刚学习技术的同学估计还是模糊,我刚学 PHP 的时候,这种感觉特别明显。PHP 中 Session 和 Cookie 的操作只要操作 $_COOKIE 和 $_SESSION 数组就可以了,而且操作方式和功能一模一样,搞得我一脸懵逼。最后,还是自己实现了一个 Session 操作类才恍然大悟,实质上就是两个不同的存储对象嘛。2 CookieCookie 的诞生是为了能让无状态的 HTTP 报文带上一些特殊的数据,让服务端能够辨识请求的身份。对于 Cookie 的概念就不多说了,Cookie 说简单点就是浏览器上的一个 key-value 存储对象,通过开发者工具直接看到 Cookie 的内容(F12)写入数据方式Cookie 写入数据的方式是通过 HTTP 返回报文 Header 部分 Set-Cookie 字段来设置,一个带有写 Cookie 指令的的 HTTP 返回报文如下HTTP/1.1 200 OKSet-Cookie: SESSIONID=e13179a6-2378-11e9-ac30-fa163eeeaea1; Path=/Transfer-Encoding: chunkedDate: Tue, 29 Jan 2019 07:12:09 GMTServer: localhost上述报文 Set-Cookie 指示浏览器设置 key 为 SESSIONID,value 为 e13179a6-2378-11e9-ac30-fa163eeeaea1 的 Cookie获取数据方式浏览器在发送请求的时候会检查当前域已经设置的 Cookie,在 HTTP 请求报文 Header 部分的 Cookie 字段里面带上 Cookie 的信息。下面捉取了一段 HTTP 报文GET http://10.0.1.24:23333/ HTTP/1.1Host: 10.0.1.24:23333Connection: keep-alivePragma: no-cacheCache-Control: no-cacheUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Cookie: SESSIONID=e13179a6-2378-11e9-ac30-fa163eeeaea1从最后的 Cookie 字段看到,浏览器请求时带上了 key 为 SESSIONID,value 为 e13179a6-2378-11e9-ac30-fa163eeeaea1 的数据,后端直接解析 HTTP 报文就能获取 Cookie 的内容。3 SessionSession 在代码里面的语意是记录客户端状态的一个存储对象,是同一个客户端请求共享的数组。这个存储对象可以是文件、缓存系统、数据库。现在假设要使用 redis 来实现 Session 功能,那么就要求浏览器每次请求都要带一个相同的字符串作为身份信息,对应 redis 的 key,redis value 则为 Session 数组序列化的内容。那么如何让浏览器每次请求都带一个身份信息呢,这就是 Session 和 Cookie 的关系,通过 Cookie 传递这个身份信息。流程如下客户端请求服务端检查 Header,发现没有 Cookie,于是生成一个 UUID服务端处理数据,把部分数据(登录信息)存储到 redis 里面,UUID 为 key,用户 id 为 value返回报文中,增加 Set-Cookie 字段,内容带上 UUID浏览器收到报文,把 UUID 写进浏览器存储里面浏览器再次请求,带上当前的域的 Cookie,就是这个 UUID服务端通过 Cookie 字段获取到该 UUID,去 redis 里面获取用户的信息…4 手动实现 Session既然知道了 Session 的原理,我们手动实现一个 Session 操作类,采用文件保存的方式。http 框架采用 web.py,安装方式如下pip install web.pySession类我们要实现的这个类就叫 Sessionclass Session: def init(self): self.session_id = None # session 数组 self._items = dict() self._load()我们所有 session 文件放在 sessions 目录下,文件名为对应的 session id,内容为 Session 数组序列化的字符串。在初始化对象的时候通过获取名为 SESSIONID 的 Cookie,如果没有就生成一个新的。def _load(self): SESSIONID = web.cookies().get(‘SESSIONID’, None) if not SESSIONID: SESSIONID = uuid.uuid() self.session_id = SESSIONID self._loadFromDisk()获取到 SESSIONID 后,检查 sessions 目录下有没有对应的文件,如果有就读取并反序列化def _loadFromDisk(self): """ 从文件加载 SESSION """ file = ‘./sessions/%s’ % self.session_id if os.path.exists(file): f = open(file, ‘rb’) self._items = pickle.load(f) f.close()获取 Session 部分完成了,接下来就是保存 Session,我们要把 SESSIONID 写进 Cookie 里面,这样才能在下次请求时获取到对应的 SESSIONIDdef _setSessionCookie(self): """ Session id 写入 cookie """ web.setcookie(‘SESSIONID’, self.session_id)最后把 Session 的内容保存到文件里面def _saveToDisk(self): """ 保存 SESSION 到文件 """ f = open(’./sessions/%s’ % self.session_id, ‘wb’) pickle.dump(self._items, f) f.close()功能测试我们新建一个文件,叫 server.py,写入测试代码#!/usr/bin/env python# -- coding: utf-8 --# 测试 sessionimport webimport timefrom session import Sessionurls = ( ‘/’, ‘index’)app = web.application(urls, globals())class index: def GET(self): session = Session() if ’login_time’ not in session: session[’login_time’] = int(time.time()) return ’login time: %s’ % session[’login_time’]if name == “main”: app.run()在同一目录新建 session.py 文件,写入 Session 类代码#!/usr/bin/env python# -- coding: utf-8 --# Sessionimport osimport webimport uuidtry: import cPickle as pickleexcept ImportError: import pickleclass Session: __instance = None def new(cls): """ 单例模式 """ if cls.__instance is None: cls.__instance = object.new(cls) return cls.__instance else: return cls.__instance def init(self): self.session_id = None # session 数组 self._items = dict() self._load() def contains(self, key): return key in self._items def getitem(self, key): return self._items.get(key, None) def setitem(self, key, value): self._items[key] = value return True def delitem(self, key): if key in self._items: del self._items[key] return True def del(self): """ 析构函数,结束请求时执行 """ self._setSessionCookie() self._saveToDisk() def _load(self): SESSIONID = web.cookies().get(‘SESSIONID’, None) if not SESSIONID or SESSIONID is None: SESSIONID = uuid.uuid() self.session_id = SESSIONID self._loadFromDisk() def _loadFromDisk(self): """ 从文件加载 SESSION """ file = ‘./sessions/%s’ % self.session_id if os.path.exists(file): f = open(file, ‘rb’) self._items = pickle.load(f) f.close() def _setSessionCookie(self): """ Session id 写入 cookie """ web.setcookie(‘SESSIONID’, self.session_id) def _saveToDisk(self): """ 保存 SESSION 到文件 """ f = open(’./sessions/%s’ % self.session_id, ‘wb’) pickle.dump(self._items, f) f.close()再在同一目录新建 sessions 目录,存放我们的 Session 文件mkdir sessions启动服务[service@chengqm mysession]$ python server.py 23333http://0.0.0.0:23333/浏览器发起请求查看 Cookie 查看 Session 文件内容[service@chengqm mysession]$ cat sessions/e13179a6-2378-11e9-ac30-fa163eeeaea1(dp1S’login_time’p2I1548749002s.可以多次刷新和更换浏览器测试,测试结果是符合我们对 Session 的预期,简陋版 Session 类功能就算实现了。5 如果禁止 Cookie 是否可以获取 Session这是一道面试题,当年竟然能用这个问题问倒过一些朋友,还是有些意思的从前面可以知道,SESSIONID 是通过 Cookie 来传递,如果 Cookie 禁止了,还能获取 SESSIONID 吗? 答案是可以的既然 Cookie 禁止了,那么我们就可以用参数的方法传递 SESSIONID,后端返回的时候,增加一个返回参数,叫 SESSIONID,然后前端存储到 localstorage 里面前端请求的时候,去 localstorage 获取SESSIONID,在请求参数里面增加这一个参数后端 Session 处理,先尝试从 Cookie 中获取 SESSIONID,如果获取不到,再尝试从请求参数中获取 SESSIONID这样,就算禁止 Cookie 也是能获取 Session 的。6 总结最后,我们得出 Session 和 Cookie 区别和联系区别Cookie 是浏览器端的存储对象,有容量限制,通过 HTTP 报文与后端交互Session 是服务端的存储对象,实现的方式可以有文件系统、缓存系统、数据库联系Session 和 Cookie 都是为了实现 HTTP 请求带上客户端状态的方法Session 大多数情况下都是依赖 Cookie 来传递 Session Id