我正在尝试使用Prototype / AJAX访问Web服务并遇到一个我无法弄清楚的错误:似乎当我向服务器发出请求时,我的请求被解释为OPTIONS而不是GET请求(反过来抛出501 - 未实现的错误,因为服务器只允许GET请求,基于我对Access-Control-Request-Method:
的理解)。我在AJAX /请求公式中遗漏了可能导致此错误的内容吗?我已经阅读了一些CORS / preflighted请求here,但我不确定当我的代码看起来合规时它是如何应用的......
以下是相关的AJAX请求:
function fetchMetar() {
var station_id = $("station_input").value;
new Ajax.Request(REQUEST_ADDRESS, {
method: "get",
parameters: {stationString: station_id},
onSuccess: displayMetar,
onFailure: function() {
$("errors").update("an error occurred");
}
});
}
这是我从Chrome获得的错误和相关的请求信息:
Request URL:http://weather.aero/dataserver_current/httpparam?
dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3
&mostRecent=true&stationString=&stationString=KSBA
Request Method:OPTIONS
Status Code:501 Not Implemented
Request Headers
Accept:*/*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:origin, x-prototype-version, x-requested-with, accept
Access-Control-Request-Method:GET
Connection:keep-alive
Host:weather.aero
Origin:http://domain.com
Referer:http://domain.com/.../...html
我可以在这里俯瞰什么?为什么Chrome会将请求作为OPTIONS而不是GET发送?当Chrome推出Access-Control-Request-Headers:
信息时,这些信息是否仅是请求中允许的唯一标头?
谢谢!
答案 0 :(得分:15)
在prototypejs上寻找正确修复的时间太长了......最后,我们在great kourge (Wilson Lee) article上有一个非侵入式解决方案!这是一段摘录:
大多数主要的Ajax框架都喜欢在Ajax上设置自定义HTTP头请求你实例化;最受欢迎的标题是X-Requested-With:XMLHttpRequest。因此,您的请求将被提升为预检请求并失败。 如果您的请求是跨域请求,则修复是为了防止您的JavaScript框架设置这些自定义标头。如果您的URL被认为是远程的,jQuery已经巧妙地避免了因为没有设置自定义标头而无意中预检请求。如果您正在使用其他框架,则必须手动阻止此操作。
它可以很简单:
new Ajax.Request('http://www.external-domain.net/my_api.php?getParameterKey=getParameterValue', {
method:'post',
contentType:"application/x-www-form-urlencoded",
postBody:'key=' + value,
onSuccess: function(response) {
// process response
},
onCreate: function(response) { // here comes the fix
var t = response.transport;
t.setRequestHeader = t.setRequestHeader.wrap(function(original, k, v) {
if (/^(accept|accept-language|content-language)$/i.test(k))
return original(k, v);
if (/^content-type$/i.test(k) &&
/^(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)(;.+)?$/i.test(v))
return original(k, v);
return;
});
}
});
如果您发现此解决方案有任何不利/改进,我们欢迎您分享:)
答案 1 :(得分:6)
实际上它是preflight request,因为Prototype会向请求添加自定义标头X-Requested-With, X-Prototype-Version。由于这些标头,浏览器会发送第一个OPTIONS
请求。 XHR spec说:
对于使用HTTP GET方法的非同一原始请求,当设置了Accept和Accept-Language之外的标题时,会产生预检请求。
如何解决这个问题?我只能看到解决这个问题的一种可能性:完全覆盖方法Ajax.Request#setRequestHeaders()
,例如在Prototype.js之后插入此脚本:
Ajax.Request.prototype.setRequestHeaders = function() {
var headers = {
// These two custom headers cause preflight request:
//'X-Requested-With': 'XMLHttpRequest',
//'X-Prototype-Version': Prototype.Version,
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
};
if (this.method == 'post') {
headers['Content-Type'] = this.options.contentType +
(this.options.encoding ? '; charset=' + this.options.encoding : '');
/* Force "Connection: close" for older Mozilla browsers to work
* around a bug where XMLHttpRequest sends an incorrect
* Content-length header. See Mozilla Bugzilla #246651.
*/
if (this.transport.overrideMimeType &&
(navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
headers['Connection'] = 'close';
}
if (typeof this.options.requestHeaders == 'object') {
var extras = this.options.requestHeaders;
if (Object.isFunction(extras.push))
for (var i = 0, length = extras.length; i < length; i += 2)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value; });
}
for (var name in headers)
this.transport.setRequestHeader(name, headers[name]);
}
此修补程序从任何AJAX请求中删除自定义标头。如果您仍然需要这些标头用于非CORS请求,可能会添加更多逻辑,这可以在new Ajax.Request()
的选项中禁用这些标头(我将在此处跳过此变体以使答案更短)。 / p>
答案 2 :(得分:1)
实际上,使用Prototype.js V1.7会更容易:
Ajax.Responders.register({
onCreate:function(r){
r.options.requestHeaders={
'X-Prototype-Version':null,
'X-Requested-With':null
};
}
});
如果Prototype.js的值为null,则会丢弃任何预定义的标头。
答案 3 :(得分:0)
我从未使用过Prototype,我不确定它会有多大用处。但我快速查看了文档,但我没有看到对方法和参数的任何支持。
所以试试:
new Ajax.Request(REQUEST_ADDRESS+"?stationString="+station_id, {
onSuccess: displayMetar,
onFailure: function() {
$("errors").update("an error occurred");
}
});
另外我只是注意到你的例子中的stationString应该在引号中,假设它不是变量。