cookie 和 session 究竟是什么

cookie 你们应该都熟悉,好比说登陆某些网站一段时间后,就要求你从新登陆;再好比有的同窗很喜欢玩爬虫技术,有时候网站就是能够拦截住你的爬虫,这些都和 cookie 有关。若是你明白了服务器后端对于 cookie 和 session 的处理逻辑,就能够解释这些现象,甚至钻一些空子无限白嫖,待我慢慢道来。git

1、session 和 cookie 简介

cookie 的出现是由于 HTTP 是无状态的一种协议,换句话说,服务器记不住你,可能你每刷新一次网页,就要从新输入一次帐号密码进行登陆。这显然是让人没法接受的,cookie 的做用就比如服务器给你贴个标签,而后你每次向服务器再发请求时,服务器就可以 cookie 认出你。github

抽象地归纳一下:一个 cookie 能够认为是一个「变量」,形如 name=value,存储在浏览器;一个 session 能够理解为一种数据结构,多数状况是「映射」(键值对),存储在服务器上golang

注意,我说的是「一个」cookie 能够认为是一个变量,可是服务器能够一次设置多个 cookie,因此有时候说 cookie 是「一组」键值对儿,这也能够说得通。web

cookie 能够在服务器端经过 HTTP 的 SetCookie 字段设置 cookie,好比我用 Go 语言写的一个简单服务:面试

func cookie(w http.ResponseWriter, r *http.Request) {
    // 设置了两个 cookie 
    http.SetCookie(w, &http.Cookie{
        Name:       "name1",
        Value:      "value1",
    })

    http.SetCookie(w, &http.Cookie{
        Name:  "name2",
        Value: "value2",
    })
    // 将字符串写入网页
    fmt.Fprintln(w, "页面内容")
}

PS:我认真写了 100 多篇原创,手把手刷 200 道力扣题目,所有发布在 labuladong的算法小抄,持续更新。建议收藏,按照个人文章顺序刷题,掌握各类算法套路后投再入题海就如鱼得水了。算法

当浏览器访问对应网址时,经过浏览器的开发者工具查看这次 HTTP 通讯的细节,能够看见服务器的回应发出了两次 SetCookie 命令:数据库

7d2b16fa1ab9b18fae7affd92be9220e.jpg

在这以后,浏览器的请求中的 Cookie 字段就带上了这两个 cookie:编程

6661b7e1dd4ba2fb0a0273d5644fe947.jpg

cookie 的做用其实就是这么简单,无非就是服务器给每一个客户端(浏览器)打的标签,方便服务器辨认而已。固然,HTTP 还有不少参数能够设置 cookie,好比过时时间,或者让某个 cookie 只有某个特定路径才能使用等等。后端

但问题是,咱们也知道如今的不少网站功能很复杂,并且涉及不少的数据交互,好比说电商网站的购物车功能,信息量大,并且结构也比较复杂,没法经过简单的 cookie 机制传递这么多信息,并且要知道 cookie 字段是存储在 HTTP header 中的,就算可以承载这些信息,也会消耗不少的带宽,比较消耗网络资源。浏览器

session 就能够配合 cookie 解决这一问题,好比说一个 cookie 存储这样一个变量 sessionID=xxxx,仅仅把这一个 cookie 传给服务器,而后服务器经过这个 ID 找到对应的 session,这个 session 是一个数据结构,里面存储着该用户的购物车等详细信息,服务器能够经过这些信息返回该用户的定制化网页,有效解决了追踪用户的问题。

session 是一个数据结构,由网站的开发者设计,因此能够承载各类数据,只要客户端的 cookie 传来一个惟一的 session ID,服务器就能够找到对应的 session,认出这个客户。

固然,因为 session 存储在服务器中,确定会消耗服务器的资源,因此 session 通常都会有一个过时时间,服务器通常会按期检查并删除过时的 session,若是后来该用户再次访问服务器,可能就会面临从新登陆等等措施,而后服务器新建一个 session,将 session ID 经过 cookie 的形式传送给客户端。

那么,咱们知道 cookie 和 session 的原理,有什么切实的好处呢?除了应对面试,我给你说一个鸡贼的用处,就是能够白嫖某些服务

有些网站,你第一次使用它的服务,它直接免费让你试用,可是用一次以后,就让你登陆而后付费继续使用该服务。并且你发现网站彷佛经过某些手段记住了你的电脑,除非你换个电脑或者换个浏览器才能再白嫖一次。

PS:我认真写了 100 多篇原创,手把手刷 200 道力扣题目,所有发布在 labuladong的算法小抄,持续更新。建议收藏,按照个人文章顺序刷题,掌握各类算法套路后投再入题海就如鱼得水了。

那么问题来了,你试用的时候没有登陆,网站服务器是怎么记住你的呢?这就很显然了,服务器必定是给你的浏览器打了 cookie,后台创建了对应的 session 记录你的状态。你的浏览器在每次访问该网站的时候都会听话地带着 cookie,服务器一查 session 就知道这个浏览器已经无偿使用过了,得让它登陆付费,不能让它继续白嫖了。

那若是我不让浏览器发送 cookie,每次都假装成一个第一次来试用的小萌新,不就能够不断白嫖了么?浏览器会把网站的 cookie 以文件的形式存在某些地方(不一样的浏览器配置不一样),你把他们找到而后删除就好了。可是对于 Firefox 和 Chrome 浏览器,有不少插件能够直接编辑 cookie,好比个人 Chrome 浏览器就用的一款叫作 EditThisCookie 的插件,这是他们官网:

504d14692f93a910999bcab3f93e56ac.jpeg

这类插件能够读取浏览器在当前网页的 cookie,点开插件能够任意编辑和删除 cookie。固然,偶尔白嫖一两次还行,不鼓励高频率白嫖,想经常使用仍是掏钱吧,不然网站赚不到钱,就只能取消免费试用这个机制了

以上就是关于 cookie 和 session 的简单介绍,cookie 是 HTTP 协议的一部分,不算复杂,而 session 是能够定制的,因此下面详细看一下实现 session 管理的代码架构吧。

2、session 的实现

session 的原理不难,可是具体实现它但是颇有技巧的,通常须要三个组件配合完成,它们分别是 ManagerProvider 和 Session 三个类(接口)。

e571de7ffcd6b9ba2b02749f58ef5236.jpeg

一、浏览器经过 HTTP 协议向服务器请求路径 /content 的网页资源,对应路径上有一个 Handler 函数接收请求,解析 HTTP header 中的 cookie,获得其中存储的 sessionID,而后把这个 ID 发给 Manager

二、Manager 充当一个 session 管理器的角色,主要存储一些配置信息,好比 session 的存活时间,cookie 的名字等等。而全部的 session 存在 Manager 内部的一个 Provider 中。因此 Manager 会把 sid(sessionID)传递给 Provider,让它去找这个 ID 对应的具体是哪一个 session。

三、Provider 就是一个容器,最多见的应该就是一个散列表,将每一个 sid 和对应的 session 一一映射起来。收到 Manager 传递的 sid 以后,它就找到 sid 对应的 session 结构,也就是 Session 结构,而后返回它。

四、Session 中存储着用户的具体信息,由 Handler 函数中的逻辑拿出这些信息,生成该用户的 HTML 网页,返回给客户端。

那么你也许会问,为何搞这么麻烦,直接在 Handler 函数中搞一个哈希表,而后存储 sid 和 Session 结构的映射不就完事儿了?

这就是设计层面的技巧了,下面就来讲说,为何分红 ManagerProvider 和 Session

先从最底层的 Session 说。既然 session 就是键值对,为啥不直接用哈希表,而是要抽象出这么一个数据结构呢?

第一,由于 Session 结构可能不止存储了一个哈希表,还能够存储一些辅助数据,好比 sid,访问次数,过时时间或者最后一次的访问时间,这样便于实现想 LRU、LFU 这样的算法。

第二,由于 session 能够有不一样的存储方式。若是用编程语言内置的哈希表,那么 session 数据就是存储在内存中,若是数据量大,很容易形成程序崩溃,并且一旦程序结束,全部 session 数据都会丢失。因此能够有不少种 session 的存储方式,好比存入缓存数据库 Redis,或者存入 MySQL 等等。

所以,Session 结构提供一层抽象,屏蔽不一样存储方式的差别,只要提供一组通用接口操纵键值对:

type Session interface {
    // 设置键值对
    Set(key, val interface{})
    // 获取 key 对应的值
    Get(key interface{}) interface{}
    // 删除键 key
    Delete(key interface{})
}

再说 Provider 为啥要抽象出来。咱们上面那个图的 Provider 就是一个散列表,保存 sid 到 Session 的映射,可是实际中确定会更加复杂。咱们不是要时不时删除一些 session 吗,除了设置存活时间以外,还能够采用一些其余策略,好比 LRU 缓存淘汰算法,这样就须要 Provider 内部使用哈希链表这种数据结构来存储 session。

PS:关于 LRU 算法的奥妙,参见前文「LRU 算法详解」。

所以,Provider 做为一个容器,就是要屏蔽算法细节,以合理的数据结构和算法组织 sid 和 Session 的映射关系,只须要实现下面这几个方法实现对 session 的增删查改:

type Provider interface {
    // 新增并返回一个 session
    SessionCreate(sid string) (Session, error)
    // 删除一个 session
    SessionDestroy(sid string)
    // 查找一个 session
    SessionRead(sid string) (Session, error)
    // 修改一个session
    SessionUpdate(sid string)
    // 经过相似 LRU 的算法回收过时的 session
    SessionGC(maxLifeTime int64)
}

最后说 Manager,大部分具体工做都委托给 Session 和 Provider 承担了,Manager 主要就是一个参数集合,好比 session 的存活时间,清理过时 session 的策略,以及 session 的可用存储方式。Manager 屏蔽了操做的具体细节,咱们能够经过 Manager 灵活地配置 session 机制。

综上,session 机制分红几部分的最主要缘由就是解耦,实现定制化。我在 Github 上看过几个 Go 语言实现的 session 服务,源码都很简单,有兴趣的朋友能够学习学习:

https://github.com/alexedwards/scs

https://github.com/astaxie/build-web-application-with-golang

_____________

相关文章
相关标签/搜索