Почему некоторые междоменные JSON-запросы терпят неудачу, а другие нет?

У меня есть небольшая проблема с пониманием безопасности JSON, потому что часто вещи, которые теоретически не должны работать, по-видимому, работают. Насколько я знаю, вызовы из скрипта на странице, которая находится в домене A, не должны иметь возможность получать данные из домена B. Но в приведенном ниже коде вызовы к одному внешнему домену терпят неудачу, тогда как другой проходит. И ни один из них не является упакованным вызовом JSON (jsonp).

Почему это? Не следует ли запретить обоим проходить проверку безопасности браузера? Я получаю те же результаты в Chrome и Firefox. Если я размещу приведенную ниже html-страницу на dropbox.com, Chrome выдаст мне это сообщение об ошибке:

XMLHttpRequest не может загрузить http://www.odinfond.no/rest/fund/calc/fundReturn?&id=300&oneTimeInvestment=100000&oneTimeInvestmentDate=2009-11-01&endDate=2010-11-01¤cy=NOK. Источник http://dl.dropbox.com не разрешен Access-Control-Allow-Origin.

Ответ JSON, который я получил бы, если бы звонок прошел, можно увидеть, щелкнув эта прямая ссылка. Вызов другой службы успешно возвращается. Я размещаю приведенный ниже код в Dropbox. Попробуйте здесь.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta http-equiv="Content-type" content="text/html;charset=UTF-8" />

  <title>JSON/JSONP test</title>
  <script src="jquery.js" type="text/javascript"></script>
 </head>

 <body>
  <script>
   service = 'http://www.odinfond.no/rest/fund/calc/fundReturn?'; 
   parameters = { 
     id: '300',
     oneTimeInvestment:'100000',
     oneTimeInvestmentDate:'2009-11-01',
     endDate:'2010-11-01',
     currency:'NOK'
    }
   $.getJSON( service, parameters, function(data) {
    alert("Success"); 
   });

   service = 'http://ws.geonames.org/postalCodeLookupJSON?'
   parameters = {
    postalcode:1540,
    country:'NO'
   }
   $.getJSON(service, parameters, function(data) {
    alert(data.postalcodes[0].adminName2);
   });
  </script>
  <p>Use Firebug to see JSON response</p>
 </body>
</html>

person oligofren    schedule 18.11.2010    source источник


Ответы (2)


Вы заметите, что рабочий запрос имеет заголовок ответа:

Access-Control-Allow-Origin: *

Это то, что освобождает браузер, чтобы сделать ответ доступным для скрипта. (Обратите внимание, что запрос всегда выполняется, одна и та же политика происхождения влияет только на то, доступен ли ответ для скрипта или нет)

Если '*' является именем хоста, доступ разрешен только в том случае, если имя хоста текущего документа соответствует Access-Control-Allow-Origin. заголовок

person Gareth    schedule 18.11.2010
comment
Спасибо, это имело смысл! Я думал, что запросы почти идентичны, но я никогда не замечал разницы в заголовке ответа :-) - person oligofren; 19.11.2010

При просмотре исходного кода оказывается, что $.ajax() обнаруживает удаленные URL-адреса и заменяет AJAX (XMLHttpRequest) старыми добрыми тегами script:

    // Build temporary JSONP function
    if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
        jsonp = s.jsonpCallback || ("jsonp" + jsc++);

        // Replace the =? sequence both in the query string and the data
        if ( s.data ) {
            s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
        }

        s.url = s.url.replace(jsre, "=" + jsonp + "$1");

        // We need to make sure
        // that a JSONP style response is executed properly
        s.dataType = "script";

        // Handle JSONP-style loading
        var customJsonp = window[ jsonp ];

        window[ jsonp ] = function( tmp ) {
            if ( jQuery.isFunction( customJsonp ) ) {
                customJsonp( tmp );

            } else {
                // Garbage collect
                window[ jsonp ] = undefined;

                try {
                    delete window[ jsonp ];
                } catch( jsonpError ) {}
            }

            data = tmp;
            jQuery.handleSuccess( s, xhr, status, data );
            jQuery.handleComplete( s, xhr, status, data );

            if ( head ) {
                head.removeChild( script );
            }
        };
    }

[...]

    // Matches an absolute URL, and saves the domain
    var parts = rurl.exec( s.url ),
        remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host);

    // If we're requesting a remote document
    // and trying to load JSON or Script with a GET
    if ( s.dataType === "script" && type === "GET" && remote ) {
        var head = document.getElementsByTagName("head")[0] || document.documentElement;
        var script = document.createElement("script");
        if ( s.scriptCharset ) {
            script.charset = s.scriptCharset;
        }
        script.src = s.url;

        // Handle Script loading
        if ( !jsonp ) {
            var done = false;

            // Attach handlers for all browsers
            script.onload = script.onreadystatechange = function() {
                if ( !done && (!this.readyState ||
                        this.readyState === "loaded" || this.readyState === "complete") ) {
                    done = true;
                    jQuery.handleSuccess( s, xhr, status, data );
                    jQuery.handleComplete( s, xhr, status, data );

                    // Handle memory leak in IE
                    script.onload = script.onreadystatechange = null;
                    if ( head && script.parentNode ) {
                        head.removeChild( script );
                    }
                }
            };
        }

        // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
        // This arises when a base node is used (#2709 and #4378).
        head.insertBefore( script, head.firstChild );

        // We handle everything using the script element injection
        return undefined;
    }
person Álvaro González    schedule 18.11.2010
comment
Самая первая строка этого фрагмента показывает, что это происходит только тогда, когда вы передаете {dataType:script} в вызов AJAX. - person Gareth; 19.11.2010
comment
Я добавил более ранний отрывок, где значение s.dataType изменилось. - person Álvaro González; 19.11.2010