09 April 2014

1. 目标

事情是这个样子的,要统计网站的用户行为(比如点击),用户每次在页面上进行点击操作,js就发送一条http请求(我们称之为pingback)到服务器进行本次点击的记录。

发送pingback请求的实现方式是(new Image()).src="包含用户行为数据的url",服务器在收到请求(把url记录到access.log)之后直接返回204 No Content响应。

现在有这样一种情况,用户的点击行为导致当前页面进行了刷新/跳转,img.src请求还未发送成功之前旧页面已经unload,此时旧页面中的img.src请求会被abort掉,导致本次点击行为的pingback未能发送成功。由于pingback是产品kpi的衡量指标,现在希望能尽量保证pingback不丢失。

2. 方案与问题

pingback是在页面端丢失的,要保证不丢失只能依靠js了。目前已经在用的方案是,监听跳转链接a的click事件

function clickHandler(event){
    var a = this;
    event.preventDefault();//阻止浏览器跳转
    sendPingback(event);//发送pinback请求
    setTimeout(function(){
        location.href = a.getAttribute("href"); //300ms后js触发跳转
    }, 300);
}

上面的实现方式就是让页面在300毫秒后再进行进行跳转,这样pingback基本不会受页面跳转的影响。

但是上面的方式用户体验较差,人为延迟了新页面的打开时间。

另外一种方式就是先把pingback参数存储到cookie/localStorage,发送成功之后再删除。这就涉及到如何判断pingback是否发送成功。

img对象有load事件,在加载完成图片元素之后触发,正好可以在img.onload事件处理器中进行cookie的清理。所以只要加几行代码就能实现pingback不丢失的目标了。

但是实际上pingback服务器对于img.src请求返回的响应不是200 Ok,而是204 No Content。204响应表示服务器收到浏览器的请求,并且成功处理OK,但是没有响应body返回给请求。我们想当然的以为即便服务器返回204响应,img.src请求的onload也能被触发。但实际在chrome浏览器上204响应导致img上的onerror被触发。

搜索了相关文档,发现MDN上对此有过描述:

error with img elements

For <img> elements, an error event is triggered in the following cases:

* Network error (DNS error, for instance)
* Incorrect Content-Type HTTP header (doesn't start with "image/")
* Content-Length HTTP header is 0
* HTTP response code is 204 (No content)

Even though this is not standardized, this behavior is consistent across web browsers.

Nokia phones (Symbian OS) running a WebKit browser do not fire any events when the Content-Length HTTP header is 0. In this case, no event is fired (neither error nor load).

尽管不是标准,但是204响应触发img的error事件在跨浏览器上都是一致的。这样,img.src和204响应就导致我们无法区分pingback究竟是否发送成功。

其实想想,204响应确实应该触发img的error事件,因为img元素没有成功加载到图片数据。我们认为应该触发onload的事件是因为我们是从服务器的角度思考的,我们考虑的是服务器有没有成功收到请求。

利用img.src可以发送http请求,但是发送http请求不是img.src的真正用意。同样,用script.src去请求jsonp格式的接口数据也不是script元素的最初设计用途。但是这些歪门邪道的技术都是利用了img/script等DOM元素能发跨域请求的特性。

如果不考虑低版本浏览器,用带withCredential的xhr对象就能实现区分发送成功和失败的情形了。只是IE678不支持cors的xhr。

3. img.src的其它用途

另外再写一个img.src和204响应结合的一个应用场景:实现DNS预解析和TCP链接的建立!

Youtobe的前端工程师说他们使用img.src请求来实现preloading video connection。

实现方式是在html的head部分有下面一段script代码:

var img = new Image();
img.src = videoConnectionUrl;

这样做的结果是:

  • Resolves DNS while page is rendering, before it is needed
  • Maintains an open connection for later use

看,又是一个剑走偏锋的用途!但是它比正规的dns-prefetch更强大,而且不存在低版本浏览器不支持的问题。



blog comments powered by Disqus