Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zmmbreeze opened this issue Sep 16, 2016 · 0 comments
Open

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

zmmbreeze opened this issue Sep 16, 2016 · 0 comments

Comments

@zmmbreeze
Copy link
Owner

zmmbreeze commented Sep 16, 2016

作者前段时间在做类似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。如果真的有很多数据需要发送,那么我们可以合并短请求、拆分长请求。

资料:


我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=t0ien3e6ws9r

@zmmbreeze zmmbreeze changed the title GA代码小技巧之Beacon请求 GA源代码里的小技巧之Beacon请求 Sep 24, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant