1. 前言

最近神策数据 Web JS SDK 默认的数据上报方式由原来的 image 改成了 beacon。 其实 image 只是数据上报方式中的一种,它是通过向服务端发送图片请求来实现数据传输的,还有另外两种向服务端发送数据的方式:ajax 和 beacon。

下面针对神策数据 Web JS SDK 数据上报方式进行详细的介绍,希望能给大家提供一些参考。

2. image

image 方式是通过将采集的数据拼接在图片请求的后面,向服务端请求一个 1*1 px 大小的图片实现的,设置它的 src 属性就可以发送数据。这种方式简单且天然可跨域,又兼容所有浏览器,没有阻塞问题,是目前比较受欢迎的前端数据上报方式。但由于是 get 请求,对上报的数据量有一定的限制,一般为 2~8 kb。代码示例如下:

var img = new Image();
img.width = 1;
img.height = 1;
img.src = '/sa.gif?project=default&data=xxx';

2.1. crossOrigin 属性

HTML5 给 <img> 标签新增加了一个 crossOrigin 属性,这个属性决定了图片获取过程中是否开启跨域功能。并且如果设置了 crossOrigin 这个属性,image 请求中将不带 cookie。代码示例如下:

  1. 浏览器端设置:

    var img = new Image();
    img.crossOrigin = "anonymous
  2. 服务端设置:

    set('Access-Control-Allow-Origin''*');

设置了 crossOrigin = “anonymous” 的结果如图 2-1 所示:

图 2-1 设置了 crossOrigin = “anonymous”

未设置 crossOrigin = “anonymous” 的结果如图 2-2 所示:

图 2-2 未设置 crossOrigin = “anonymous”

如果只是在客户端设置了 crossOrigin,服务端没有设置跨域,请求图片将失败!

如果 canvas 加载了不同域的图片,就会变成被污染的画布,此时为了安全考虑,就不能再使用 getImageData()、toBlob()、toDataURL() 方法了。使用 crossOrigin 时,可以设置资源按照 CORS 来请求,这样 canvas 就不会识别出资源的跨域问题。

2.2. image 方式发送

Web JS SDK 将用户在 Web 页面的行为比如页面浏览、元素点击、视区停留等上报给服务端。先将采集数据用 JSON 编码为字符串,再通过拼接 data 参数传递给服务端。发送的方式是 get,最后由服务端统一处理。在 Network 中可以看到一个带有 sa 字段的 gif 请求,就是神策 Web JS SDK 发送的 image 请求,如图 2-3 所示:

图 2-3 image 方式发送数据

3. ajax

在 JavaScript 中,XMLHttpRequest 是客户端的一个 API,它为浏览器与服务端通信提供了一个便捷通道。现代浏览器都支持 XMLHttpRequest API,如 IE 7+、Firefox、Chrome、Safari 和 Opera 等。这种方式发送数据相对来说比较简单,使用 post 方式可以发送大量的数据。默认发送方式是异步,不会阻塞页面,但会占用一定的客户端资源,且需要特殊处理跨域限制。代码示例如下:

function createXHR(){
    return window.XMLHttpRequest?
    new XMLHttpRequest():
    new ActiveXObject("Microsoft.XMLHTTP");
}
var xhr = createXHR();  //实例化 XMLHttpRequest 对象
xhr.open ("post", url , true);  //建立连接
xhr.send(data);  //发送请求

3.1. credentials 属性

现代的 XMLHttpRequest 支持跨域请求,且跨域请求默认不携带 cookie。credentials 默认值是 false,表示默认情况下不带 cookie。如果跨域请求要带 cookie 的话,需要满足下面三个条件:

  1. 浏览器端设置:

    xhr.credentials=true;
  2. 服务端设置:

    set('Access-Control-Allow-Credentials'true)
    //不能设为星号,必须指定明确的、与请求网页一致的域名。
    set('Access-Control-Allow-Origin', getRequestHeader('origin'))
  3. 浏览器设置中,没有关闭第三方 cookie 功能。

3.2. ajax 方式发送

将采集数据用 JSON 编码为字符串,再通过 XMLHttpRequest 的 send 方法发送 data 给服务端,发送的方式是 post,最后由服务端统一处理。在 Network 中可以看到一个带有 sa 字段的 xhr 请求,就是神策 Web JS SDK 发送的 ajax 请求,如图 3-1 所示:

图 3-1 ajax 方式发送数据

4. beacon

navigator.sendBeacon 是一个比较新的 API,它是指浏览器通过异步的 post 方式发送数据到服务端。该方法在页面跳转、刷新、关闭页面时发送请求,可以保证数据发送不易丢失,浏览器会对其进行调度以保证数据有效送达,并且不会阻塞页面的加载或卸载。不受跨域限制,浏览器兼容性较好,可以支持除 IE 之外的几乎所有浏览器。具体使用方法如下:

navigator.sendBeacon(url, data);
  • url:data 将要被发送到的网络地址;
  • data:将要发送的 ArrayBufferView、Blob、DOMString 或者 FormData  类型的数据;
  • 返回值:当用户代理成功把数据加入传输队列时,sendBeacon() 方法将会返回 true,否则返回 false。

sendBeacon 允许开发者通过 post 请求发送少量数据到服务端,它的特点很明显:

  • 在浏览器空闲的时候异步发送数据,不影响页面诸如 JS、CSS Animation 等执行;
  • 页面在 unload 状态下,也会异步发送数据,不阻塞页面刷新和跳转等操作;
  • 能够被客户端优化发送,尤其在 Mobile 环境下,可以将 beacon 请求合并到其他请求上一起处理;
  • 只能判断出是否放入浏览器任务队列,不能判断是否发送成功。

4.1. 兼容性

如图 4-1 可见当前浏览器对 sendBeacon 的支持情况:

图 4-1 sendBeacon 兼容性 (图片来源于 MDN

虽然现代浏览器对 sendBeacon 的支持很好,但也需要做兼容性处理。Web JS SDK 会判断用户当前设备是否支持 sendBeacon,如果不支持,就会走 image 方式将数据发送出去。比如初始化代码配置了 send_type:’beacon’,而用户的浏览器不支持 navigator.sendBeacon 方法,则 Web JS SDK 会自动转而使用 image 方式发送数据。代码处理方法如下:

if (sendType === 'beacon' && typeof navigator.sendBeacon !== "function") {
    sendType = 'image';
}

注意:在 iOS 11.1-12 上 sendBeacon 发送请求到一个之前未访问过的域名会失败,iOS 13 修正了这个问题。详情可见文档:https://caniuse.com/?search=sendbeacon

4.2. 发送数据大小限制

目前没有给出具体的发送数据大小限制标准,不过有人做了下面的测试:当数据是 65536 字符长度时,异步请求进入浏览器发送队列失败,代表数据大小是有限制,不一样的浏览器应该有所差异。测试代码如下:

var url = 'http://jsfiddle.net?sendbeacon';
var n = 65536; // sendBeacon limit for Chrome v40 on Windows (2^16)
var data = new Array(n+1).join('X'); // generate string of length n
if(!navigator.sendBeacon(url, data))
{
   alert('data limit reached');
}

具体参考:http://www.voidcn.com/article/p-okdxpzox-bwh.html

4.3. beacon 方式发送

将采集数据用 JSON 编码为字符串,再通过 XMLHttpRequest 的 send 方法发送 data 给服务端,发送的方法是 post,最后由服务端统一处理。在 Network 中可以看到一个带有 sa 字段的 ping 请求,就是神策 Web JS SDK 发送的 beacon 请求,如图 4-2 所示:

图 4-2 beacon 方式发送数据

5. 演进过程

上面我们分别对三种数据上报方式进行了介绍,下面从多个角度来分析 image、ajax、sendBeacon 的优缺点,如表 5-1 所示:

方式

https 下使用 http 请求

跨域

关闭页面发数据

window.onload 问题
请求方式
浏览器兼容性
cookie 携带
image 部分支持 完全支持 效果差 部分支持 仅 get 完全支持 同域名携带
ajax 不支持 部分支持 效果很差 支持 基本支持 可配置
sendBeacon 不支持 支持 效果好 支持 仅 post 部分支持 同域名携带

表 5-1 发送方式对比

5.1. 第一版

神策数据 Web JS SDK 第一版发送方式选择的是 image,采用 image 方式的优点如下:

  1. 使用方式简单;
  2. 天然可跨域;
  3. 浏览器兼容性好。

当然,image 方式也存在缺点:

  1. 关闭页面时发送数据效果较差;
  2. 对上报的数据量有一定的限制,一般为 2~8 kb;
  3. 很容易被一些浏览器图片加载器拦截,之前我们有过相关的介绍:https://mp.weixin.qq.com/s/EEoRjpgafJFbk5T6BtXzGg

5.2. 第二版

由于 image 方式关闭页面时发送数据效果较差,因此 SDK 增加了缓存模式,发送方式选择了批量发送。优点如下:

  1. 当数据产生后会先将数据存储在 localStorage 中,达到发送条件后才会把存储在 localStorage 中的数据合并发送出去;
  2. 如果数据发送不成功,会将发送的数据保存起来,满足发送条件后,与之后的数据一起尝试发送。

当然,缓存模式也存在缺点:

  1. localstorage 遵循浏览器同源策略,即使子域名 localstorage 也不能共享;
  2. 可能会出现数据延迟上报和跨子域数据不发送的现象。

5.3. 第三版

由于缓存模式中子域名 localstorage 不能共享,因此选择了 beacon 作为新的发送方式,通过异步的 post 方式发送数据到服务端。优点如下:

  1. 不受跨域限制;
  2. 页面刷新和跳转时发送数据表现较好。

当然,beacon 方式也存在缺点:不兼容 IE 等部分浏览器。

为了解决这一问题,在不支持 navigator.sendBeacon 方法的浏览器中,Web JS SDK 会自动转而使用 image 方式发送数据。

6. 总结

通过前面的介绍可以看出,没有一种数据上报方式在各个方面都是优秀的,都有一定的优缺点。因此,面对不同的需求时,我们要选择不同的方式来上报数据:

  • 如果发送数据量较小,比如采集用户在 Web 页面的页面浏览、元素点击、视区停留等行为事件,采用 image 方式上报给服务端更合适;
  • 如果发送数据量较大,比如获取后端所有数据用于前端渲染,ajax 方式更合适;
  • 如果需要进行精确统计,比如点击支付按钮、视频播放时长、页面跳转或关闭等行为时,选择 beacon 方式能最大程度保证数据成功率。

对于神策数据 Web JS SDK 而言,我们为了最大程度上保证数据的可靠性,优先使用 beacon 方式来上报数据。在浏览器不支持 beacon 的情况下,Web JS SDK 自动转而使用 image 方式来上报。