使用jQuery和YQL,以Ajax方式載入外部內容

turingbooks發表於2011-12-15

我們來看看怎樣使用jQuery,以Ajax方式載入外部(其他域上)的內容。這裡的所有程式碼都可以從GitHub下載,也可以在這個演示頁面中獲取,因而不用複製貼上了。

OK,Ajax通過jQuery是很容易做到的,大多數解決方案就幾行程式碼:

$(document).ready(function(){
$('.ajaxtrigger').click(function(){
$('#target').load('ajaxcontent.html');
});
});

檢視這個簡單但有點粗陋的Ajax演示就可以看到結果。

這會將所有帶ajaxtrigger類的元素轉換成觸發器來載入ajaxcontent.html,並在ID為target的元素中顯示其內容。

這樣不好,因為多數時候這意味著人們將使用<a href="#">click me</a>這種空連結,但這不是我們現在要討論的問題。我在撰寫一篇更長的文章,其中會提到增強Ajax可用性和可訪問性的所有技巧。

要使其能夠重用可以像下面這樣:

$(document).ready(function(){
$('.ajaxtrigger').click(function(){
$('#target').load($(this).attr('href'));
return false;
});
});

這樣,你可以使用<a href="ajaxcontent.html" class="ajaxtrigger">load some content</a>來載入內容,而所有JavaScript程式碼都可以重用。

檢視這個可重用Ajax演示就能看到結果。

我要解決的問題發生在點選演示頁面中的第二個連結時:載入外部內容失敗,因為Ajax不允許跨域載入內容。這意味著,<a href="http://icant.co.uk/" class="ajaxtrigger">see my portfolio</a>載入Ajax內容將失敗,而且沒有提示。儘管你無數遍地點選這個連結,但是什麼都不會發生。避免出現這種情況的一個方法,是簡單地讓瀏覽器載入該文件,但前提是使用者真的想載入外部連結。

檢視這個允許載入外部連結的演示就能看到結果。

$(document).ready(function(){
$('.ajaxtrigger').click(function(){
var url = $(this).attr('href');
if(url.match('^http')){
return true;
} else {
$('#target').load(url);
return false;
}
});
});

使用PHP代理

如果瀏覽Web,你會發現大多數的解決方案是PHP(或其他語言)代理指令碼。比如,下面是使用cURL的proxy.php代理指令碼:

<?php
$url = $_GET['url'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
echo $content;
?>

然後可以稍作修改使用這個指令碼(使用代理):

$(document).ready(function(){
$('.ajaxtrigger').click(function(){
var url = $(this).attr('href');
if(url.match('^http')){
url = 'proxy.php?url=' + url;
}
$('#target').load(url);
return false;
});
});

用這樣的代理指令碼依舊是個很蠢的辦法,因為不進行過濾,人們就可以使用這個指令碼來載入你伺服器上的任何文件,並將其內容顯示在自己的頁面中(用firebug來重新命名連結,就能看到你伺服器上的任何內容),他們可以使用它將郵件群髮指令碼插入文件,或者簡單地使用它來重定向到任何其他Web資源,並且讓你的伺服器看上去就是傳送請求的那個伺服器。垃圾郵件製造者就有了施展才華的地方了。

使用白名單和過濾代理

因而,要想使用代理,就得確保有被認可的URI的白名單。此外,除了另一個HTML文件的主體,其他的都除去比較好。另一個好辦法是過濾指令碼。這會避免顯示錯誤和執行你本不想在網站上執行的指令碼。

就像下面這樣:

<?php
$url = $_GET['url'];
$allowedurls = array(
'http://developer.yahoo.com',
'http://icant.co.uk'
);
if(in_array($url,$allowedurls)){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$content = preg_replace('/.*<body[^>]*>/msi','',$output);
$content = preg_replace('/</body>.*/msi','',$content);
$content = preg_replace('/<?/body[^>]*>/msi','',$content);
$content = preg_replace('/[r|n]+/msi','',$content);
$content = preg_replace('/<--[Ss]*?-->/msi','',$content);
$content = preg_replace('/<noscript[^>]*>[Ss]*?</noscript>/msi','',$content);
$content = preg_replace('/<script[^>]*>[Ss]*?</script>/msi','',$content);
$content = preg_replace('/<script.*/>/msi','',$content);
echo $content;
} else {
echo 'Error: URL not allowed to load here.';
}
?>

使用YQL的純JavaScript解決方案

但是,如果沒有權利訪問伺服器,或者你只想使用JavaScript,怎麼辦?不用擔心,這是可以做到的。藉助YQL可以載入任何HTML文件,並以JSON格式返回。jQuery具有載入JSON的好介面,因此與YQL一起使用就可以達到我們的目的。

從YQL獲取HTML很容易,使用下面語句即可:

select * from html where url="http://icant.co.uk"

YQL還可以完成下面一些事:

  • 載入並清理HTML文件
  • 使用HTML Tidy執行HTML文件來刪除不好的標記
  • 快取HTML
  • 只返回HTML的主體內容,因而除內聯樣式外不需處理其他樣式

資料輸出格式可以是XML或JSON。如果為JSON定義了回撥引數,就表明要使用JSON-P,所有HTML都會儲存在一個JavaScript物件中——這不適合重組。

foo({
"query":{
<a href=""1" title="">count</a>",
<a href=""2010-01-10T07:51:43Z" title="">created</a>",
<a href=""en-US" title="">lang</a>",
<a href=""2010-01-10T07:51:43Z" title="">updated</a>",
<a href=""http://query.yahoo[...whatever...]k%22" title="">uri</a>",
"results":{
"body":{
"div":{
<a href=""doc2" title="">id</a>",
<a href="[{"id":"hd" title="">div</a>",
<a href=""icant.co.uk" title="">h1</a> - everything Christian Heilmann"
},
{<a href=""bd" title="">id</a>",
"div":[
{<a href="[{"h2":"About" title="">div</a> this and me","[... and so on...]
}}}}}}}});

當定義了帶XML輸出的回撥時,會得到將HTML資料作為陣列中字串的函式呼叫,簡單多了:

foo({
"query":{
<a href=""1" title="">count</a>",
<a href=""2010-01-10T07:47:40Z" title="">created</a>",
<a href=""en-US" title="">lang</a>",
<a href=""2010-01-10T07:47:40Z" title="">updated</a>",
<a href=""http://query.y[...who" title="">uri</a> cares...]%22"},
"results":[
"<body>n    <div id="doc2">n      <div id="hd">n
<h1>icant.co.uk - everything Christian Heilmann</h1>n
... and so on ..."
]
});

使用jQuery的getJSON()方法,訪問YQL端點,這很容易實現:

$.getJSON("http://query.yahooapis.com/v1/public/yql?"+
"q=select%20*%20from%20html%20where%20url%3D%22"+
encodeURIComponent(url)+
"%22&format=xml'&callback=?",
function(data){
if(data.results[0]){
var data = filterData(data.results[0]);
container.html(data);
} else {
var errormsg = '<p>Error: could not load the page.</p>';
container.html(errormsg);
}
}
);

組合在一起可以得到使用jQuery和YQL的跨域Ajax解決方案

$(document).ready(function(){
var container = $('#target');
$('.ajaxtrigger').click(function(){
doAjax($(this).attr('href'));
return false;
});
function doAjax(url){
// 如果它是個外部URI
if(url.match('^http')){
// 呼叫YQL
$.getJSON("http://query.yahooapis.com/v1/public/yql?"+
"q=select%20*%20from%20html%20where%20url%3D%22"+
encodeURIComponent(url)+
"%22&format=xml'&callback=?",
// 這個函式得到的資料來自成功的JSON-P呼叫
function(data){
// 如果有資料,過濾它並呈現出來
if(data.results[0]){
var data = filterData(data.results[0]);
container.html(data);
// 否則提示出錯了
} else {
var errormsg = '<p>Error: could not load the page.</p>';
container.html(errormsg);
}
}
);
// 如果它不是外部URI,使用Ajax的load()方法
} else {
$('#target').load(url);
}
}
// 過濾掉一些不好的東西
function filterData(data){
data = data.replace(/<?/body[^>]*>/g,'');
data = data.replace(/[r|n]+/g,'');
data = data.replace(/<--[Ss]*?-->/g,'');
data = data.replace(/<noscript[^>]*>[Ss]*?</noscript>/g,'');
data = data.replace(/<script[^>]*>[Ss]*?</script>/g,'');
data = data.replace(/<script.*/>/,'');
return data;
}
});

當然,這個例子還很粗糙。實際的Ajax解決方案應該考慮超時,以及未找到文件的情況。檢視帶載入指示器、異常處理和黃褪技術的完整程式碼以獲得靈感。

相關文章