ajax|錯誤
即使你現在還沒有聽說,AJAX已經成為web技術領域最熱門的詞。AJAX架構的關鍵是名為 XMLHttpRequest 的 JavaScript 對象,通過它用戶端開發人員可以在不打斷使用者操作或者在充分使用隱藏表單的情況下通過 HTTP 直接發送和接收XML文檔。現在,有些人可能會有這種憂慮,讓那些以前只做表單(form)校正和增加圖片動畫效果的用戶端開發人員突然間負責分析 XML 文檔結構,以及與 HTTP 協議的 header 部分打交道,這能行嗎?但是,沒有風險就沒有回報。為了減輕這種疑惑,我將展示如何使用 XMLHttpRequest 實現以前無法實現功能,同時如何減少程式錯誤和如何提高程式品質。
XMLHttpRequest 和 XML DOM 的 JavaScript 基礎
首先,我們需要聲明一些規則。現在常用的瀏覽器(IE, Mozilla, Safari, Opera)都特別提供了對 XMLHttpRequest 對象的支援,同時也廣泛支援 XML DOM,雖然和往常一樣:微軟(Microsoft)使用了一種稍微有些不同的實現並有一些需要特殊注意的地方。和我們那些更進取的朋友直接實現 XMLHttpRequest 不同,IE需要你建立一個具有相同屬性的 ActiveXObject 對象的執行個體。Apple Developer Connection 網站上有一篇非常好的文章總覽了 XMLHttpRequest,並列舉了它的全部特性。
下面是個基礎的例子:
var req;
function postXML(xmlDoc) {
if (window.XMLHttpRequest) req = new XMLHttpRequest();
else if (window.ActiveXObject) req = new ActiveXObject("Microsoft.XMLHTTP");
else return; // fall on our sword
req.open(method, serverURI);
req.setRequestHeader('content-type', 'text/xml');
req.onreadystatechange = xmlPosted;
req.send(xmlDoc);
}
function xmlPosted() {
if (req.readyState != 4) return;
if (req.status == 200) {
var result = req.responseXML;
} else {
// fall on our sword
}
}
這個強大的工具應用前景非常廣泛,而且對其潛在應用方面的探索才剛剛開始。但是在任何準備在網上建立XML園地的人失控前,我建議我們先架起一個安全網以防止任何抱負極高的人摔斷他們的脖子。
JavaScript錯誤處理基礎
JavaScript在其早期版本就支援簡單的錯誤處理,但是非常簡陋,只有少數的特性,並且實現的很差。新近的瀏覽器不僅支援了類似於C++和Java用於錯誤處理的關鍵字try/catch/finally,而且實現了onerror事件提供捕捉在運行期產生的任何錯誤。其使用方法非常簡單而且直接:
function riskyBusiness() {
try {
riskyOperation1();
riskyOperation2();
} catch (e) {
// e是錯誤類型對象
// 至少有兩個屬性:name及message
} finally {
// 清理工作
}
}
window.onerror = handleError; // 架起捕獲錯誤的安全網
function handleError(message, URI, line) {
// 提示使用者,該頁可能不能正確回應
return true; // 這將終止預設資訊
}
一個執行個體:將用戶端的錯誤傳遞個伺服器
現在我們已經瞭解了XMLHttpRequest和JavaScript錯誤處理的基礎,讓我們通過一個簡單的例子來看一下如何將這兩個聯絡在一起使用。你也許會認為JavaScript錯誤應該很容易通過狀態列的黃色三角認出,但是我仍然在幾個可靠組織的面向公眾的網站上發現他們躲過了品質評估部門。
因此,在這我將提供一種捕捉錯誤並將他們在伺服器上記錄,希望能夠提醒某個人去修改這些錯誤。首先,我們考慮用戶端。用戶端需要提供一個產生日誌記錄的類,這個類在使用時必須只執行個體化一次,並能夠透明地處理那些複雜的細節。
我們首先建立構造器:
// 一個類構造方法
function Logger() {
// 域
this.req;
// 方法
this.errorToXML = errorToXML;
this.log = log;
}
其次,我們定義一個將Error對象轉成XML的方法。預設情況下,Error對象只有兩個屬性:name和message,但我們需要核對第三個可能很有用屬性location。
// 將一個錯誤映射到XML文檔
function errorToXML(err) {
var xml = '<?xml version="1.0"?>\n' +
'<error>\n' +
'<name>' + err.name + '</name>\n' +
'<message>' + err.message + '</message>\n';
if (err.location) xml += '<location>' + err.location + '</location>';
xml += '</error>';
return xml;
}
接著是log方法。這是這段指令碼將上述兩個原則(XMLHttpRequest和XML DOM)結合在一起的最基礎部分。注意我們使用了POST方法。在這我本質上是建立了一個唯寫的定製的web服務,在每一次成功的請求時它會建立一些新紀錄。因此,使用POST是唯一可行的方法。
// Logger類的日記方法
function log(err) {
// 嗅探環境
if (window.XMLHttpRequest) this.req = new XMLHttpRequest();
else if (window.ActiveXObject) this.req =
new ActiveXObject("Microsoft.XMLHTTP");
else return; // 無功而返
// 確定方式及URI
this.req.open("POST", "/cgi-bin/AjaxLogger.cgi");
// 設定request的headers。 如果錯誤出現在一個所包含的.js檔案中,
//REFERER 這個最頂層的URI可能會與產生錯誤的地方不一致
this.req.setRequestHeader('REFERER', location.href);
this.req.setRequestHeader('content-type', 'text/xml');
// 請求完畢時要調用的函數
this.req.onreadystatechange = errorLogged;
this.req.send(this.errorToXML(err));
// 如果不能在10秒在完成請求,
// 需交易處理
this.timeout = window.setTimeout("abortLog();", 10000);
}
最後是執行個體化日誌記錄類。注意這個類只能有一個執行個體。
// logger只能有一個執行個體
var logger = new Logger();
最後還有兩個我們的類裡調用的方法。如果在記錄錯誤時發生錯誤,除了告訴使用者我們沒有其它的辦法。如果運氣好的話,這種情況並不會出現。因為瀏覽器的事件(event)不會擁有我們的對象的引用,但是會引用我們建立的logger執行個體,所以這兩個方法不是日誌記錄類的方法。
// 儘管已經嘗試,但如果有串連錯誤,放棄吧
function abortLog() {
logger.req.abort();
alert("Attempt to log the error timed out.");
}
// 當request狀態有變化則調用
function errorLogged() {
if (logger.req.readyState != 4) return;
window.clearTimeout(logger.timeout);
// 請求完畢
if (logger.req.status >= 400)
alert('Attempt to log the error failed.');
}
以上所有的代碼都可以寫在一個.js檔案裡,你可以在任何一個(或者所有)的頁面裡引入這個檔案。下面是這個例子展示了如何引入並使用這個檔案:
現在我們已經知道如何將日誌和HTML頁面整合,剩下的就是如何接收和解析用戶端傳遞到伺服器的訊息。我使用了大家易接受的CGI指令碼來實現這個功能,在這個指令碼裡我用了XML::Simple(我最喜歡的模組之一)來解析提交上來的資料,並且用CGI::Carp將結果直接寫到httpd(http服務程式)的錯誤記錄檔裡,這樣你的系統管理員就不用去監控另一個日誌。這個指令碼裡還包含了一些好的例子來說明如何對不同的成功和失敗條件編寫對應的響應代碼。
<script type="text/javascript" src="Logger.js"></script>
<script type="text/javascript">
function trapError(msg, URI, ln) {
// 將未知錯誤封裝進一個對象
var error = new Error(msg);
error.location = URI + ', line: ' + ln; // 增加定製屬性
logger.log(error);
warnUser();
return true; // 避免出現黃色三角形符號
}
window.onerror = trapError;
function foo() {
try {
riskyOperation();
} catch (err) {
//增加定製屬性
err.location = location.href + ', function: foo()';
logger.log(err);
warnUser();
}
}
function warnUser() {
alert("An error has occurred while processing this page."+
"Our engineers have been alerted!");
// 錯誤處理
location.href = '/path/to/error/page.html';
}
</script>
現在我們已經知道如何將日誌和HTML頁面整合,剩下的就是如何接收和解析用戶端傳遞到伺服器的訊息。我使用了大家易接受的CGI指令碼來實現這個功能,在這個指令碼裡我用了XML::Simple(我最喜歡的模組之一)來解析提交上來的資料,並且用CGI::Carp將結果直接寫到httpd(http服務程式)的錯誤記錄檔裡,這樣你的系統管理員就不用去監控另一個日誌。這個指令碼裡還包含了一些好的例子來說明如何對不同的成功和失敗條件編寫對應的響應代碼。
use CGI;
use CGI::Carp qw(set_progname);
use XML::Simple;
my $request = CGI->new();
my $method = $request->request_method();
# method must be POST
if ($method eq 'POST') {
eval {
my $content_type = $request->content_type();
if ($content_type eq 'text/xml') {
print $request->header(-status =>
'415 Unsupported Media Type', -type => 'text/xml');
croak "Invalid content type: $content_type\n";
}
# when method is POST and the content type is neither
# URI encoded nor multipart form, the entire post
# is stuffed into one param: POSTDATA
my $error_xml = $request->param('POSTDATA');
my $ref = XML::Simple::XMLin($error_xml);
my ($name, $msg, $location) =
($ref->{'name'}, $ref->{'message'}, '');
$location = $ref->{'location'} if (defined($ref->{'location'}));
# this will change the name of the carper in the log
set_progname('Client-side error');
my $remote_host = $request->remote_host();
carp "name: [$name], msg: [$msg], location: [$location]";
};
if ($@) {
print $request->header(-status => '500 Internal server error',
-type => 'text/xml');
croak "Error while logging: $@";
} else {
# this response code indicates that the operation was a
# success, but the client should not expect any content
print $request->header(-status => '204 No content',
-type => 'text/xml');
}
} else {
print $request->header(-status => '405 Method not supported',
-type => 'text/xml');
croak "Unsupported method: $method";
}
以上就是全部的代碼了。現在,當下次有一些有問題的JavaScript代碼溜進系統時,你可以想象你的日誌監控人員開始閃動紅色的燈,而你的用戶端開發人員在半夜接到電話的情景。