原型 AJAX 请求作为 OPTIONS 而不是 GET 发送;导致 501 错误

IT技术 javascript ajax http-headers prototypejs
2021-02-22 04:47:38

我正在尝试使用 Prototype/AJAX 访问 Web 服务,但遇到了一个我无法弄清楚的错误:似乎当我向服务器发出请求时,我的请求被解释为 OPTIONS 而不是 GET 请求(并反过来抛出一个 501 - 未实现的错误,因为服务器只允许 GET 请求,根据我的理解Access-Control-Request-Method:)。我是否在 AJAX/请求公式中遗漏了可能导致此错误的内容?我在这里阅读了一些 CORS/预检请求但我不确定当我的代码看起来合规时它如何应用......

这是相关的 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:信息时,这些是请求中唯一允许的标头吗?

谢谢!

4个回答

在prototypejs上寻找正确修复的时间太多了……最后,我们在很棒的kourge(Wilson Lee)文章上有一个非侵入性的解决方案!。这是摘录:

大多数主要的 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; 
                }); 
            } 
        });

如果您发现此解决方案有任何缺点/改进,我们欢迎您分享:)

经过数小时的搜索,我找到了这个答案,这正是我所需要的:)
2021-05-08 04:47:38

实际上它是预检请求,因为 Prototype向请求添加了自定义标头X-Requested-With, X-Prototype-Version由于这些标头,浏览器会发送第一个OPTIONS请求。XHR 规范说:

对于使用 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()(我将在此处跳过此变体以缩短答案)。

实际上,使用 Prototype.js V1.7 更容易:

Ajax.Responders.register({
    onCreate:function(r){
        r.options.requestHeaders={
        'X-Prototype-Version':null,
        'X-Requested-With':null
        };
    }
});

如果其值为 null,Prototype.js 会删除任何预定义的标头。

我从来没有用过 Prototype,我不确定我会用多少。但是我快速浏览了文档,并没有看到对方法和参数的任何支持。

所以尝试:

new Ajax.Request(REQUEST_ADDRESS+"?stationString="+station_id, {
    onSuccess: displayMetar,
    onFailure: function() {
        $("errors").update("an error occurred");
    }
});

另外,我刚刚注意到您示例中的 stationString 应该用引号引起来,假设它不是变量。

嗯...这是一个延伸,但您可以尝试设置 Access-Control-Allow-Origin 标头。我看不出它有什么不同,因为它无论如何都来自同一个域……除此之外,我还没有想法,伙计。
2021-04-22 04:47:38
只是为了检查一下,REQUEST_ADDRESS 是相对/绝对 URL 吗?与“http://”不同。由于安全问题,现代浏览器似乎限制了跨域请求。在答案链接中进行了解释简而言之,请确保您正在执行请求的 REQUEST_ADDRESS 与呼叫来自的主机域相匹配
2021-04-27 04:47:38
啊,Prototype 绝对支持请求的方法和参数——我不确定问题出在那里(过去曾对请求使用过这种确切的语法,当然对于不同的参数/服务器略有变化)。我会继续玩弄语法......
2021-05-01 04:47:38
它确实是一个绝对 URL - 我尝试访问的 Web 服务位于请求发起的主机域之外。但是,我之前使用过其他 Web 服务(例如Last.fm API),其中跨域请求没有引起问题......为什么会这样(它是特定于服务器的)?如果我试图向另一台服务器发出请求,如何绕过同域请求的限制?根据您的链接(谢谢,顺便说一句),它看起来像标题操作...
2021-05-17 04:47:38
它是特定于服务器的——服务器必须设置一个允许跨源请求的标头。为了解决这个问题,您可以编写一个代理脚本,将信息来回传递给您的 AjaxRequest。如果需要,它还允许您操作或缓存它。维基百科文章CORS解释了一个简单的例子。
2021-05-20 04:47:38