2012-02-10

Tags: javascript , 程式語言 , jquery

要用JavaScript實作Ping程式,用jQuery裡的ajax功能來實作是我想到最簡單的方式(自己用JavaScript純手工打造也ok啦,但是可以用jQuery爽爽做事就不想自己當苦行僧了...:P)。然後...我自己在實作時遇到下面所提的問題。

JavaScript基於同源策略(Same-origin policy)," 一般而言"位於不同domain的網頁是無法相互叫用的(e.g. 放在test1.mydomain.com的JavaScript不能執行test2.mydomain.com的JavaScript)。 不給相互叫用的原因是基於安全性的考量,怕會發生 XSS(Cross-site scripting)的問題。

用JavaScript去實作Ping一定會違背同源策略,所以要想辦法突破這個Cross-Domain Requests的難題。相對的解法有儿種,可以參考 這篇文章。話說回來,看完後應該會覺的都很麻煩,但是現行也沒什麼其它的選擇就是了。

最後我解決這問題的方式是用了JSONP(相關說明: 其一其二)的方式來處理。因為<script></script>裡面 src屬性指到的網址所包含的內容不受同源策略限制(這就是前面那段" 一般而言"裡的例外啦),所以JSONP的方法才能成功運作。

因為用的是JSONP的方式,所以你預期ServerSide一定會吐回一個JavaScript格式的資料,但是這在Ping程式上是一定辦不到的。因為ServerSide可不是你管的,你無權在ServerSide上加入任何Server端程式碼(e.g. JSP、PHP、ASP.Net...etc)。不過沒關系,反正只要ServerSide能成功吐回任何資料,Ping程式就有辦法判斷執行的結果(Ping是否成功、執行花了多久...etc),吐回資料的內容不是JavaScript格式只是導致jQuery在" 成功"取得資料後(指http status code為200,並拿到回傳的資料),每次都會發生parsererror。發生parsererror的原因是由於ServerSide執行成功時,丟回來的資料會是html格式,所以jQuery在接手處理後一定會發生parsererror,這其實是我們預期中的狀況。

有了解法後,就可以開始實作了,下面是用jQuery實作出來的解法。

ping.js

$.ping = function(url,callBack) 
{
var requestTime;

function appendHttpPrefix(url){
//保證url帶http://
var strReg="^((https|http)?://){1}";
var re=new RegExp(strReg);
return re.test(url)?url:"http://"+url;
}

$.ajax({
url: appendHttpPrefix(url),
type: "GET",
dataType: "jsonp", //設成jsonp來解決cross-domain requests的問題
timeout: 5000, //超過5秒沒回應就timeout
cache: false, //不保留cache
beforeSend: function(){
requestTime = new Date().getTime();
},
complete: function(jqXHR, textStatus){
var responseTime = new Date().getTime();
var ackTime = responseTime - requestTime;
var status;

//因為dataType用jsonp格式,但是Server端未針對這部份進行處理,所以執行成功時
//必定會發生"parsererror"(因為回傳的資料格式不為JavaScript,所以會"parsererror")。
//此時將需將status手動改回"success"
if(textStatus == "parsererror"){
status = "success";
}
else{
status = textStatus;
}

callBack({
url: url,
ackTime: ackTime,
status: status
});
}
});
};

ping_demo.html
<html>
<head>
<script language="javascript" src="jquery-1.7.1.min.js"></script>
<script language="javascript" src="ping.js"></script>
<script>
$(function(){               
    var timerId;
    var isStopPing = false;   
   
    $("#pingStart").click(function(){       
        $(this).attr("disabled", true);
        $("#pingStop").attr("disabled", false);
       
        $.ping($("#webSiteUrl").val(),
            function(pingResult){
                timerId = setInterval(function(){
                    if(isStopPing == true){
                        clearInterval(timerId);
                        isStopPing = false;
                        return;
                    }
                   
                    if(pingResult){
                        $("#pingResultMsg").append("{ url:" + pingResult.url + " ,ackTime:" + pingResult.ackTime + " ,status:" + pingResult.status + " }<br>");
                        clearInterval(timerId);
                        $("#pingStart").trigger("click");
                    }
                },1000);               
            }
        );       
    });
   
    $("#pingStop").click(function(){
        isStopPing = true;
        $("#pingStart").attr("disabled", false);
        $(this).attr("disabled", true);
    });
   
    $("#clearPingResultMsg").click(function(){
        $("#pingResultMsg").html("");
    });   
});
</script>
</head>
<body>
<div>
    WebSiteURL:<input type="text" size="50" id="webSiteUrl">
</div>
<div>
    <input type="button" id="pingStart" value="PingStart">
    <input type="button" id="pingStop" value="PingStop" disabled="disabled">
    <input type="button" id="clearPingResultMsg" value="ClearPingResultMsg">
</div>
<div id="pingResultMsg"></div>
</body>
</html>

後記: