Koa session和cookie

从http协议的无状态性说起

HTTP是一种无状态协议。关于这个无状态之前我也不太理解,因为HTTP底层是TCP,既然是TCP,就是长连接,这个过程是保持连接状态的,又为什么说http是无状态的呢?先来搞清楚这两个概念:

无连接和无状态

无连接

每次连接只处理一个请求,服务端处理完客户端一次请求,等到客户端作出回应之后便断开连接;

无状态

是指服务端对于客户端每次发送的请求都认为它是一个新的请求,上一次会话和下一次会话没有联系;

无连接的维度是连接,无状态的维度是请求;http是基于tcp的,而从http1.1开始默认使用持久连接;在这个连接过程中,客户端可以向服务端发送多次请求,但是各个请求之间的并没有什么联系;这样来考虑,就很好理解无状态这个概念了。

持久连接

持久连接,本质上是客户端与服务器通信的时候,建立一个持久化的TCP连接,这个连接不会随着请求结束而关闭,通常会保持连接一段时间。`

现有的持久连接类型有两种:HTTP/1.0+的 keep-alive 和HTTP/1.1的 persistent

  • HTTP/1.0+的keep-alive

先来开一张图:

这张图是请求www.baidu.com时的请求头信息。这里面我们需要注意的是:

1
connection: keep-alive

我们每次发送一个HTTP请求,会附带一个connection:keep-alive,这个参数就是声明一个持久连接。

  • HTTP/1.1的persistent

HTTP/1.1的持久连接默认是开启的,只有首部中包含connection:close,才会事务结束之后关闭连接。当然服务器和客户端仍可以随时关闭持久连接。

当发送了connection:close首部之后客户端就没有办法在那条连接上发送更多的请求了。当然根据持久连接的特性,一定要传输正确的content-length。

还有根据HTTP/1.1的特性,是不应该和HTTP/1.0客户端建立持久连接的。最后,一定要做好重发的准备。

http无状态

OK,首先来明确下,这个状态的主体指的是什么?应该是信息,这些信息是由服务端所维护的与客户端交互的信息(也称为状态信息);
因为HTTP本身是不保存任何用户的状态信息的,所以HTTP是无状态的协议。

如何保持状态信息

在聊这个这个问题之前,我们来考虑下为什么http自己不来做这个事情:也就是让http变成有状态的。

http本身来实现状态维护

从上面关于无状态的理解,如果现在需要让http自己变成有状态的,就意味着http协议需要保存交互的状态信息;暂且不说这种方式是否合适,但从维护状态信息这一点来说,代价就很高,因为既然保存了状态信息,那后续的一些行为必定也会受到状态信息的影响。

从历史角度来说,最初的http协议只是用来浏览静态文件的,无状态协议已经足够,这样实现的负担也很轻。但是随着web技术的不断发展,越来越多的场景需要状态信息能够得以保存;一方面是http本身不会去改变它的这种无状态的特性(至少目前是这样的),另一方面业务场景又迫切的需要保持状态;那么这个时候就需要来“装饰”一下http,引入一些其他机制来实现有状态。

cookie和session体系

通过引入cookie和session体系机制来维护状态信息。即用户第一次访问服务器的时候,服务器响应报头通常会出现一个Set-Cookie响应头,这里其实就是在本地设置一个cookie,当用户再次访问服务器的时候,http会附带这个cookie过去,cookie中存有sessionId这样的信息来到服务器这边确认是否属于同一次会话。

cookie是由服务器发送给客户端(浏览器)的小量信息,以{key:value}的形式存在。

Cookie机制原理

客户端请求服务器时,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。而客户端浏览器会把Cookie保存起来。当浏览器再请求 服务器时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器通过检查该Cookie来获取用户状态。

cookie 是存储于访问者的计算机中的变量。可以让我们用同一个浏览器访问同一个域名的时候共享数据。

HTTP 是无状态协议。简单地说,当你浏览了一个页面,然后转到同一个网站的另一个页面,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次的访问,都是没有任何 关系的。

1、Koa 中设置 Cookie 的值

1
ctx.cookies.set(name, value, [options])

通过options 设置 cookie name 的value :

options 名称 options 值
maxAge 一个数字表示从 Date.now() 得到的毫秒数
expires cookie 过期的Date
path cookie 路径, 默认是’/‘
domain cookie 域名,可以访问该Cookie的域名。如果设置为“.baidu.com”,则所有以“baidu.com”结尾的域名都可以访问该Cookie;第一个字符必须为“.”
secure 安全 cookie 默认 false,设置成 true 表示只有 https 可以访问
httpOnly 是否只是服务器可访问 cookie, 默认是true
overwrite 一个布尔值,表示是否覆盖以前设置的同名的 cookie (默认是 false). 如果是 true, 在同一个请求中设置相同名称的所有 Cookie(不管路径或域) 是否在设置此 Cookie 时从Set-Cookie 标头中过滤掉。

Cookie属性

name

cookie的名字,Cookie一旦创建,名称便不可更改

value

cookie值

comment

该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明

maxAge

Cookie失效的时间,单位秒。

  • 正数,则超过maxAge秒之后失效。

  • 负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。

  • 为0,表示删除该Cookie。

path

该Cookie的使用路径。例如:

  • path=/,说明本域名下contextPath都可以访问该Cookie。
  • path=/app/,则只有contextPath为“/app”的程序可以访问该Cookie

path设置时,其以“/”结尾.

secure

该Cookie是否仅被使用安全协议传输。这里的安全协议包括HTTPS,SSL等。默认为false。

从请求中获取Cookie

1
ctx.cookies.get('name');

Cookie同源与跨域

我们知道浏览器的同源策略:

URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。浏览器的同源策略,限制了来自不同源的”document”或脚本,对当前”document”读取或设置某些属性。

对于Cookie来说,Cookie的同源只关注域名,是忽略协议和端口的。所以一般情况下,https://localhost:80/和http://localhost:8080/的Cookie是共享的。

Cookie是不可跨域的;在没有经过任何处理的情况下,二级域名不同也是不行的。(wenku.baidu.com和baike.baidu.com)。

Cookie数量&大小限制及处理策略

IE6.0 IE7.0/8.0 Opera FF Safari Chrome
个数/个 20/域 50/域 30/域 50/域 无限制 53/域
大小/Byte 4095 4095 4096 4097 4097 4097
1
2
3
4
5
// 转换成 base64 字符串:aGVsbG8sIHdvcmxkIQ==
console.log(new Buffer('hello, world!').toString('base64'));

// 还原 base64 字符串:hello, world!
console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString());

eg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
var Koa=require('koa'),
router = require('koa-router')(),
render = require('koa-art-template'),
path=require('path');

var app=new Koa();

//配置 koa-art-template模板引擎
render(app, {
root: path.join(__dirname, 'views'), // 视图的位置
extname: '.html', // 后缀名
debug: process.env.NODE_ENV !== 'production' //是否开启调试模式

});

router.get('/',async (ctx)=>{

//正常就这样配置就可以使用了
/*
ctx.cookies.set('userinfo','zhangsan',{
maxAge:60*1000*60
});
* */


ctx.cookies.set('userinfo','zhangsan2222',{
maxAge:60*1000*60,

// path:'/news', /*配置可以访问的页面*/
//domain:'.baidu.com' /*正常情况不要设置 默认就是当前域下面的所有页面都可以方法*/

httpOnly:false, //true表示这个cookie只有服务器端可以访问,false表示客户端(js),服务器端都可以访问
/*
a.baidu.com
b.baidu.com 共享cookie的数据
express基础教程
* */


});



let list={
name:'张三'
}
await ctx.render('index',{
list:list

});
})

router.get('/about',async (ctx)=>{

ctx.cookies.set('userinfo','zhangsan33333',{
maxAge:60*1000*60,
path:'/news'
});

ctx.body="这是关于我们";
})
//接收post提交的数据
router.get('/news',async (ctx)=>{

var userinfo=ctx.cookies.get('userinfo');

console.log(userinfo);
let app={
name:'张三11'
};
await ctx.render('news',{
list:app
});
})

router.get('/shop',async (ctx)=>{

var userinfo=ctx.cookies.get('userinfo');

console.log(userinfo);
ctx.body='这是一个商品页面'+userinfo;
})

app.use(router.routes()); /*启动路由*/
app.use(router.allowedMethods());
app.listen(3000);

Session

session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而session 保存在服务器上。

Cookie机制弥补了HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。

与Cookie不同的是,session是以服务端保存状态的。

session机制原理

当浏览器访问服务器并发送第一次请求时,服务器端会创建一个session 对象,生成一个类似于key,value 的键值对, 然后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带 key(cookie),找到对应的 session(value)。 客户的信息都保存在session 中

当客户端请求创建一个session的时候,服务器会先检查这个客户端的请求里是否已包含了一个session标识 - sessionId,

  • 如果已包含这个sessionId,则说明以前已经为此客户端创建过session,服务器就按照sessionId把这个session检索出来使用(如果检索不到,可能会新建一个)
  • 如果客户端请求不包含sessionId,则为此客户端创建一个session并且生成一个与此session相关联的sessionId

sessionId的值一般是一个既不会重复,又不容易被仿造的字符串,这个sessionId将被在本次响应中返回给客户端保存。保存sessionId的方式大多情况下用的是cookie。

koa-session 的使用:

安装 express-session

1
npm	install	koa-session --save

引入express-session

1
const session = require('koa-session');

设置官方文档提供的中间件

1
2
3
4
5
6
7
8
9
10
11
app.keys = ['some secret hurr'];   /*cookie的签名*/
const CONFIG = {
key: 'koa:sess', /** 默认 */
maxAge: 10000, /* cookie的过期时间 【需要修改】 */
overwrite: true, /** (boolean) can overwrite or not (default true) 没有效果,默认 */
httpOnly: true, /** true表示只有服务器端可以获取cookie */
signed: true, /** 默认 签名 */
rolling: true, /** 在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false) 【需要修改】 */
renew: false, /** 快要过期的时候重新设置 【需要修改】*/
};
app.use(session(CONFIG, app));

使用

1
2
3
4
5
//设置值 
ctx.session.username = "张三";

//获取值
ctx.session.username

eg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
var Koa=require('koa'),
router = require('koa-router')(),
render = require('koa-art-template'),
path=require('path'),
session = require('koa-session');


var app=new Koa();

//配置 koa-art-template模板引擎
render(app, {
root: path.join(__dirname, 'views'), // 视图的位置
extname: '.html', // 后缀名
debug: process.env.NODE_ENV !== 'production' //是否开启调试模式
});
//配置session的中间件
app.keys = ['some secret hurr']; /*cookie的签名*/
const CONFIG = {
key: 'koa:sess', /** 默认 */
maxAge: 10000, /* cookie的过期时间 【需要修改】 */
overwrite: true, /** (boolean) can overwrite or not (default true) 没有效果,默认 */
httpOnly: true, /** true表示只有服务器端可以获取cookie */
signed: true, /** 默认 签名 */
rolling: true, /** 在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false) 【需要修改】 */
renew: false, /** (boolean) renew session when session is nearly expired 【需要修改】*/
};
app.use(session(CONFIG, app));



router.get('/',async (ctx)=>{

//获取session
console.log(ctx.session.userinfo);
await ctx.render('index',{
list:{
name:'张三'
}
});
})

router.get('/news',async (ctx)=>{

//获取session
console.log(ctx.session.userinfo);
ctx.body="登录成功";
})


router.get('/login',async (ctx)=>{

//设置session
ctx.session.userinfo='张三';
ctx.body="登录成功";
})




app.use(router.routes()); /*启动路由*/
app.use(router.allowedMethods());
app.listen(3000);

1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。

2、cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗考虑到安全应当使用 session。

3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用 COOKIE。

4、单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。

这两种是一样的;如果session不存在,就新建一个;如果是false的话,标识如果不存在就返回null;

生命周期

session的生命周期指的是创建session对象到销毁的过程。会依据session对象设置的存活时间,在达到session时间后将session对象销毁。session生成后,只要用户继续访问,服务器就会更新session的最后访问时间,并维护该session。

之前在单进程应用中,session我一般是存在内存中的,不会做持久化操作或者说使用三方的服务来存session信息,如redis。但是在分布式场景下,这种存在本机内存中的方式显然是不适用的,因为session无法共享。这个后面说。

session的有效期

session一般在内存中存放,内存空间本身大小就有一定的局限性,因此session需要采用一种过期删除的机制来确保session信息不会一直累积,来防止内存溢出的发生。

session的超时时间可以通过maxInactiveInterval属性来设置。

如果我们想让session失效的话,也可以当通过调用session的invalidate()来完成。

分布式session

首先是为什么会有这样的概念出现?

先考虑这样一个问题,现在我的应用需要部署在3台机器上。是不是出现这样一种情况,我第一次登陆,请求去了机器1,然后再机器1上创建了一个session;但是我第二次访问时,请求被路由到机器2了,但是机器2上并没有我的session信息,所以得重新登录。当然这种可以通过nginx的IP HASH负载策略来解决。对于同一个IP请求都会去同一个机器。

但是业务发展的越来越大,拆分的越来越多,机器数不断增加;很显然那种方案就不行了。那么这个时候就需要考虑是不是应该将session信息放在一个独立的机器上,所以分布式session要解决的问题其实就是分布式环境下的session共享的问题。

上图中的关于session独立部署的方式有很多种,可以是一个独立的数据库服务,也可以是一个缓存服务(redis,目前比较常用的一种方式,即使用Redis来作为session缓存服务器)。