欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 前端技术 > HTML >内容正文

HTML

挣脱浏览器的束缚(7) - CrossSubDomainExecutor

发布时间:2025/7/25 HTML 59 豆豆
生活随笔 收集整理的这篇文章主要介绍了 挣脱浏览器的束缚(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,经过下面判断的三个分支做出不同逻辑。它们是:

  • 如果iframeCache中没有相应的iframe对象,则调用_createIFrame方法创建一个作为Proxy的iframe。
  • 如果已经存在iframe对象,但是Proxy页面还没有加载完毕,则等待500毫秒后重新尝试着调用_crossDomainExecuteRequest方法。
  • 如果Proxy已经加载成功了,并且用户没有调用abort方法,请求也没有过超时时间,则将document.domain设为正确值,并且调用Proxy页面里的方法发出AJAX请求。
  • Sys.Net.XMLHttpExecutor._iframeCache = {}; Sys.Net.XMLHttpExecutor._iframeLoaded = {};Sys.Net.XMLHttpExecutor.prototype._crossDomainExecuteRequest = function() {var webRequest = this.get_webRequest();var rawDomain = webRequest._getRawDomain();var iframeCache = Sys.Net.XMLHttpExecutor._iframeCache;var timeout = webRequest.get_timeout();if (timeout > 0){this._timer = window.setTimeout(this._onTimeout, timeout);}if (!iframeCache[rawDomain]){this._createIFrame();}else if (!Sys.Net.XMLHttpExecutor._iframeLoaded[rawDomain]){setTimeout(Function.createDelegate(this, this._crossDomainExecuteRequest),500);}else if (!this.get_aborted() && !this.get_timedOut()){document.domain = webRequest._getDocDomain();iframeCache[rawDomain].contentWindow.sendRequest(this);} }

     

    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的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。