我正在尝试基于SproutCore 1.8构建一个Web应用程序。为了从服务器检索数据,应用程序向在单独域上运行的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错误“Access Denied”。此行引发错误:
// initiate request.
rawRequest.open(this.get('type'), this.get('address'), async);
经过一些简短的调查后,我发现Microsoft在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是我正在使用的现有服务器基础结构的要求。我既不能将服务放在与提供客户端的服务器相同的域上,也不能用反向代理或类似的基础设施来解决问题。
答案 0 :(得分:2)
您有两个选择
1)Hack Sproutcore本身。您可以尝试添加
function() {return new XDomainRequest(); }
到xhr交通工具的差异清单。先添加它;如果它在浏览器上可用,它将被使用,否则代码将回退到其他对象。
2)如果您不想修改SC源,可以创建自己的App.CorsResponse
扩展SC.XHRResponse
的类。在您的实现中,提供您自己的createRequest
方法并执行我在1中所述的操作。无论何时创建请求,请指定responseClass
作为自定义实现。
答案 1 :(得分: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;
}
});