字节小程序
开发者社区
小程序小游戏
登录
技术新风向丨挖掘藏在小程序 Cookie 里的秘密

技术新风向丨挖掘藏在小程序 Cookie 里的秘密

2002浏览作者: falafala

Cookie 可不是一般的“小甜饼”

Cookie 直译过来其实是“小甜饼”。但是在互联网的世界里,Cookie 是 Web 开发中一种常用的数据存储、会话跟踪技术。


Cookie 机制在小程序开发中也有很大的需求,然而此前多数主流小程序平台并不支持 Cookie 机制,导致开发者们不得不通过小程序本地缓存的方式来模拟 Cookie 的效果。常见使用手动管理 Cookie 或者第三方库的形式来进行小程序端 Cookie 处理,但是这种方式不够完美,也会存在诸多问题

手动管理 Cookie

使用第三方库



下面为大家详细地介绍以下这两种流行方式!

手动管理 Cookie

使用小程序数据缓存能力模拟 Cookie,只能满足基本需要,开发者负担较重。

常见的操作是,开发者封装 request 请求,从接口响应中取出需要保存的值,保存在本地缓存 storage 中,每次接口请求时,再从 storage 中读取相关数据添加进请求 header 或 body 中,以此模拟 Cookie 的效果。

// 从接口响应中取出并保存 cookie 值: 
tt.request({
  url: "https://xxx.com/login",
  data: { /* ... */ },
  success(res) {
    res.header["Set-Cookie"] !== undefined && tt.setStorageSync("cookie", res.header["Set-Cookie"]);
  },
  fail(res) {
    console.log("调用失败", res.errMsg);
  },
});

// 请求的时候读缓存数据带上 cookie 信息
const header = { 
    'content-type': 'application/json'
};
const cookie = tt.getStorageSync("cookie");
if(url !== 'login' && cookie){
    header['cookie'] = cookie;
}
tt.request({
    url: "https://xxx.com/request",
    data: { /* ... */ },
    header,
});
  1. 增加开发者手动维护负担
    • 需要前端开发者手动维护 Cookie;
    • 需要手动区分 domain、path;
      • storage 是全局存储,如果需要增加对 domain、path 作用域的支持,需要前端增加维护代码,进一步增加开发者负担;
    • 需要手动维护过期时间;
      • 支持过期时间处理也需要前端增加维护代码,同样增加开发者负担。

2.降低小程序性能

    • 如果本地缓存数据分多个变量存储,读写数据时,将出现多次 getStorageSync、 setStorageSync 调用,该方法为同步方法,需要和客户端进行数据通信,频繁调用对小程序性能有一定影响
// 连续读取
const param1 = tt.getStorageSync("param1") || "";
const param2 = tt.getStorageSync("param2") || "";
const param3 = tt.getStorageSync("param3") || "";
const param4 = tt.getStorageSync("param4") || "";
const param5 = tt.getStorageSync("param5") || "";

// 发送请求
tt.request({
    data: {
        param1,
        param2,
        // ...
    }
});

b.在小程序开发工具的 trace 分析面板中可以看到这样连续调用的效果;


      • 接口请求前为了设置 Cookie,需要连续多次从 storage 中同步地读取数据,getStorageSync 一次的耗时可能仅 1~3 ms,但累积起来可能达数十毫秒。特别当开发者将其封装为基础方法,在高频接口中使用时,将带来更多的性能损耗,且很容易被开发者忽视;
      • Cookie 的更新类似,需要连续调用 setStorageSync 写入数据,同样带来性能损耗。

3.一些非 tt.request 请求无法处理

    • 小程序中,除了 tt.request ,还有 videolive-player 等原生组件,以及 audio tt.previewImage 这类 API 都会发送网络请求。而这类请求受限于小程序能力开放程度,开发者无法修改其中请求参数,也就无法设置 Cookie;
// 视频请求想要标记用户这么办?
<video src="https://xxx.com/video.mp4"></video>


使用第三方库

社区中有一些第三方库支持小程序端的 Cookie 机制,使用虽然方便,但存在性能、安全性、兼容性等问题。

weapp-cookie 为例,通过劫持小程序的 tt.request、tt.uploadFile、tt.downloadFile 等 API ,增加自动解析和添加 Cookie 的操作,免去了开发者自行管理 Cookie 的负担。

使用第三方库相比手动管理有一定的优势:

  1. 只需要引入 npm 包即可使用,减轻了开发者手动维护的成本;
  2. 提供了 API 获取、设置 Cookie;
  3. 提供了 domain/path 作用域、过期等支持;

但第三方库的 Cookie 实现,最终还是基于小程序本地缓存,依然存在诸多不足:

  1. 同样存在频繁读取 storage 的性能问题;
  2. 可能与小程序的自定义 Cookie 冲突;
    • 小程序 tt.request API 支持自定义设置 Cookie,但是当使用第三方库时,同名 Cookie 可能存在冲突,被第三方库覆盖掉,导致自定义 Cookie 无效。


3.无法支持 video、audio、live-player、tt.previewImage 等网络请求方式;

4.对于 uni-app、Taro 等跨平台框架可能需要第三方库进行兼容适配,否则无法使用;

5.存在安全问题,如果有恶意的第三方框架可以获取/修改 Cookie;

6.引入第三方库将增大小程序包体积。

官方小程序的“Cookie” 它不香吗?

考虑到开发者的迫切需求,以及现有使用本地缓存方式的种种弊端,字节小程序在基础库 2.45.0 版本开始从框架层面提供了 Cookie 的支持。


字节小程序支持服务端在 tt.request 的响应中,使用 HTTP 首部中的 Set-Cookie 字段设置 Cookie。框架侧负责 Cookie 解析、存储、匹配和发送,处理逻辑遵循 RFC6265 规范。小程序之间、小程序与宿主 Cookie 相互隔离,同时支持手动设置 Cookie。

更多介绍可查看开发者文档:小程序 Cookie 机制

小程序 Cookie 机制核心优势凸显

较市面上的两种流行方式,字节小程序官方提供的 Cookie 机制更具备以下核心优势:


功能相对丰富完善

相比开发者使用 storage 模拟实现的 Cookie,小程序 Cookie 遵循 RFC6265 规范,提供的功能比较完善和丰富:

  • 支持 domain/path 作用域
  • 支持 Max-Age/Expires 过期机制
    • 当 Max-Age 和 Expires 同时存在时,Max-Age 优先级更高
  • 兼容小程序 tt.request 的自定义 Cookie
  • 兼容各类第三方框架
    • 小程序 Cookie 是从平台基础能力提供的支持,即便使用 uni-app、Taro 等开发框架,只要能在小程序配置文件中开启,即可使用小程序 Cookie。

性能更优

小程序 Cookie 由框架 SDK 管理,读写速度要比前端 storage 模拟的方式快得多,根据数据来看,小程序 Cookie 的读取速度 < 1ms,写入速度在 7ms 左右。

再对比看看开发者自己读取数据的情况,性能优势相当明显:


更加安全

  • 不会被前端 js 获取,防止被恶意三方框架读取或篡改
  • 支持 secure 属性,保证安全传输
// Set-Cookie: "id=1; secure;"

// http 请求将不携带 Cookie
// Cookie: ''
tt.request({
    url: "http:/xxx.com"
});

// https 请求才携带 Cookie
// Cookie: 'id=1'
tt.request({
    url: "https:/xxx.com"
});
  • 隔绝跨域设置 Cookie
// 跨域 Set-Cookie 不会被存储
url: "https://a.com/"
Set-Cookie: "id1=1; domain=b.com"

提供了一些原生组件/API中网络请求 Cookie 的支持

使用小程序 Cookie 后,原生组件/API(Video、Audio、Live-Player、tt.previewImage)也会自动携带cookie,可以解决资源请求难以标识用户问题。这在框架提供支持之前,开发者是难以做到的。

Web 应用中,一些图片、视频等资源类的请求也会使用 Cookie 来标识用户信息,以此提供更加精准和个性化的服务(如权限、个性化推荐等)。

在小程序不支持 Cookie 的时候,经常看到开发者要通过事件监听用户的操作,在事件回调中再通过额外的 tt.request 发送信息。

// ttml
<video id="id1" src="https://xxx.com/video.mp4" bindplay="playVideo"></video>

// js 
playVideo(e) {
    const id = e.target.id;
    const userId = tt.getStorageSync('useId');
    tt.request({
        url: 'xxx',
        data: {
            userId,
            videoId: id,
        }
    });
}

现在有了小程序 Cookie,完全不需要这么麻烦,video 组件在请求视频等资源时也能自动添加上 Cookie,服务端可以据此校验用户身份,前端不需要再写过多额外代码,还能减少 tt.request 的次数,减少网络请求,降低服务端压力。

减轻了小程序前端开发者负担

完整的 Cookie 实现是比较复杂的(参见 RFC6265),开发者们使用 storage 模拟的方式,往往也只是实现一些简单的基本功能。如果有更丰富的需求怎么办?domain/path 作用域要不要支持?过期机制要不要支持?这些都是开发者的负担。

即便封装或使用一些相关的三方库,也只是一定程度上减轻了开发者的负担,但随之又带来的性能问题、原生组件网络请求的支持问题、安全性问题、三方框架的兼容适配问题等等,依然不够令人满意。

使用小程序 Cookie 就完全不需要考虑这么多,前端开发者无需写任何代码,服务端的开发者也可以达到类似 Web 开发中的体验。

再不会用小程序 Cookie 你就 OUT 了!


官方小程序的 Cookie 这么香,据说还有80%的人不会用.....别说我没有教过你这个“小甜饼”的正确使用方法!这届开发者已经在背着你偷偷学习了!


如果你是前端:

只需要在 app.json 中开启配置即可:

{
  "cookie": {
    "enableStore": true // true 开启小程序 Cookie 机制。默认 false
  }
}

如果你需要发起请求时添加额外的 Cookie:

tt.request API 支持设置请求 header 的部分字段,可以添加自定义的 Cookie,小程序框架侧会将自定义 Cookie 与本地保存的 Cookie 合并,当出现冲突时,自定义 Cookie 会覆盖本地 Cookie 同名 key 部分:

// 本地 Cookie
// key1=value1; key2=value2

// 自定义 Cookie
tt.request({
  url: "https://xxx.com/request",
  header: {
    "content-type": "application/json",
    cookie: "key1=value3", // 此处添加cookie
  },
  success(res) {
    console.log("调用成功", res.data);
  },
});

// 发送出的 Cookie
// key1=value3; key2=value2

如果你是服务端:

前端配置中开启后,开发者的服务端就可以通过 HTTP 请求响应的Set-Cookie字段设置 Cookie,小程序框架会帮你保存和管理,开发者前端无需再费心处理了。

更多接入细节可查看开发者文档


看到这里,我猜你还想了解:

Q1:小程序 Cookie 如何做隔离的?

A1:不同小程序之间隔离 Cookie;宿主账号切换会清空 Cookie 数据。

Q2:Cookie 对数量、大小是否有限制?

A2:每个小程序每个域名下最多 50 个 Cookie总 Cookie 不超过1000个,如超过限制使用 LRU 算法淘汰。每个 Cookie 大小不超过 4K,如超过则服务端此次 Set-Cookie 操作无效。

Q3:是否支持跨域 Set-Cookie?

A3:不支持。

只有真正在乎你的人,才会把“小程序 Cookie 机制”的秘密告诉你!还不快去做聪明人的选择!



最后一次编辑于 2022年05月09日
加载中