内容简介:如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
一、前言
早期为了解决“会话保持”的需求,社区中出现了「cookie 方案」并最终成为 W3C 标准:当某个网站登录成功后,客户端(浏览器)收到一个 cookie 标识(文本)并保存下来,在后续请求中会自动带上这个字段,由此 Web 后台可以判断是否同一个用户,从而使“会话”得以延续。
微信小程序没有像浏览器一样内置实现了 cookie 方案,需要开发者自行模拟,而原先京东购物小程序及京喜小程序(现微信一级购物入口)是从微信及手 Q 购物入口 H5 中迁移迭代出来的,也就是说我们不仅要在小程序中模拟一套 cookie 方案,并且要保持和原业务对 cookie 处理逻辑的一致,为此我们将实现方向确定为“基于小程序开放能力,和浏览器保持一致”。
微信小程序开放了 数据缓存 Storage [1] 和 网络 Network [2] 这两种能力,通过这两套 API,我们可以自行 DIY 一个 cookie 方案。
PS:本文所有代码及使用示例都可以 在这里 [3] 找到,阅读本文时配合实践,效果更佳。
二、浏览器中的 cookie
为了保持后端对 cookie 的处理逻辑和原来的 H5 一致,小程序的实现需要往浏览器看齐。
所以模拟小程序的 cookie 前,先看看浏览器的 cookie 机制,主要有以下几个部分:
-
本地存储:浏览器会在本地分配一块空间,存储 cookie
-
请求携带:每次发起请求,都会从本地取出 cookie 并追加在请求头上
-
响应设置:当响应头有 Set-Cookie 字段时,需要解析并更新
-
过期时间:每个 cookie 字段有单独的过期时间,并且到期会自动清除
-
读写操作:暴露 API 给前端 JS 调用,可进行增删改查操作
-
作用域:路径 path、域名 domin
-
编码:cookie 值,在网络传输需要 encode,建议存储也一样
-
其它:HttpOnly、Secure、SameSite
在浏览器的 DevTools
中,可以看到当前站点下的 Cookie 明细:
三、小程序中的 cookie 实现
方案设计
在小程序中模拟 Cookie,主要涉及五个部分: 其中我们会重点关注 「Cookie 基础库」 的实现,另外也会给出「Request 基础库」的封装示例。
本地存储
小程序提供了 「数据缓存 Storage API」(可以理解为 Web 规范中的 LocalStorage
),支持存储“原生类型、Date、及能够通过 JSON.stringify 序列化的对象”。
我们可以利用这些 API,在 Storage 中新开一个 cookies
字段进行存储:
// 存: wx.setStorageSync('cookies', cookies) // 取: wx.getStorageSync('cookies')
其中 cookies
的「存储结构」如下:
// cookies = { cookie1: { // “最小 cookie 单元” ==> cookieItem name: 'cookie1', // cookie 名 value: 'xxx', // cookie 值 expires: 'Fri, 17 Jan 2020 08:49:41 GMT' // 过期时间,使用 GMT(格林威治标准时间)格式 } },
上面的 cookie1
便是一个“最小 cookie 单元 cookieItem
”,包含了 3 个字段(name、value、expires),是本文中定义的「标准 cookie 格式」,也是 cookie 操作的基本单元。
打开【微信开发工具】的 Storage
选项卡,可以查看本地存储的情况:
读写操作
这部分主要作为“公共基础库“的角色,为外部业务提供增删改查 cookie 的 API。
1. 获取 cookie———— getCookie()
步骤:从 Storage 中取出完整 cookies ==> 取出指定 name 的 cookie 项 ==> 校验有效期 ==> 返回值 value
实现如下:
function getCookie(name = '') { let cookies = wx.getStorageSync('cookies') // try/catch 略过 let { value, expires } = cookies[name] || {} return (name && expires && !isExpired(expires)) ? decodeURIComponent(cookieItem.value) : '' }
2. 设置 cookie———— setCookie()
步骤:从 Storage 中取出完整 cookies ==> 解析入参 ==> 覆盖更新 ==> 同步到本地 Storage
首先看下本 API 设计需求:
-
设置单个/多个 cookie
-
直接传值/传 cookieItem(Object)
-
时间格式 maxAge/expires
调用示例如下:
setCookie({ cookie1: 12345, cookie2: '12345' }) setCookie({ cookie1: { value: 12345, maxAge: 3600 * 24 // 自定义有效期(这里示例是24小时) }, cookie2: { value: '12345', expires: 'Wed, 21 Oct 2015 07:28:00 GMT' // 标准 GMT 格式 } })
这里可对入参遍历,而 cookie 子项无论直接传值 value 还是传了详细 object,都尽量的获取 name/value/expires/maxAge
,传给格式化函数转为标准的 cookieItem
:
function setCookie(cookiesParam) { let oldCookies = wx.getStorageSync('cookies') // try/catch 略过 let newCookies = {} // 由 cookiesParam 转化为标准格式后的 cookies for (let name in cookiesParam) { if (isObject(cookiesParam[name])) { // 传入是 Object 格式 let { value, expires, maxAge } = cookiesParam[name] // 转换为标准 cookie 格式(cookieItem) newCookies[name] = getStandardCookieItem({ name, value, expires, maxAge }) } else { newCookies[name] = getStandardCookieItem({ name, value: cookiesParam[name] }) } } // 同步到本地 Storage saveCookiesToStorage(Object.assign({}, oldCookies, newCookies)) }
3. 删除 cookie———— removeCookie()
步骤:从 Storage 中取出完整 cookies ==> 删除指定的 cookie 项 ==> 同步到本地 Storage
function removeCookie(cookieName) { let cookies = wx.getStorageSync('cookies') // try/catch 略过 delete cookies[cookieName] saveCookiesToStorage(Object.assign({}, cookies)) }
四、Cookie 在网络中的传递
本节主要简单实现设计图中的【Request 基础库】部分
如上图所示,Cookie 在网络中的传输主要有四个过程:
-
客户端发起 HTTP 请求
-
服务端响应,并在响应头加上
Set-Cookie
,客户端接受并解析保存 -
下一次客户端发起 HTTP 请求,在请求头加上
Cookie
-
服务端识别出请求头的
Cookie
,作出相应处理
以下是对一个请求的抓包示例:
在小程序中,请求发起有两种方式: HTTP
和 WebSocket
,这里以 HTTP 为例,先对请求 api 进行「封装」:
function requestPro({ url, data, header, method = 'GET' }) { return new Promise((resolve, reject) => { wx.request({ url, data, header: Object.assign({}, { 'Cookie': CookieLib.getCookiesStr() }, header), // 请求头————带上 Cookie success (res) { let { data : resData, header, statusCode } = res let setCookieStr = header['Set-Cookie'] || header['set-cookie'] || '' CookieLib.setCookieFromHeader(setCookieStr) // 响应头————解析 Set-Cookie resolve(resData) }, fail (err) { reject(err) } }) }) }
如上代码所示,Cookie 在前端侧请求模块中的处理主要有 3 点:
1. 请求携带
步骤:(每次发请求前)从 Storage 中取出完整 cookies ==> 转化为 HTTP 规范的请求头 Cookie 格式 ==> 设置到 Request Header
中
上面代码中的 getCookiesStr()
直接取 cookies 拼接即可,返回示例: cookie1=xxx;cookie2=yyy
。
2. 响应设置
步骤:(每次收到响应后)解析 Response Header
的 Set-Cookie
字段 ==> 转为标准 Cookie 格式 ==> setCookie()
这里处理 Set-Cookie
内容时,有几个点需要留意:- 最基本的格式: Set-Cookie: <cookie-name>=<cookie-value>
- 可能同时包含多个 cookie 字段,以,分割(但需要排除时间值里的,) - 时间格式:Max-Age/Expires (不区分大小写)
具体实现可在文末 Demo 中找到。
3. 编码问题
「Cookie 值编码方式」是容易产生困惑的地方,目前看到的广泛做法都是使用「URL 编码」。
但笔者翻阅 RFC6265 [4] 发现,原始规范中并没有对编码进行指定,比如在第四章 Server Requirements (服务端)中是这样描述:
To maximize compatibility with user agents, servers that wish to store arbitrary data in a cookie-value SHOULD encode that data, for example, using Base64 [RFC4648].
“为了最好的兼容效果,服务端应该对 cookie 值进行编码,例如使用 Base64。”
而在第五章 User Agent Requirements (客户端,也就是浏览器),则是“建议以第四章服务端的实现为准”。
总之规范并没有指定使用「URL 编码」,但基于该编码方案已经深入人心,也就顺其自然成了“默认选择”。那这里也不做例外,浏览器怎么做,咱们小程序也保持一致。
在浏览器中,推荐 cookie 值经过 encode
编码后保存下来,所以直接取到的也是 encode
后的值,所以追加在请求头 Cookie
字段,就不需要 decode
解码了,直接拼接即可(但基础库 API 的 get 操作最终需要进行 decode
解码)。
而对于响应头 Set-Cookie
的值,我们认为后端已经做了 encode
编码,所以前端不需要处理,直接存进 Storage 即可。
五、性能优化(高频读写)
前面实现中每次读写 cookie 都会调用小程序 Storage API(而且是同步的),小程序框架会读写到本地 Storage。对于高频场景,可以将 cookie 在内存中维护一份,读写都直接走「内存层」,有更新才同步到「Storage 层」。
1. 初始化
首先需要在内存中声明一个 _COOKIES
(命名自行 diy),建议在 cookie 基础库中声明,便于统一维护。
2. 读
前面初始化时已经从 Storage 读取一次 cookies,后续 getCookie 就直接读内存的 _COOKIES
即可。
3. 写
写操作直接更新内存,间接更新 Storage。如果有高频写场景,可以考虑做个任务队列进行节流。
六、单元测试
微信官方在 2019 年 5 月推出了「小程序自动化 SDK」 miniprogram-automator
,经过半年多的迭代,目前已基本稳定下来。
在购物小程序场景试用了一下,cookie 相关的用例很快就完成了,简直是开发者的福音:真香!!!
实际项目中,对 cookie 的单元测试可以分为两类:
-
小程序全局范围的 cookie 验证(比如初始化小程序后,有没有种下版本号、访问行为等关键 cookie)
-
cookie 基础库 API 验证(比如 get/set/remove 等各个 API 是否正常工作)
以验证 setCookie()
API 为例:
it('API 验证:setCookie()', async () => { await miniProgram.evaluate(() => { wx.CookieLib.setCookie({ // 调用 API cookie1: 12345, }) }) let { cookies } = await miniProgram.callWxMethod('getStorageSync', 'cookies') expect(cookies['cookie1'].value).toBe(12345) // 期望成功设置 cookie1为12345 })
这里为了方便测试用例调用基础库 API,在小程序启动前,把 Cookie 基础库(CookieLib)挂到了 wx
对象上,实现方式是使用 node 读写文件的 API 去【植入代码】:
fs.appendFileSync('./your_project/app.js', ''\n wx.CookieUtil = require(\'./lib/cookie.js\');\n'')
七、Cookie 安全
Cookie 安全是一个比较大的话题,这里只简单列出和小程序相关的几个点。
path、domin、HttpOnly、Secure、SameSite
小程序中已经做了一些安全措施,比如只能走 HTTPS、合法域名需要管理员到微信后台进行配置、Storage 只能由写入它的小程序中访问,等等。因此 path、domin、HttpOnly、Secure、SameSite
这些字段在小程序环境下的价值没有浏览器环境大,本例中没有使用(懒..),而实际业务场景可以按自身情况决定是否要使用。
白名单机制
1 前端维护(大小/数量)
通常浏览器保持的 Cookie 数据不超过 4k,部分浏览器限制同一站点最多 cookie 数为 20 个。
如果业务庞大的话,建议在 Cookie 基础库做一套「白名单」机制,在白名单内才可以写入,以此防止“非法写入”或“内容超大导致信息丢失”的问题。
2 后台维护(网关白名单)
同样的,建议从网关层面,建立一个“可信 cookie”白名单,自动过滤请求中的“非法 cookie”字段。
前端防篡改
小程序前端更多是防“误改”————即在操作 Cookie 过程中,发生了意料之外的修改。通常发生在 JS “引用拷贝”特性上,比如前面提到的内存维护一个 _Cookies
,如果有一个 API getAllCookies()
直接将这份内存版 cookies 暴露出去,对象引用容易被连带修改。所以 cookie 基础库需要控制暴露 API 的能力范围,并对取值进行“深拷贝”。
Session
Session 机制将用户状态放在了服务端维护,具备更好的安全性,而且目前各种后端对于 session 的存储和同步都有很成熟的技术方案,有条件的业务应以 Session 为主做会话保持。
指纹上报
用户访问时生成设备指纹并上报(通常是登录/结算等环节),业务后台配合风控系统,遇到异常请求时下发验证环节。
八、完整小程序实现 Demo
代码片段:https://developers.weixin.qq.com/s/x4sFASmh7xdq
九、小结
本文先解析了浏览器的 Cookie 机制 运作原理,然后使用「数据缓存」和「网络」能力,以 公共基础库 的形式,在小程序中实现了一套 Cookie 方案。希望对大家有所帮助。
十、相关链接
-
RFC6265(HTTP 状态管理机制规范) [5]
-
HTTP Cookies Explained [6]
-
小程序数据缓存 Storage API [7]
-
小程序网络 Network API [8]
-
小程序自动化 SDK [9]
-
小程序实现 Demo [10]
参考资料
数据缓存 Storage: https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorageSync.html
网络 Network: https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html
在这里: https://developers.weixin.qq.com/s/x4sFASmh7xdq
RFC6265: https://tools.ietf.org/html/rfc6265
RFC6265(HTTP 状态管理机制规范): https://tools.ietf.org/html/rfc6265
HTTP Cookies Explained: https://humanwhocodes.com/blog/2009/05/05/http-cookies-explained/
小程序数据缓存 Storage API: https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorageSync.html
小程序网络 Network API: https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html
小程序自动化 SDK: https://developers.weixin.qq.com/miniprogram/dev/devtools/auto/
小程序实现 Demo: https://developers.weixin.qq.com/s/x4sFASmh7xdq
:heart:看完三件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
-
点赞,让更多的人也能看到介绍内容(收藏不点赞,都是耍流氓-_-)
-
关注公众号“前端劝退师”,不定期分享原创知识。
-
也看看其他文章
也可以来我的 GitHub
博客里拿所有文章的源文件:
前端劝退指南 :https://github.com/roger-hiro/BlogFN
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C#图解教程
索利斯 (Daniel M.Solis) / 姚琪琳、苏林、朱晔 / 人民邮电出版社 / 2013-7-1 / CNY 89.00
本书是广受赞誉的C# 图解教程的最新版本。作者在本书中创造了一种全新的可视化叙述方式,以图文并茂的形式、朴实简洁的文字,并辅以大量表格和代码示例,全面、直观地阐述了C# 语言的各种特性。新版本除了精心修订旧版内容外,还全面涵盖了C# 5.0 的新增特性,比如异步编程、调用者信息、case 表达式、带参数的泛型构造函数、支持null 类型运算等。通过本书,读者能够快速、深入理解C#,为自己的编程生涯......一起来看看 《C#图解教程》 这本书的介绍吧!