挣脱浏览器的束缚(7) - CrossSubDomainExecutor
在上次的文章中,我們已經(jīng)提到了一種能夠跨子域名進(jìn)行AJAX請(qǐng)求的方法。我們現(xiàn)在就來(lái)實(shí)現(xiàn)一個(gè)對(duì)開(kāi)發(fā)人員透明的實(shí)現(xiàn),它會(huì)自動(dòng)判斷這個(gè)請(qǐng)求是否是跨子域名,如果不是,則使用傳統(tǒng)的方法發(fā)出AJAX請(qǐng)求,反之則使用我們的方式。
我在如何實(shí)現(xiàn)這個(gè)Executor的問(wèn)題上,我想了很久。按照ASP.NET AJAX的“標(biāo)準(zhǔn)”來(lái)說(shuō),應(yīng)該開(kāi)發(fā)一個(gè)WebRequestExecutor的子類,然后將其設(shè)為默認(rèn)的Executor或者某個(gè)特定WebRequest的Executor。但是最終我使用了直接修改XMLHttpExecutor的做法,因?yàn)榭紤]到以下原因:
- 完整實(shí)現(xiàn)一個(gè)WebRequestExecutor需要實(shí)現(xiàn)太多接口,而CrossSubDomainExecutor和XMLHttpExecutor有太多相同的地方。
- 如果繼承WebRequestExecutor的話,還是需要了解XMLHttpExecutor的太多細(xì)節(jié),JavaScript做不到太好的封裝,無(wú)法實(shí)現(xiàn)真正的面向?qū)ο蟆?
- CrossSubDomainExecutor在普通情況下和XMLHttpExecutor的行為一模一樣。
直接修改XMLHttpExecutor的做法其實(shí)就是在XMLHttpExecutor的prototype上做文章,這也就是JavaScript擴(kuò)展對(duì)象的一貫做法。
首先,我們定義一個(gè)Sys.Net.WebRequest類的靜態(tài)方法,用來(lái)從一個(gè)URL中獲得域名。例如輸入http://www.sample.com/Default.aspx這個(gè)URL,返回http://www.sample.com,這個(gè)靜態(tài)函數(shù)對(duì)于判斷是否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; }?
其次,我們?yōu)镾ys.Net.WebRequest類擴(kuò)展一個(gè),用于檢測(cè)請(qǐng)求能否直接發(fā)出,而不需要使用我們的方法。在這里我們直接使用了一個(gè)XMLHttpRequest對(duì)象作為嘗試:
Sys.Net.WebRequest.prototype._checkIfIsCrossDomainRequest = function() {var request = new XMLHttpRequest();try{request.open('get', this.get_url());return false;}catch(e){return true;} }?
我們還需要得到WebRequest的兩個(gè)特性:它的Document Domain(設(shè)為document.domain的值),以及它本身的域名(Raw Domain),我們依舊為Sys.Net.Request擴(kuò)展方法:
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,最關(guān)鍵的一步就是要重新實(shí)現(xiàn)executeRequest方法。很顯然,再這之前,我們需要保留原來(lái)的實(shí)現(xiàn):
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();} }?
接下來(lái),就需要實(shí)現(xiàn)一個(gè)跨子域名發(fā)出AJAX請(qǐng)求方法了。再這之前,需要對(duì)于XMLHttpExecute本身的實(shí)現(xiàn)有所了解。我們現(xiàn)在就對(duì)添加的方法進(jìn)行簡(jiǎn)單的分析。
首先,自然是_crossDomainExecuteRequest方法。我們準(zhǔn)備了兩個(gè)字典:_iframeCache和_iframeLoaded,分別用于保存作為Proxy的iframe對(duì)象,以及表明iframe對(duì)象是否已經(jīng)加載成功了(iframe加載也是異步的,也需要一定時(shí)間)。我們先模仿XMLHttpExecutor的實(shí)現(xiàn),建立一個(gè)偵測(cè)是否超時(shí)的監(jiān)聽(tīng)器;再使用該請(qǐng)求的Raw Domain作為key,經(jīng)過(guò)下面判斷的三個(gè)分支做出不同邏輯。它們是:
?
createIFrame方法的作用就是創(chuàng)建一個(gè)新的作為Proxy的iframe對(duì)象,加載的內(nèi)容即為我們準(zhǔn)備的Proxy頁(yè)面,請(qǐng)注意Proxy頁(yè)面還帶有QueryString,用于指定Proxy頁(yè)面的document.domain。我們也會(huì)響應(yīng)iframe對(duì)象的onload事件。很幸運(yùn),這個(gè)事件被IE和FireFox瀏覽器都實(shí)現(xiàn)了——畢竟FireFox原本就是向IE拿來(lái)的iframe,有何道理實(shí)現(xiàn)的不同呢?
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加載成功時(shí),onIFrameLoadHandler方法會(huì)被調(diào)用。在這個(gè)方法里,我們將會(huì)通過(guò)查看Proxy方法有沒(méi)有被正確加載來(lái)判斷iframe里的Proxy有沒(méi)有被加載成功。如果沒(méi)有加載成功,則清除iframeCache字典中相應(yīng)的iframe對(duì)象,并直接結(jié)束WebRequest請(qǐng)求:清除檢測(cè)超時(shí)的Timer,調(diào)用WebRequest對(duì)象的completed方法等等。如果加載成功了,則在iframeLoaded字典中標(biāo)記這個(gè)iframe已經(jīng)加載成功了,并且在該方法之后重新調(diào)用_crossDomainExecuteRequest方法——只要使用setTimeout再將時(shí)間設(shè)為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);} }?
最后我們只要再提供一個(gè)作為Proxy的頁(yè)面即可,我們必須把它放在每個(gè)子域名的根目錄下——當(dāng)然您也可以改進(jìn)之前_createIFrame的代碼,加載其他位置上的Proxy頁(yè)面。Proxy頁(yè)面的實(shí)現(xiàn)非常簡(jiǎn)單,只要模仿XMLHttpExecutor原有的executeRequest方法實(shí)現(xiàn)即可:
<!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>
?
到目前為止,這個(gè)CrossDomainExecutor就實(shí)現(xiàn)好了,我們現(xiàn)在使用WebRequst時(shí)就可以把它的URL設(shè)為不同子域名下的資源。例如,我們?cè)趆ttp://www.test.com里可以請(qǐng)求http://sub.test.com或http://sub0.sub1.test.com里的資源。而且,如果不做跨子域名的請(qǐng)求,XMLHttpExecutor的行為和之前不會(huì)有任何不同。
?
點(diǎn)擊這里下載源碼。
轉(zhuǎn)載于:https://www.cnblogs.com/JeffreyZhao/archive/2007/02/05/Break_the_Browsers_Restrictions_7.html
總結(jié)
以上是生活随笔為你收集整理的挣脱浏览器的束缚(7) - CrossSubDomainExecutor的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 一些面试题(JAVA)
- 下一篇: 【原】HTML页面元素加载顺序研究报告(