Прототип AJAX-запроса отправляется как ОПЦИИ, а не GET; приводит к ошибке 501

Я пытаюсь получить доступ к веб-сервису с помощью Prototype/AJAX и запутался в ошибке, которую я не могу понять: кажется, что когда я делаю запрос на сервер, мой запрос интерпретируется как ОПЦИИ, а не GET (и, в свою очередь, выдает ошибку 501 - не реализована, поскольку сервер разрешает только запросы GET, исходя из того, что я понимаю из Access-Control-Request-Method:). Я что-то пропустил в своей формулировке AJAX/request, которая может вызвать эту ошибку? Я читал немного в запросах CORS/preflighted здесь, но я не уверен, как это можно применить, когда мой код выглядит совместимым...

Здесь соответствующий запрос 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 говорит, что запрос отправляется как ОПЦИИ, а не GET? Когда Chrome выдает информацию Access-Control-Request-Headers:, являются ли они исключительно единственными заголовками, разрешенными в запросе?

Спасибо!

Ответ 1

Слишком много часов ищет правильное исправление на prototypejs... наконец, у нас есть неинтрузивное решение на отличная статья (Wilson Lee)!. Вот выдержка:

Большинство основных фреймворков Ajax, например, для установки пользовательских HTTP-заголовков в запросах Ajax, которые вы создаете; самый популярный заголовок - X-Requested-With: XMLHttpRequest. Следовательно, ваш запрос продвигается до предполного и не выполняется. Исправление состоит в том, чтобы запретить вашей инфраструктуре JavaScript устанавливать эти пользовательские заголовки, если ваш запрос является междоменным. jQuery уже умело избегает непреднамеренных предвыборных запросов, не устанавливая настраиваемые заголовки, если ваш URL-адрес считается удаленным. Вам придется вручную предотвратить это, если вы используете другие фреймворки.

Это может быть так просто:

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; 
                }); 
            } 
        });

Если вы видите какой-либо недостаток/улучшение этого решения, мы будем рады вам поделиться:)

Ответ 2

Фактически это предпросмотр запроса, потому что Prototype добавляет пользовательские заголовки X-Requested-With, X-Prototype-Version к запросу. Из-за этих заголовков браузер отправляет первый запрос OPTIONS. Спецификация XHR говорит:

Для запросов одного и того же происхождения, использующих метод HTTP GET, запрашивается предполетный запрос, когда установлены заголовки, отличные от Accept и Accept-Language.

Как решить эту проблему? Я вижу только одну возможность решить эту проблему как можно скорее: полностью перезаписать метод Ajax.Request#setRequestHeaders(), например. вставьте этот script сразу после 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() (здесь я пропущу этот вариант, чтобы сделать ответ короче).

Ответ 3

Собственно, это намного проще с Prototype.js V1.7:

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

Prototype.js удаляет любой предварительно определенный заголовок, если его значение равно null.

Ответ 4

Я никогда не использовал Prototype, и я не уверен, насколько я буду использовать. Но я быстро просмотрел документы, и я не видел поддержки методов и параметров.

Итак, попробуйте:

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

Также я заметил, что stationString в вашем примере должна быть в кавычках, предполагая, что это не переменная.