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代碼溜進系統時,你可以想象你的日志監控人員開始閃動紅色的燈,而你的客戶端開發人員在半夜接到電話的情景。