Cookie

Cookie

由于 HTTP(S) 协议是一个无状态的协议,所以多次请求之间并不知道是来自同一个用户。这样就会带来很多问题,如:有些页面用户登录后才能访问,页面内容根据用户相关。

在早期时代,解决方案一般是生成一个随机 token,以后每次请求都会携带这个 token 来识别用户。这需要在 form 表单中插入一个包含 token 的隐藏域,或者放在 URL 请求的参数上。

这种方式虽然能解决问题,但给开发带来很大的不便,也不利于页面地址的传播。为了解决这个问题,RFC 2965 引用了 Cookie 机制,请求时携带 Cookie 头信息,响应时通过 Set-Cookie 字段设置 Cookie

Cookie 格式

请求时 Cookie 格式为:

Cookie: name1=value1; name2=value2; name3=value3 //多个 Cookie 之间用 `; ` 隔开

响应时 Cookie 格式为:

Set-Cookie: key1=value1; path=path; domain=domain; max-age=max-age-in-seconds; expires=date-in-GMTString-format; secure; httponly Set-Cookie: key2=value2; path=path; domain=domain; max-age=max-age-in-seconds; expires=date-in-GMTString-format; secure; httponly

  • key=value 名称、值的键值对

如果不设置 max-ageexpires,那么 Cookie 会随着浏览器的进程退出而销毁。对于不希望 JS 能够获取到 Cookie,一般设置 httponly 属性,比如:用户 Session 对应的 Cookie。

虽然标准里并没有对 Cookie 的大小限制的规定,但浏览器一般都会有限制,所以不能将太大的文本保存在 Cookie 中(一般不能超过 4K)。

配置

框架中是通过 cookies 模块来进行 Cookie 的读取与设置的,支持如下的配置:

  • maxAge: cookie的超时时间,表示当前时间(Date.now())之后的毫秒数。

如果需要修改上面的配置,可以在配置文件 src/config/config.js 中修改。如:

module.exports = { cookie: { domain: '', path: '/', maxAge: 10 * 3600 * 1000, // 10个小时 signed: true, keys: [] // 当 signed 为 true 时,使用 keygrip 库加密时的密钥 } }

操作 cookie

在 ctx、controller、logic 中,提供了 cookie 方法来操作 cookie

获取 cookie

const theme = this.cookie('theme')

设置 cookie

this.cookie('theme', 'gray' this.cookie('theme', 'yellow', { // 设定 cookie 时指定额外的配置 maxAge: 10 * 1000, path: '/theme' })

删除 cookie

this.cookie('theme', null) this.cookie('theme', null, { domain: '', path: '' })

删除 cookie 时需要和设置 cookie 时同样的 domain 和 path 配置,否则会因为不匹配导致 cookie 删除不成功。

常见问题

输出内容后能否再发送 cookie?

由于发送 cookie 是通过 Set-Cookie header 字段来完成的,HTTP 协议中,规定 header 信息必须在内容之前发送,所以输出内容后不能再发送 cookie 信息。

如果强制在输出内容之后发送 cookie 等 header 信息,会出现类似下面的错误:

[ERROR] - Error: Can't set headers after they are sent. at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:346:11) at Cookies.set (think-demo/node_modules/thinkjs/node_modules/cookies/index.js:115:13) at Object.cookie (think-demo/node_modules/thinkjs/lib/extend/context.js:260:21) at IndexController.cookie (think-demo/node_modules/thinkjs/lib/extend/controller.js:181:21) at Timeout._onTimeout (think-demo/src/controller/index.js:10:12) at tryOnTimeout (timers.js:224:11) at Timer.listOnTimeout (timers.js:198:5)