GA源代码里的小技巧之Beacon请求

GA源代码里的小技巧之Beacon请求

作者前段时间在做类似Google Analytics(以下简称GA)的第三方监控脚本。所以对GA的前端代码做过调研,对GA的压缩后代码做了一定程度上的人肉美化。这里美化的是analytics.js的j41版本,本文提到的小技巧也是基于这个版本的js。

智能Beacon

GA监控脚本一般都放在开发者的网页上。域名往往和Google不一样,这样发送请求到Google服务器的时候会涉及到跨域。普通的Ajax请求是做不到的,通常称这种请求为beacon或是ping。业内常用的一个方案是发送一个图片请求(GET方式),将请求参数放在图片请求的地址后面。例如:

https://www.google-analytics.com/r/collect?v=1&_v=j46&a=134081920&t=pageview&_s=1&dl=http%3A%2F%2Fzencode.in%2F&ul=zh-cn&de=UTF-8&dt=MZhou%27s%20blog%20-%20Taste%20of%20life.&sd=24-bit&sr=1280x800&vp=481x676&je=0&fl=22.0%20r0&_utma=9582782.1438874051.1425021219.1431473728.1471631422.8&_utmz=9582782.1471631422.8.1.utmcsr%3D(direct)%7Cutmccn%3D(direct)%7Cutmcmd%3D(none)&_utmht=1473782171819&_u=QACCAAABI~&jid=633265686&cid=1438874051.1425021219&tid=UA-36422454-1&_r=1&gtm=GTM-MP42BH&z=300649187

因为通常第三方监控请求没有很强的安全要求(不会发送密码、密钥之类的信息),所以使用图片请求将请求参数放在地址里面也是OK的。示例代码如下:

function imgPing(url, callback) {
    var key = '__SOME_RANDOM_KEY__' + (+new Date());
    var img = new Image();
    window[key] = img;
    img.onload = img.onerror = img.onabort = function () {
        img.onload = img.onerror = img.onabort = null;
        window[key] = null;
        img = null;
        if (callback) {
            callback();
        }
    };
    img.src = concatUrl;
    return true;
}

图片请求是GET请求,参数放在URL地址中,而URL地址的长度是有一定限制的。规范对URL长度并没有要求,但是浏览器、服务器、代理服务器都URL对长度有要求。例如:IE6、7、8(部分)的URL长度不能超过2083的字符长度,URL中的path部分不能超过2048。这就导致有些请求会发送不完全。

为了解决这个问题可以使用XMLHttpRequest(简称XHR)来发送跨域POST请求。当然这需要浏览器的跨域支持。发送POST请求时,参数都放在请求的payload中,不会受到URL长度所限制。但因为是POST请求,所以需要协议头部比GET方法多一点点,消耗也稍高。示例代码如下:

function xhrPing(url, params, callback) {
    if (hasCors()) {
        return;
    }

    var xhr = new window.XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.withCredentials = true;
    xhr.setRequestHeader('Content-Type', 'text/plain');
    if (callback) {
        xhr.onreadystatechange = function () {
            if (xhr.readyState !== 4) {
                return;
            }

            var status = xhr.status;
            var isSuccess = status >= 200 && status < 400;
            var error = null;
            if (!isSuccess) {
                error = new Error();
            }
            callback(error);
        };
    }
    xhr.send(params);
};

除了这两种方法之外,浏览器还提供了一个标准的用于发送beacon的方法:[navigator.sendBeacon](http://)。这个方法本质上和跨域的XHR请求没有多大区别,但是sendBeacon方法能够确保在页面关闭的时候还能发送成功。这也是它的最大优势。示例代码如下:

function beaconPing(url, params) {
    if (hasSendBeacon()) {
        return window.navigator.sendBeacon(url, params);
    }
    else {
        return false;
    }
};

sendBeacon出现之前,为了能够在页面关闭时发送beacon,常用的方法是两种:

  1. 先发送一个图片的beacon,然后死循环200ms左右来提高beacon请求发送成功的概率
  2. 发送同步的XHR请求,确保页面要等到XHR请求结束后才能关闭。不过同步XHR已经被W3C标准定义为不推荐使用了: Synchronous XMLHttpRequest outside of workers is in the process of being removed from the web platform as it has detrimental effects to the end user's experience. (This is a long process that takes many years.) Developers must not pass false for the async argument when entry settings object's global object is a Window object. User agents are strongly encouraged to warn about such usage in developer tools and may experiment with throwing an InvalidAccessError exception when it occurs.

综合上面的讨论给出如下的对比表格:

方法

优点

缺点

图片请求

1. GET请求头部少,快2. 支持广

1. URL长度限制2. 需要延迟关闭才能用于unload发送

sendBeacon

1. unload时更能确保成功2. 支持发送更多数据

1. POST请求消耗多2. 旧浏览器支持少

XHR CORS

支持发送更多数据

1. POST请求消耗多2. 旧浏览器支持少3. unload时不能使用

如果没有指定发送方法,那么GA会在URL字符长度不超长时使用图片beacon的方式发送。如果超过了则尝试使用sendBeacon方法发送beacon请求,如果不支持sendBeacon则会采用跨域XHR发送。如果跨域XHR不支持则最后fallback到图片发送。实际代码如下:

var smartPing = function(api, param, callback) {
    callback = callback || noop;
    if (2036 >= param.length) {
        imgPing(api, param, callback);
    } else if (8192 >= param.length) {
        beaconPing(api, param, callback) || xhrPing(api, param, callback) || imgPing(api, param, callback);
    } else {
        errorPing('len', param.length);
        throw new OverLengthError(param.length);
    }
};

当然GA的做法并非最优,因为非IE6、7的浏览器图片请求发送的数据可以超过2083。如果真的有很多数据需要发送,那么我们可以合并短请求、拆分长请求。

资料:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT大咖说

MongoDB技术分享:WiredTiger存储引擎

内容来源:2018 年 10 月 27 日,MongoDB中文社区联席主席郭远威在“2018年MongoDB中文社区 广州大会”进行《WiredTiger存储引...

21220
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第二十七天 SVN使用【悟空教程】

为保障团队开发过程中人员沟通各方面成本的降低,必须使用一种有效的方式减少沟通环节,提高开发效率,对资源的共享进行管理。

20110
来自专栏安智客

GP规范中定义的四种SE访问控制架构

GP规范给人的感觉好像有点晦涩难懂,由于是规范,所以比较抽象,而且GP这个组织的专家们来自世界各地,大家都用英语文档交流,所以不同的文档风格不同,难免大家阅读起...

20820
来自专栏小勇DW3

线上测试环境搭建过程记录

3.安装完以后  会在 /usr/java/latest 下有对应的 jdk 版本

21910
来自专栏云计算教程系列

如何将Ubuntu从16.04升级到18.04

Ubuntu 18.04是一个长期支持(LTS)版本,LTS 版本每两年发布一次,而 Ubuntu 18.04 是自 2016 年以来的第一个长期支持版本。Ub...

3.2K40
来自专栏无题

分布式Session一致性解决方案

在分布式架构或微服务架构下,必须保证一个应用服务器上保存Session后,其它应用服务器可以同步或共享这个Session Web应用在单机部署的情况下,Ses...

44060
来自专栏deepcc

IE=edge,chrome=1的META信息详解

42280
来自专栏程序员宝库

Laravel 开发 RESTful API 的一些心得

最近用 Laravel 写了一段时间的 API,总结一下自己的心得吧。 Start API开发我们可以看到,有些网站用token验证身份,有些用OAuth2.0...

54490
来自专栏北京马哥教育

Redis数据库安全手册

Redis是一个 高性能的key-value数据库,这两年可谓火的不行。而Redis的流行也带来一系列安全问题,不少攻击者都通过Redis发起攻击。本文将讲解...

35960
来自专栏小特工作室

微信小程序开发填坑指南V1

近期用了一星期的时间,开发了一个小程序。小程序名称是:小特Jarvis,取自钢铁侠的管家。

21760

扫码关注云+社区

领取腾讯云代金券