【问题标题】:Cross origin request with SproutCore in IE9在 IE9 中使用 SproutCore 进行跨域请求
【发布时间】:2012-05-05 00:43:59
【问题描述】:

我正在尝试构建一个基于 SproutCore 1.8 的网络应用程序。为了从服务器检索数据,应用程序向在单独域上运行的 REST Web 服务发出 CORS 请求。

此代码如下所示:

var request = SC.Request.getUrl('http://example.com/some/path');
request.set('attachIdentifyingHeaders', NO);    
request.json().notify(this, this.didFetchData).send();

这在 Chrome、Safari 和 Firefox 中效果很好,但在 Internet Explorer 9 中不起作用。事实上,IE9 在 SproutCore 的内部请求实现中遇到了 JavaScript 错误“拒绝访问”。在这一行引发了错误:

// initiate request.
rawRequest.open(this.get('type'), this.get('address'), async);

经过一些简短的调查,我发现微软在 IE9 中为 CORS 请求实现了一个专用的 XDomainRequest 对象。 SproutCore 似乎不支持这一点,正如我从这些行中推断的那样(SproutCore 本机请求实现选择):

return tryThese(
  function() { return new XMLHttpRequest(); },
  function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
  function() { return new ActiveXObject('Microsoft.XMLHTTP'); }
);

这是 SproutCore 的缺点还是我遗漏了什么?如果是这样,您对我如何在不编写自己的请求抽象的情况下解决该问题有什么建议吗?

请注意,CORS 是我正在使用的现有服务器基础架构的要求。我既不能将服务与交付客户端的服务器放在同一个域中,也不能使用反向代理或类似的基础设施解决问题。

【问题讨论】:

    标签: ajax xmlhttprequest sproutcore cors


    【解决方案1】:

    我已经根据 hvgotcodes 的建议快速编写了一个解决方案。下面的代码实现了SC.XHRResponse 的子类,它添加了处理特定于 IE 的 XDomainRequest CORS 请求所需的功能。请注意,当使用 XDomainRequest 时,这还不能处理 IE 中的错误。

    MyApp.CorsRequest = SC.XHRResponse.extend({
      createRequest: function() {
        function tryThese() {
          for (var i=0; i < arguments.length; i++) {
            try {
              var item = arguments[i]();
              return item;
            } catch (e) {}
          }
          return NO;
        }
    
        return tryThese(
          function() { return new XDomainRequest(); },
          function() { return new XMLHttpRequest(); },
          function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
          function() { return new ActiveXObject('Microsoft.XMLHTTP'); }
        );
      },
    
      invokeTransport: function() {
        var rawRequest, transport, handleReadyStateChange, async, headers;
    
        rawRequest = this.createRequest();
        this.set('rawRequest', rawRequest);
    
        // configure async callback - differs per browser...
        async = !!this.getPath('request.isAsynchronous');
    
        if (async) {
          if (!SC.browser.isIE && !SC.browser.isOpera) {
            SC.Event.add(rawRequest, 'readystatechange', this,
                         this.finishRequest, rawRequest);
          } else if(SC.browser.isIE) {
               transport = this;
            handleLoad = function() {
              if (!transport) { return null; }
              var ret = transport.finishRequest();
              if (ret) { transport = null; }
              return ret;
            };
            rawRequest.onload = handleLoad;          
          } else {
            transport = this;
            handleReadyStateChange = function() {
              if (!transport) { return null; }
              var ret = transport.finishRequest();
              if (ret) { transport = null; }
              return ret;
            };
            rawRequest.onreadystatechange = handleReadyStateChange;
          }
        }
    
        // initiate request.
        rawRequest.open(this.get('type'), this.get('address'), async);
    
        // now send the actual request body - for sync requests browser will
        // block here
        rawRequest.send(this.getPath('request.encodedBody')) ;
        if (!async) { this.finishRequest(); }
    
        return rawRequest;
      }, 
    
      finishRequest: function(evt) {
        var rawRequest = this.get('rawRequest'),
            readyState = rawRequest.readyState,
            error, status, msg;
    
           if (SC.browser.isIE) {
                readyState = 4;
                rawRequest.status = 200;
           }       
    
        if (readyState === 4 && !this.get('timedOut')) {
          this.receive(function(proceed) {
            if (!proceed) { return; }
    
            // collect the status and decide if we're in an error state or not
            status = -1;
            try {
              status = rawRequest.status || 0;
            } catch (e) {}
    
            // if there was an error - setup error and save it
            if ((status < 200) || (status >= 300)) {
    
              try {
                msg = rawRequest.statusText || '';
              } catch(e2) {
                msg = '';
              }
    
              error = SC.$error(msg || "HTTP Request failed", "Request", status);
              error.set("errorValue", this) ;
              this.set('isError', YES);
              this.set('errorObject', error);
            }
    
            // set the status - this will trigger changes on related properties
            this.set('status', status);
          }, this);
    
          // Avoid memory leaks
          if (!SC.browser.isIE && !SC.browser.isOpera) {
            SC.Event.remove(rawRequest, 'readystatechange', this, this.finishRequest);
          } else {
               if (window.XDomainRequest)
                    rawRequest.onload = null;
               else
                 rawRequest.onreadystatechange = null;
          }
    
          return YES;
        }
        return NO;
      } 
    });
    

    【讨论】:

      【解决方案2】:

      你有两个选择

      1) 破解 Sproutcore 本身。您可以尝试添加

      function() {return new XDomainRequest(); }

      到那个不同的 xhr 交通工具列表。先添加;如果它在浏览器上可用,它将被使用,否则代码回退到其他对象。

      2) 如果您不想修改 SC 源代码,您可以创建自己的 App.CorsResponse 类来扩展 SC.XHRResponse。在您的实现中,提供您自己的 createRequest 方法并按照我在 1) 中所说的操作。每当您创建请求时,将 responseClass 指定为您的自定义实现。

      【讨论】:

      • 好的,谢谢,这正是我所期待的。选项 1 之前出现在我的脑海中,但我认为这是我应该做的最后一件事。我会尝试使用选项 2。
      • 事实证明,这两个选项最终归结为相同的结果。您不仅必须重写 createRequest,还必须重写 invokeTransport 和 finishRequest。这甚至不包括错误处理,并且意味着大量重复的代码。我认为 SC.Response 类需要更多的抽象才能使一个干净的解决方案成为可能。 XDomainRequest 没有相同的属性和回调,例如您需要实现 onload 而不是 onreadystatechange 并且需要使用不同的回调检查错误。只要 stackoverflow 允许我回答我自己的问题,我就会发布代码。
      猜你喜欢
      • 1970-01-01
      • 2012-08-17
      • 2023-03-17
      • 2015-10-13
      • 2013-05-07
      • 2011-10-20
      • 2012-07-29
      • 1970-01-01
      相关资源
      最近更新 更多