挣脱浏览器的束缚(7) - CrossSubDomainExecutor
在上次的文章中,我們已經提到了一種能夠跨子域名進行AJAX請求的方法。我們現在就來實現一個對開發人員透明的實現,它會自動判斷這個請求是否是跨子域名,如果不是,則使用傳統的方法發出AJAX請求,反之則使用我們的方式。
我在如何實現這個Executor的問題上,我想了很久。按照ASP.NET AJAX的“標準”來說,應該開發一個WebRequestExecutor的子類,然后將其設為默認的Executor或者某個特定WebRequest的Executor。但是最終我使用了直接修改XMLHttpExecutor的做法,因為考慮到以下原因:
- 完整實現一個WebRequestExecutor需要實現太多接口,而CrossSubDomainExecutor和XMLHttpExecutor有太多相同的地方。
- 如果繼承WebRequestExecutor的話,還是需要了解XMLHttpExecutor的太多細節,JavaScript做不到太好的封裝,無法實現真正的面向對象。
- CrossSubDomainExecutor在普通情況下和XMLHttpExecutor的行為一模一樣。
直接修改XMLHttpExecutor的做法其實就是在XMLHttpExecutor的prototype上做文章,這也就是JavaScript擴展對象的一貫做法。
首先,我們定義一個Sys.Net.WebRequest類的靜態方法,用來從一個URL中獲得域名。例如輸入http://www.sample.com/Default.aspx這個URL,返回http://www.sample.com,這個靜態函數對于判斷是否Cross Domain非常重要,如下:?
Sys.Net.WebRequest._getRawDomain = function(url) {url = Sys.Net.WebRequest._resolveUrl(url);var index = url.indexOf('://');var prefix = url.substring(0, index + 3);var url = url.substring(index + 3);index = url.indexOf('/');if (index < 0){index = url.indexOf('?');}if (index >= 0){url = url.substring(0, index);}return prefix + url; }?
其次,我們為Sys.Net.WebRequest類擴展一個,用于檢測請求能否直接發出,而不需要使用我們的方法。在這里我們直接使用了一個XMLHttpRequest對象作為嘗試:
Sys.Net.WebRequest.prototype._checkIfIsCrossDomainRequest = function() {var request = new XMLHttpRequest();try{request.open('get', this.get_url());return false;}catch(e){return true;} }?
我們還需要得到WebRequest的兩個特性:它的Document Domain(設為document.domain的值),以及它本身的域名(Raw Domain),我們依舊為Sys.Net.Request擴展方法:
Sys.Net.WebRequest.prototype._getDocDomain = function() {if (!this._docDomain){var pageDomain = Sys.Net.WebRequest._getRawDomain(window.location.href);var requestDomain = this._getRawDomain();var i = 0;while (true){i ++;var c1 = pageDomain.charAt(pageDomain.length - i);var c2 = requestDomain.charAt(requestDomain.length - i);if (c1 !== c2){break;}}var url = pageDomain.substring(pageDomain.length - i + 1);var index = url.indexOf('.');this._docDomain = url.substring(index + 1);}return this._docDomain; } Sys.Net.WebRequest.prototype._getRawDomain = function() {if (!this._rawDomain){this._rawDomain = Sys.Net.WebRequest._getRawDomain(this.get_url());}return this._rawDomain; }?
我們要修改XMLHttpExecutor,最關鍵的一步就是要重新實現executeRequest方法。很顯然,再這之前,我們需要保留原來的實現:
Sys.Net.XMLHttpExecutor.prototype._normalExecuteRequest = Sys.Net.XMLHttpExecutor.prototype.executeRequest;Sys.Net.XMLHttpExecutor.prototype.executeRequest = function() {if (this.get_webRequest()._checkIfIsCrossDomainRequest()){this._crossDomainExecuteRequest();}else{this._normalExecuteRequest();} }?
接下來,就需要實現一個跨子域名發出AJAX請求方法了。再這之前,需要對于XMLHttpExecute本身的實現有所了解。我們現在就對添加的方法進行簡單的分析。
首先,自然是_crossDomainExecuteRequest方法。我們準備了兩個字典:_iframeCache和_iframeLoaded,分別用于保存作為Proxy的iframe對象,以及表明iframe對象是否已經加載成功了(iframe加載也是異步的,也需要一定時間)。我們先模仿XMLHttpExecutor的實現,建立一個偵測是否超時的監聽器;再使用該請求的Raw Domain作為key,經過下面判斷的三個分支做出不同邏輯。它們是:
?
createIFrame方法的作用就是創建一個新的作為Proxy的iframe對象,加載的內容即為我們準備的Proxy頁面,請注意Proxy頁面還帶有QueryString,用于指定Proxy頁面的document.domain。我們也會響應iframe對象的onload事件。很幸運,這個事件被IE和FireFox瀏覽器都實現了——畢竟FireFox原本就是向IE拿來的iframe,有何道理實現的不同呢?
Sys.Net.XMLHttpExecutor.prototype._createIFrame = function() {var webRequest = this.get_webRequest();var rawDomain = webRequest._getRawDomain();var proxyUrl = rawDomain + "/SubDomainProxy.htm?" +webRequest._getDocDomain();var iframe = document.createElement('iframe');Sys.Net.XMLHttpExecutor._iframeCache[rawDomain] = iframe;iframe.style.display = "none";$addHandler(iframe, 'load',
Function.createDelegate(this, this._onIFrameLoadHandler)); iframe.src = proxyUrl;document.body.appendChild(iframe);return iframe; }
?
在iframe加載成功時,onIFrameLoadHandler方法會被調用。在這個方法里,我們將會通過查看Proxy方法有沒有被正確加載來判斷iframe里的Proxy有沒有被加載成功。如果沒有加載成功,則清除iframeCache字典中相應的iframe對象,并直接結束WebRequest請求:清除檢測超時的Timer,調用WebRequest對象的completed方法等等。如果加載成功了,則在iframeLoaded字典中標記這個iframe已經加載成功了,并且在該方法之后重新調用_crossDomainExecuteRequest方法——只要使用setTimeout再將時間設為0即可:
Sys.Net.XMLHttpExecutor.prototype._onIFrameLoadHandler = function() {var webRequest = this.get_webRequest();var rawDomain = webRequest._getRawDomain();var iframeCache = Sys.Net.XMLHttpExecutor._iframeCache;try{document.domain = webRequest._getDocDomain();if (iframeCache[rawDomain].contentWindow.sendRequest){Sys.Net.XMLHttpExecutor._iframeLoaded[rawDomain] = true;setTimeout(Function.createDelegate(this, this._crossDomainExecuteRequest),0);}else{throw new Error();}}catch(e){var iframe = iframeCache[rawDomain];document.body.removeChild(iframe);iframeCache[rawDomain] = null;this._clearTimer();webRequest.completed(Sys.EventArgs.Empty);} }?
最后我們只要再提供一個作為Proxy的頁面即可,我們必須把它放在每個子域名的根目錄下——當然您也可以改進之前_createIFrame的代碼,加載其他位置上的Proxy頁面。Proxy頁面的實現非常簡單,只要模仿XMLHttpExecutor原有的executeRequest方法實現即可:
<!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><title>Untitled Page</title><script type="text/javascript" language="javascript">var search = window.location.search;document.domain = search.substring(1);if (!window.XMLHttpRequest){window.XMLHttpRequest = function window$XMLHttpRequest(){var progIDs = [ 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP' ];for (var i = 0; i < progIDs.length; i++){try {var xmlHttp = new ActiveXObject(progIDs[i]);return xmlHttp;}catch (ex) {}}return null;}}function sendRequest(executor){executor._webRequest = executor.get_webRequest();if (executor._started){throw window.parent.Error.invalidOperation(window.parent.String.format(window.parent.Sys.Res.cannotCallOnceStarted,'executeRequest'));}if (executor._webRequest === null){throw window.parent.Error.invalidOperation(window.parent.Sys.Res.nullWebRequest);}var body = executor._webRequest.get_body();var headers = executor._webRequest.get_headers();
executor._xmlHttpRequest = new XMLHttpRequest();executor._xmlHttpRequest.onreadystatechange =
executor._onReadyStateChange;
var verb = executor._webRequest.get_httpVerb();executor._xmlHttpRequest.open(verb,
executor._webRequest.getResolvedUrl(), true);if (headers){for (var header in headers){var val = headers[header];if (typeof(val) !== "function")executor._xmlHttpRequest.setRequestHeader(header, val);}}if (verb.toLowerCase() === "post"){if ((headers === null) || !headers['Content-Type']){executor._xmlHttpRequest.setRequestHeader(
'Content-Type', 'application/x-www-form-urlencoded');}if (!body){body = "";}}executor._xmlHttpRequest.send(body);executor._started = true;}</script> </head> <body></body> </html>
?
到目前為止,這個CrossDomainExecutor就實現好了,我們現在使用WebRequst時就可以把它的URL設為不同子域名下的資源。例如,我們在http://www.test.com里可以請求http://sub.test.com或http://sub0.sub1.test.com里的資源。而且,如果不做跨子域名的請求,XMLHttpExecutor的行為和之前不會有任何不同。
?
點擊這里下載源碼。
轉載于:https://www.cnblogs.com/JeffreyZhao/archive/2007/02/05/Break_the_Browsers_Restrictions_7.html
總結
以上是生活随笔為你收集整理的挣脱浏览器的束缚(7) - CrossSubDomainExecutor的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一些面试题(JAVA)
- 下一篇: 不用代理实现弹出进度条窗体