Stay Hungry,Stay Foolish!

对于ajax更新的页面,如何实现离线浏览? 一则方案尝试。

背景

现代页面上越来越多的内容是通过ajax更新, 因为页面可以显示的更加快速, 局部更新, 同时可以实现页面内容的动态性, 增加页面的内容的丰富性。

但是如果将页面内容保存下来, 以备离线浏览器查看, 则由于ajax存在的原因, 直接使用保存的方法不能实现, 因为会有ajax访问禁用的报错。

 

对于非ajax页面, 页面上内容仅仅使用 链接组织资源的情况, 页面的内容是完整的,  则浏览器保存下来的内容是完整的, 则可以在本地使用离线版本浏览。

 

方案思路

分析现有页面使用ajax更新的 方法, 有如下两种:

1、 使用ajax方式获取部分html代码, 其中可能有js代码混杂其中, 但是整体上 html代码,  然后使用 jquery.html 接口将 html代码 插入页面中。

  插入过程, 页面html被解析显示, js代码被执行。

 

2、 使用ajax方式获取页面需要显示的数据, 一般为json格式和xml格式,ajax后去后, 到前台使用js给页面上的指定DOM元素赋值, 获取阻止为到html字符串中, 然后将构造的带有动态数据的html字符串, 插入到DOM节点中。

 

针对这两种情况, 希望此这两种情况的 数据如果能够存储在当前页面中, 并且重载ajax函数, ajax不去发起http请求, 替换的是从本地的页面中, 查找到缓存的数据, 并返回。那么, ajax调用后的其它部分的代码逻辑没有改变, 不用改动。

 

只要实现如下内容:

1、 ajax数据的抓取, 并存储到本页面DOM中。

2、 将此页面中使用ajax函数重载为 本地缓存 查找的函数。

3、 将此页面保存为离线文件形式。

 

然后,你就可以离线浏览了。 当然还有一些链接存在的 文件, 这个不能被保存, 需要另行下载或者准备。 但是被ajax容易多了。

方案实现

借助phantomjs实现抓取和插入DOM, 并修改ajax接口, 并保存为本地文件。

 

样例

集合 ajax请求html 和 json数据的场景,  顺序为  ajax先请求html文件, html文件中js脚本又发起请求, 请求json文件。

https://github.com/fanqingsong/code-snippet/tree/master/web/ajaxPage2OfflineGUI 

 

test.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.js"></script>
</head>
<body>
  <img src="./favicon.ico">

  <form id="realform">
  </form>

  <script>
    $(document).ready(function (){
      $.get("./formContent.html", function (data) {
        $("#realform").html(data)
      })
    })
  </script>
</body>
</html>

 

formContent.html

<input type="text" name="Frm_username" id="Frm_username" value="">
<input type="password" name="Frm_password" id="Frm_password" value="">
<button type="submit">submit</button>

<script>
$(document).ready(function () {
   $.getJSON('./test.json', function(data) {
    $("#Frm_username").val(data.username);
    $("#Frm_password").val(data.password);
   });
});
</script>

 

test.json

{
    "username":"fanqingsong",
    "password":"xxxx"
}

 

 

PhantomJS 代码

如下代码中 利用页面中的 ajax 重新获取某个URL响应的逻辑, 可以使用 onCallback 机制代替, 以达到更加优化方案, 不用重新发起。

http://phantomjs.org/api/webpage/handler/on-callback.html

在页面中, ajax获取数据的地方, 注入如下代码。

if (typeof window.callPhantom === 'function') {
  window.callPhantom({ hello: 'world' });
}

 

也可以使用ajax嗅探器, 获得ajax响应结果。

http://medialab.github.io/artoo/sniffers/

 

"use strict";

var page = require('webpage').create();

var fs = require('fs');
page.open('http://localhost/test.html', function () {
    page.evaluate(function(){

    });
});

var ajaxURLCode = {};

page.onConsoleMessage = function(msg, lineNum, sourceId) {
  console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};

function hereDoc(f) { 
    return f.toString().replace(/^[^\/]+\/\*!?\s?/, '').replace(/\*\/[^\/]+$/, '');
}

page.onLoadFinished = function() {
    console.log("page load finished");

    // 待页面ajax内容加载完毕才 截图 和 保存页面内容
    waitFor(function () {
        return page.evaluate(function () {
            if ( $("#Frm_username").val() != "" ) 
            {
                return true;
            }

            return false;      
        });
    }, function  () {
        // 将每个ajax请求的结果记录到当前page 的DOM 中
        for (var urlPath in ajaxURLCode) {
            console.log("====== urlPath="+urlPath);

            var content = ajaxURLCode[urlPath];

            page.evaluate(function(urlPath, content){
                $("<div style='display:none' id='"+urlPath+"'></div>")
                .text(content)
                .appendTo("body");
            }, urlPath, content);
        };

        // 注入 ajax 请求桩函数, 以实现从页面中缓存显示效果
        page.evaluate(function(hereDoc){
            $("body").prepend("<div id='overwriteAjaxFunc'></div>");

            var ajaxGetRewrite = hereDoc(function () {/*
<script type='text/javascript'>
$(document).ready(function () {
  $.get = function (url, callback) {
     console.log('enter $.get refactor')
     url = url.match('./(.*)')[1];
     var data = $('[id="'+url+'"]').text();
     callback(data);
  }

  $.getJSON = function (url, callback) {
     console.log('enter $.getJSON refactor')
     url = url.match('./(.*)')[1];
     var data = $('[id="'+url+'"]').text();
     data = JSON.parse(data);
     callback(data);
  }  
});
</script>
            */});

            $("#overwriteAjaxFunc").html(ajaxGetRewrite);
        }, hereDoc);


        // 保存页面图片和代码
        page.render('test.png');
        fs.write('test.html', page.content, 'w');
    })
};

page.onResourceRequested = function(requestData, networkRequest) {
    // 判断是否为ajax
    var isAjaxRequested = false;
    var headers = requestData.headers;
    for (var i = 0; i < headers.length; i++) {
        var oneHeader = headers[i];
        var headerName = oneHeader.name;
        var headerValue = oneHeader.value;
        if ( headerName == "X-Requested-With" 
            && headerValue == "XMLHttpRequest" ) 
        {
            isAjaxRequested = true;
        }
    }

    // 只记录AJAX请求的结果
    if ( !isAjaxRequested ) 
    {
        return;
    }

    console.log('Request (#' + requestData.id + '): ' + JSON.stringify(requestData));

    var url = requestData.url;
    console.log(" onResourceRequested url="+url)

    if ( url.match("json$") || url.match("html$") ) 
    {
        var pageRaw = require("webpage").create()
        //pageRaw.settings.javascriptEnabled = false;
        // 借助有jquery的页面 下载 ajax内容
        pageRaw.open('http://localhost/test.html', function  () {
            console.log("url  =----------------- ="+url)
            //console.log("url plainText ="+pageRaw.plainText)
            //console.log("url content ="+pageRaw.content)

            var content = pageRaw.evaluate(function (url) {
                var ajaxRet = "";

                var request = $.ajax({
                    async: false,
                    url: url,
                    dataType: "text"
                });
                 
                request.done(function( msg ) {
                  ajaxRet = msg;
                });

                console.log("ajaxRet="+ajaxRet)
                return ajaxRet;
            }, url)

            console.log("!!!!!!!!!!!!!content="+content);

            // http://xxx/urlpath
            var matchRet = url.match("http://.*/(.*)")
            console.log("matchRet  =----------------- ="+matchRet)
            var urlPath = matchRet[1]
            console.log("urlPath  =----------------- ="+urlPath)

            ajaxURLCode[urlPath] = content;
        })
    }

};

// page.onResourceReceived = function(response) {
//     //console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response));

// };


function waitFor(testFx, onReady, timeOutMillis) {
    var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000, //< Default Max Timout is 3s
        start = new Date().getTime(),
        condition = false,
        interval = setInterval(function () {
            if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
                // If not time-out yet and condition not yet fulfilled
                condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
            } else {
                if (!condition) {
                    // If condition still not fulfilled (timeout but condition is 'false')
                    console.log("'waitFor()' timeout");
                    phantom.exit(1);
                } else {
                    // Condition fulfilled (timeout and/or condition is 'true')
                    console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
                    typeof (onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
                    clearInterval(interval); //< Stop this interval
                }
            }
        }, 250); //< repeat check every 250ms
};

截图

 

 

OFFLINE GUI Page Code

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.js"></script>
</head>
<body><div id="overwriteAjaxFunc">
<script type="text/javascript">
$(document).ready(function () {
  $.get = function (url, callback) {
     console.log('enter $.get refactor')
     url = url.match('./(.*)')[1];
     var data = $('[id="'+url+'"]').text();
     callback(data);
  }

  $.getJSON = function (url, callback) {
     console.log('enter $.getJSON refactor')
     url = url.match('./(.*)')[1];
     var data = $('[id="'+url+'"]').text();
     data = JSON.parse(data);
     callback(data);
  }  
});
</script>
            </div>
  <img src="./favicon.ico">

  <form id="realform"><input type="text" name="Frm_username" id="Frm_username" value="">
<input type="password" name="Frm_password" id="Frm_password" value="">
<button type="submit">submit</button>

<script>
$(document).ready(function () {
   $.getJSON('./test.json', function(data) {
    $("#Frm_username").val(data.username);
    $("#Frm_password").val(data.password);
   });
});
</script>




</form>

  <script>
    $(document).ready(function (){
      $.get("./formContent.html", function (data) {
        $("#realform").html(data)
      })
    })
  </script>


<div style="display:none" id="formContent.html">&lt;input type="text" name="Frm_username" id="Frm_username" value=""&gt;
&lt;input type="password" name="Frm_password" id="Frm_password" value=""&gt;
&lt;button type="submit"&gt;submit&lt;/button&gt;

&lt;script&gt;
$(document).ready(function () {
   $.getJSON('./test.json', function(data) {
    $("#Frm_username").val(data.username);
    $("#Frm_password").val(data.password);
   });
});
&lt;/script&gt;




</div><div style="display:none" id="test.json">{
    "username":"fanqingsongaaa",
    "password":"xxxx"
}</div></body></html>

 

posted @ 2016-10-17 23:26  lightsong  阅读(771)  评论(0编辑  收藏  举报
Life Is Short, We Need Ship To Travel