基于设备检测的移动设备网页优化

背景

前段时间遇到一个Bug,一群人纠结了很久,最后终于找到了问题的所在,在此记录下来算是总结吧!

Bug描述:在同一个git branch的两个不同手机产品Phone1和Phone2,他们的Browser模块拥有相同的代码。但是这两个产品拥有不同UA Header。

在使用Browser访问网址:http://m.o2.de/goto/tbm-bild 时,Phone1能够正常显示网页,但是Phone2却显示出错信息(Please enable JavaScript in your browsers settings.)。

UA Header:全名是Useragent Header。拿手机举例,UA Header用来标识手机的型号(例如:Nokia 204)。在使用手机Browser访问网页或者进行其他Http链接的时候,手机端会把手机的型号按照Http规范拼接成类似‘Nokia 515/2.0(p)’的字串并添加到Http request里面发送到WebServer端。WebServer端则根据不同的手机型号对返回的网页数据进行定制。

问题分析

这个问题的奇怪之处就在于同属于一个git branch的phone1能够正常访问网页,但是Phone2却显示出错信息。那Phone1和Phone2有些什么区别呢?原来Phone1和Phone2虽然拥有相同的上层代码,但由于产品硬件的不同,却拥有不同的Driver层代码和产品配置信息。由于Browser使用Http协议获取和解析网页数据的代码都属于上层代码与Driver层没有太直接的关系。所以,大概可以排除由于产品Driver层代码差异的这种可能。把分析的思路放在产品配置信息上。

为了确定是那些产品配置信息导致了这个问题,我们使用了网络抓包工具Wireshark获取了Phone1和Phone2在使用Browser访问该网站时的Protocol信息。

(1)Phone1

   

(2)Phone2

   

 

通过对比发现Phone1和Phone2在和WebServer交互的前两对Request和Response都是一样的。

第1对Request和Response将网址:http://m.o2.de/goto/tbm-bild 重定向到:http://m.o2online.de/goto/tbm-bild 

第2对Request和Response将网址:http://m.o2online.de/goto/tbm-bild 重定向到:http://wap.bild.de/?nbsrc=o2_w2g_textlink

第3对Request和Response中,WebServer给Phone1和Phone2返回了不同的数据如下:

Phone1:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.2//EN" "http://www.wapforum.com/dtd/wml12.xml">
<wml>
<head>
<meta name="keywords" content="Bild-Channel Home"/>
<meta name="description" content="Aktuelle Nachrichten aus den Bereichen News, Leute, Sport, Auto und mehr"/>
</head>
<card title="
Home - BILD.de MOBIL">
<!--wd_ok-->
<p>
<br/><br/><br/>
<a href="/">
<img src="http://bilder.bild.de/fotos-skaliert/logo-2013--30727524/14,w=64,c=0,p=0,f=0,format=jpg,fsize=12000.bild.png" width="64" alt="Bild.de"/>
</a>
<br/>
<a href="/bild/json.bild.de/servlet/json/wap/18496602/4.html?i=0&amp;p=2062659024">07:03 Uhr Super Bowl: Broncos vs. Seahawks</a>
<br/>
<a href="/bild/json.bild.de/servlet/json/wap/34307694/17.html">
<img src="http://bilder.bild.de/fotos-skaliert/a-dschungelcamp-mitternacht_36439853-1390172259-34311730/2,w=101,c=0,p=0,f=0,format=jpg,fsize=12000.bild.jpg" width="101" alt=""/></a>
<br/><br/>
<a href="/bild/json.bild.de/servlet/json/wap/34308038/12.html">
......
<!--wd_ok-->
</p>
<do label="zurück" type="prev">
<prev/>
</do>
</card>
</wml>

Phone2:

<?xml version="1.0" encoding="utf-8" ?><!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        

        <script type="text/javascript">
            var sURL = "http://wap.bild.de/";
            var sQuery = "nbsrc=o2_w2g_textlink";
            var method = "inner width";
            var cookieDomain = "";
            var checkForCookie = true;
            var checkForSize = false;
            var originalReferer = "";
            var canReload = true;
            var canDisabledCookies = false;
            var cacheKey = "";
            var detectImmediately = false;
            var cookieLifetime = null;
            
            var connectiontype = function() {
                var ct, t;
                ct = "unknown";
                try {
                    for (t in navigator.connection) {
                        if (t != "type" && navigator.connection.type === navigator.connection[t]) {
                            ct = t;
                        }
                    }
                } catch (ex) { }
                return ct;
            }
            
            var getQueryVariable = function(variable) {
                var query, vars, i, pair, val;
                try {
                    query = window.location.search.substring(1);
                    vars = query.split("&");
                    for (i = 0; i < vars.length; i++) {
                        pair = vars[i].split("=");
                        if (pair[0] === variable) {
                            val = pair[1];
                            i = vars.length;
                        }
                    }
                } catch (ex) { }
                return val;
            }

            var cookiesupport = function() {
                var support, cvalue, readUrl;
                support = false;
                cvalue = 0;
                try {
                    if (document.cookie) {
                        var cookies = document.cookie.split("; ");
                        var output = "";
                        for ( var i = 0; i < cookies.length; i++) {
                            var cookiesplit = cookies[i].split("=");
                            if (cookiesplit[0] === "emvcc") {
                                cvalue = cookiesplit[1];
                            }
                        }
                    }
                    if (cvalue === "true" || cvalue === "1") {
                        support = true;
                    }
                    readUrl = getQueryVariable("emvcc");
                    if (readUrl === "false" || readUrl === "0") {
                        support = false;
                    }
                } catch (ex) { }
                return support;
            }
            
            var pixelratio = function() {
                var pr = 1;
                try {
                    pr = window.devicePixelRatio;
                    if (pr) {
                        pr = parseFloat(pr, 10);
                    } else {
                        pr = 1;
                    }
                } catch (ex) { }
                return pr;
            }
            
            var viewportsize = function(method) {
                var width, heigth;
                width = window.innerWidth;
                height = window.innerHeight;
                try {
                    if (method === "inner width") {
                        width = window.innerWidth;
                        height = window.innerHeight;
                    }
                    else if (method === "outer width") {
                        width = window.outerWidth;
                        height = window.outerHeight;
                    }
                    else if (method === "avail width") {
                        width = screen.availWidth;
                        height = screen.availHeight;
                    }
                    else if (method === "body client width") {
                        width = document.body.clientWidth;
                        height = document.body.clientHeight;
                    }
                    else if (method === "document client width") {
                        width = document.documentElement.clientWidth;
                        height = document.documentElement.clientHeight;
                    }
                } catch (ex) { }
                return width + "x" + height;
            }

            function doLoad() {
                setTimeout("executeTest()", 100);
            }
            
            function cookify(name, value) {
                var c, d, l, e;
                e = "";
                if (cookieLifetime) {
                    l = 1000 * cookieLifetime;
                    d = new Date();
                    d = new Date(d.getTime() + l);
                    e = ' expires=' + d.toGMTString() + ';';
                }
                c = name + '=' + value + '; path=/;' + e;
                if (cookieDomain && cookieDomain.length > 0) {
                    c = c + " domain=" + cookieDomain;
                }
                return c;
            }

            function executeTest() {
                var pr = pixelratio();
                var ct = connectiontype();
                var nbcol = '1|' + ct;
                nbcol = window.escape(nbcol);
                var vpsize = viewportsize(method);
                var useCookie = cookiesupport();

                var ad = "";
                var anchor = "";
                if (document.location && document.location.hash) {
                    anchor = document.location.hash;
                }
                if (useCookie === true) {
                    document.cookie = cookify('nborh', originalReferer);
                    document.cookie = cookify('nbpr', pr);
                    document.cookie = cookify('nbcol', nbcol);
                    if (checkForSize) {
                        document.cookie = cookify('emvAD', vpsize);
                    }
                    if (checkForCookie) {
                        document.cookie = cookify('emvcc', 1);
                    }
                    var index = sURL.indexOf(";jsessionid=");
                    if (index > -1) {
                        sURL = sURL.substring(0,index);
                    }
                    sURL = sURL + (sQuery.length > 0 ? "?" + sQuery:"");
                    var cookiemess = canDisabledCookies && checkForCookie;
                    if (canReload && !cookiemess) {
                        window.location.reload(true);
                    } else {
                        if (cookiemess) {
                            sURL = sURL + (sQuery.length > 0 ? "&emvcc=0" : "?emvcc=0");
                        }
                        window.location.replace(sURL);
                    }
                }
                else {
                    var connector = "?";
                    if (sURL.indexOf("?") >= 0) {
                        connector = "&";
                    }
                    ad = connector + "nborh=" + originalReferer;
                    if (checkForSize) {
                        ad =  ad + "&emvAD=" + vpsize;
                    }
                    if (checkForCookie) {
                        ad = ad + "&emvcc=0";
                    }
                    ad = ad + "&nbpr=" + pr;
                    ad = ad + "&nbcol=" + nbcol;
                    if (cacheKey !== "") {
                        ad = ad + "&nbck=" + cacheKey;
                    }
                    sURL = sURL + ad + (sQuery.length > 0 ? "&" + sQuery : "") + anchor;
                    window.location.replace(sURL);
                }
            }
            if (detectImmediately) {
                executeTest();
            }
</script>
<noscript>
    <meta http-equiv="refresh" content="1; url=http://wap.bild.de/?nbsrc=o2_w2g_textlink&emvcc=0&emvAD=240x320&nbcol=0%7Cunknown" />
</noscript>
<style>
    body {
        color:#ffffff;
        background-color:#ffffff;
        padding: 0px;
        margin-left: 0px;
        margin-top: 0px;
        margin-right: 0px;
        margin-bottom: 0px;
        font-family: Arial, Helvetica, sans-serif; font-size:11px;
    }

    div {
        color:#ffffff;
        background-color:#ffffff;
        margin-left: 5px;
        margin-top: 0px;
        margin-right: 0px;
        margin-bottom: 0px;
    }

    .text {
        color:#ffffff;
        background-color:#ffffff;
        font-size:11px;
    }
</style>
  </head>
  <body onload="doLoad();">
  <!-- Copyright 2013, Netbiscuits GmbH. External usage of this data without granted permission is strictly prohibited. -->
      <div class="text">
          <br/><br/><br/><br/><br/><br/>
          <br/><br/><br/><br/><br/><br/>
          <span style="color:#ffffff; background-color:#ffffff;">--------------------------------------------------------------------------------------------------------</span><br/><br/>
          <span style="color:#ffffff; background-color:#ffffff;">Detecting browser settings.</span><br/><br/>
          <span style="color:#ffffff; background-color:#ffffff;">Please enable JavaScript in your browsers settings.</span><br/><br/>
          <span style="color:#ffffff; background-color:#ffffff;">--------------------------------------------------------------------------------------------------------</span><br/><br/>
      </div>
  </body>
</html>

对于Phone1,WebServer返回的就是网页的实际数据。后续的HttpReqeust和HttpResponse只是获取该网页数据中某些图片的数据。

然而,对于Phone2,WebServer返回的是一段JavaScript的代码。通过分析不难看出,该代码用于在获取一些手机的参数,并将获取的参数拼接到一个新的URL里面,传到WebServer端。该网页JS的代码执行流程如下:

1) 网页解析进行到Body标签的时候,调用doLoad函数。

2) 在doLoad函数中调用setTimeout函数。

3) 在Timeout后调用setTimeout函数的第一个参数executeTest函数,进行获取device info和跳转URL的操作。

问题就出在第三步,由于当前Phone1和Phone2对JS的支持不完善。无法支持setTimeout JS函数。所以,导致无法跳转到网站的内容页面。所以,该页面body标签里的提示信息便显示了出来。

新问题:在测试的时候,当我们把Phone2的JavaScript Disable后,再去访问该网站,发现却能访问成功。这是什么原因呢?

原来在WebServer返回的这段代码中有这样一行:

<noscript>
    <meta http-equiv="refresh" content="1; url=http://wap.bild.de/?nbsrc=o2_w2g_textlink&emvcc=0&emvAD=240x320&nbcol=0%7Cunknown" />
</noscript>

noscript 元素用来定义在脚本未被执行时的替代内容(文本)。此标签可被用于可识别 <script> 标签但无法支持其中的脚本的浏览器。(详见:http://www.w3school.com.cn/tags/tag_noscript.asp)

那么,为什么WebServer会对Phone1和Phone2返回不同的数据呢?

同样我们抓出了Phone1和Phone2发给WebServer的Http Request信息:

Phone1:

Phone2:

通过对比发现,Phone1和Phone2的Http Request除了UA header和时间戳,其他参数都一样。所以,可以断定是UA Header导致了WebServer给Client端返回了不同的数据。

那么WebServer为什么需要这样实现呢?通过进行一步分析我们找到了WebServer的如下结构:

 

 

(更多:http://www.netbiscuits.com/solutions/platform/device-context-service/)

原来,WebServe会通过Client传过来的UA header到第三方的公司Netbiscuits提供的服务器上面获取该手机的Device information(例如:屏幕分辨率),用于优化网页的显示效果。对于Phone1来说,Netbiscuits的服务器中收录的该手机对应的信息,所以,WebServer能够获取到这些信息并对网页进行适配。所以,网页显示正常。然后对于Phone2,Netbiscuits服务器没有收录该手机的对应信息,所以,webserver试图通过Javascript函数获取这些信息。但是,由于Phone2的Browser无法支持JS的setTimeout函数。所以,这个问题便发生。

 

posted @ 2014-01-21 21:34  Printf Jerry Li  阅读(269)  评论(0编辑  收藏  举报