Кроссдоменный AJAX. In action

В последнее время никак не мог разгадать одну загадку, касающуюся объекта XMLHttpRequest, ну или - XHR. В моем случае на локальном диске располагалась html - страничка, которая при определенном событии (нажатии кнопочки Send) создавала "фоновый" запрос к веб-серверу. Для этого использован асинхронный Javascript (AJAX). Также данная страничка была размещена на веб - сервере в соответствующей корневой директории.

Для тестирования воспользовался нетрадиционным веб - сервером автора Максима Калабзина - Ascet HTTPd (http://ascethttpd.ruall.org/). Связано это с тем, что для меня важно было видеть весь код, дабы при случае можно было самому отлавливать те или иные баги. На текущий момент доступна версия Ascet HTTPd v.1.4. Она написана на C, из скриптовой поддержки - cgi, хотя при желании можно прикрутить и покруче функционал. Развернул данный веб - сервер на виртаулке (Linux Ubuntu Server v.12.04 32bit).

Загадка состояла в том, что  "фоновый" запрос к веб-серверу в свойствах объекта XMLHttpRequest - responseText и responseXML, в Internet Explorer (v.8) возвращались с ожидаемым содержимым, в то время как FireFox (v.11) возвращал либо ничего, либо null. Так происходило только если данный запрос отправлялся с локальной странички. Если же обращение происходило непосредственно к веб - серверу, который подгружал туже страницу, то запрос возвращал в обоих случаях все так, как и полагалось.
Сравним относительные адреса этих страниц:
- локальное расположение file:///C:/index.htm
- на веб - сервере http://127.0.0.1/index.htm
Отсюда видно, что они в своем роде - сайты с разных доменов. Это я клоню к так называемой безопасности браузера FireFox - "Same Origin Policy" (Политика одного источника), то есть каждому сайту своя песочница, чтобы свести к минимуму их взаимодействие. Например об этом написано на сайте Mozilla Developer Network:

Cross-site HTTP requests initiated from within scripts have been subject to well-known restrictions, for well-understood security reasons.  For example HTTP Requests made using the XMLHttpRequest object were subject to the same-origin policy.  In particular, this meant that a web application using XMLHttpRequest could only make HTTP requests to the domain it was loaded from, and not to other domains.  Developers expressed the desire to safely evolve capabilities such as XMLHttpRequest to make cross-site requests, for better, safer mash-ups within web applications.

К тому же каждый запрос анализировался (громко сказано) с помощью плагина FireFox - LiveHTTPHeaders v.017 (http://livehttpheaders.mozdev.org/). Откуда было замечено, что браузер отправляет в сторону веб - сервера http - заголовок "Origin". Который указывает на источник запроса, то есть место, откуда посылается запрос. В случае с локальной страницей - Origin указывал на null. В случае веб - сервера - http://127.0.0.1/index.htm
И снова мысли наводили на "Same Origin Policy". Все точки над "и" расставила статья на хабре - "Кроссдоменный AJAX" (http://habrahabr.ru/post/114432/). Вот ее текст:

На вопрос, как сделать AJAX запрос к другому домену, я всегда отвечал, что никак, и предлагал в качестве альтернативы jsonp, прокси, флеш, фреймы. Но, оказывается, большинство современных браузеров (IE8+, FF3.5+, Chrome 6+ и Safari 4+) вполне поддерживает кроссдоменный XMLHTTPRequest.

С клиентской стороны все остается без изменений. Только теперь браузер не блокирует запрос при отправке, а добавляет к нему заголовок с именем домена, откуда делается запрос: Origin: example.com

Ответ от сервера он так просто назад не пропускает, сервер должен добавить специальный заголовок:
Access-Control-Allow-Origin: *
Вместо звездочки сервер может указать конкретный домен, которому разрешено получить ответ.

Вот такое элегантное решение. Без лишних HTTP запросов, без изменения клиентского API, без нарушения безопасности существующих приложений (ведь подгрузить картинку с другого домена или отправить форму в ифрейм можно было и раньше), и наконец, в отличие от Флеша, с гибкой настройкой прав доступа к индивидуальным страницам.


Как только в ответы веб - сервера было заложено то, что здесь рекомендовано: добавлен специальный заголовок:

Access-Control-Allow-Origin: *

FireFox отобразил то, чего так долго не мог от него добиться. Действительно - элегантное решение. Как следствие данный подход можно реализовать на уровне скриптов.
Например, - в cgi скрипте:

// Some code
printf("Access-Control-Allow-Origin: *\r\n");
printf("\r\n");
// Some code

- в php: 

 // Some code
header("Access-Control-Allow-Origin: *");
// Some code

   
Под конец приведу код той самой html - страницы, которую то и дело тропошил во время решения данной головоломки. Удачи нам в этом нелегком деле :)

<html>
    <head>
        <title>Testing web page...</title>
        <script language="javascript" type="text/javascript">
            function createRequest() {
                var request = null;
                try {
                    // Mozilla, FireFox, Safari, Opera и последние версии IE
                    request = new XMLHttpRequest();
                }     catch (trymicrosoft) {
                    try {
                        // большинство IE
                        request = new ActiveXObject("Msxml2.XMLHTTP");
                    }    catch (othermicrosoft) {
                        try {
                            // для некоторых IE
                            request = new ActiveXObject("Microsoft.XMLHTTP");
                        }    catch (failed) {
                            request = null;
                            }
                        }
                    }  
                // Проверяем на успешность создание объекта request
                if (request == null) {
                    alert("Error creating request object!");
                } else {
                    return request;
                }
            }
      
            function getResponse() {
                var myrequest = createRequest();
                // URL сценарий на сервере
                var url = "http://127.0.0.1";
                url = url + "?dummy=" + new Date().getTime();
                myrequest.open("GET", url, true);
                myrequest.send(null);
                myrequest.onreadystatechange = catchIT(myrequest);              
            }
      
            function catchIT(myrequest) {
              
                if(myrequest.readyState == 0) {
                    alert("0");
                }
                if(myrequest.readyState == 1) {
                    alert("1");
                }
                 if(myrequest.readyState == 2) {
                    alert("2");
                }
                 if(myrequest.readyState == 3) {
                    alert("3");
                }
                if(myrequest.readyState == 4) {
                    alert(myrequest.responseText);
                }              
            }
        </script>
    </head>
    <body>
        <form method="GET" action="#">
            <p id="inname">Type your name:</p>
            <p><input type="text" size="10" name="usname" id="usname" /></p>
            <!-- getResponse() -->
            <p><input value="Send" type="button" onClick="getResponse();"/></p>          
        </form>
    </body>
</html>