Джино: хостинг и веб-сервисы

Система Orphus
Russian version
Добавить на Del.icio.us
English version
Добавить на Digg.com

 dkLab | Конструктор | JsHttpRequest 5: кроссбраузерный AJAX + закачка файлов | Полная документация 

Карта сайта :: Форум «Лаборатории» :: Проект «Денвер»
Проект «Orphus» :: Куроводство: наблы :: Конструктор


2006-07-29
Обсудить на форуме

В данной статье описывается библиотека JsHttpRequest (лицензия LGPL), реализующая загрузку данных методом AJAX (Remote Scripting). Вот краткий перечень ее ключевых возможностей и отличий от аналогов:

  • Кроссбраузерность. Библиотека работает в IE5.0+, Mozilla 1.7+, Firefox 1.0+, Opera 7.20+, Safari (здесь "+" означает "в этой и более новых версиях"). Кроме того, код может работать без поддержки ActiveX и XMLHttpRequest (однако, если эти возможности включены в браузер, они автоматически задействуются). Кроссбраузерность гарантируется автоматическим framework-ом для тестирования библиотеки.
  • Поддержка и "прозрачная" работа с любыми кодировками (в том числе русскоязычными). Вы можете писать скрипты так, как привыкли к этому раньше, обо всем остальном позаботится библиотека. Если вы используете windows-1251, koi8-r или UTF-8, вам даже нет необходимости инсталлировать какие-либо дополнительные расширения PHP (типа iconv или mbstring).
  • Закачка файлов (upload) из браузера на сервер без перезагрузки страницы.
  • Совместимость с библиотекой prototype. Prototype — это популярное средство для упрощения работы JavaScript-программиста, включающее поддержку AJAX и другие возможности. Библиотека JsHttpRequest может быть использована в качестве ее серверной PHP-части (после подключение небольшого модуля совместимости JsHttpRequest-prototype.js). При этом все дополнительные возможности, присущие JsHttpRequest (кроссбраузерность, закачка файлов, работа с русскими кодировками и т. д.), остаются в силе.
  • Полная поддержка отладочных возможностей PHP. Если в скрипте на серверной стороне происходит ошибка (включая фатальную, например, вызов неопределенной функции), клиентская часть имеет возможность корректно ее обработать и вывести диагностику. Помимо данных ответа, ей передается выходной поток скрипта, содержащий текст ошибки PHP.
  • Передача многомерных структур (аналог JSON) в данных запроса и ответа сервера. При этом используются стандартные средства PHP — многомерные массивы (данные запроса можно получить из $_REQUEST, данные ответа записываются в $_RESULT), а также стандартные средства JavaScript — вложенные объекты и свойства. Никакого XML на уровне API: работа происходит "родными" средствами языков.
  • Поддержка сессий PHP стандартными средствами.
  • Выбор оптимального метода загрузки данных (XMLHttpRequest, Microsoft.XMLHTTP, <SCRIPT>, <IFRAME>) в зависимости от браузера. В частности, возможность загружать данные с других хостов.
  • Компонентность библиотеки позволяет отключить ненужные методы загрузки и тем самым сократить объем JavaScript-кода. Например, если вы не планируете закачивать файлы AJAX-ом, вы можете выбрать версию с поддержкой только XML- и SCRIPT-загрузчиков.
  • Интерфейс, совместимый с XMLHttpRequest.

Введение

Не так давно определенную популярность получил новый сервис Google: так называемый Google Suggest. Те, кто еще не видел, что это такое, могут посмотреть прямо сейчас: http://www.google.com/webhp?complete=1&hl=en.

Пример автодополнения запроса в Google Suggest

Чайник 

Работа Google Suggest заключается в том, что по нескольким введенным буквам специальная программа на JavaScript обращается к сайту Google и запрашивает у него 10 самых "популярных" слов, начинающихся с тех же букв. Скрипт срабатывает настолько быстро, что выпадающий список с вариантами появляется практически мгновенно. Естественно, перезагрузка страницы при этом не производится — все реализовано на JavaScript и DHTML.

Для реализации "динамической подгрузки" Google использует следующие средства:

  1. В Internet Explorer: ActiveX-компонента с именем Msxml2.XMLHTTP или Microsoft.XMLHTTP.
  2. В Mozilla и Firefox: встроенный класс XMLHttpRequest.
  3. В Opera: динамически создаваемый <IFRAME> нулевого размера (скрытый).

Про то, как работает Google Suggest, в Интернете пишут все, кому не лень, и я совершенно не собираюсь повторяться. Вместо этого я представлю другой подход под названием JsHttpRequest, несколько обходящий Google Suggest по совместимости с различными браузерами и гибкости использования.

Метод, который реализует динамическую подгрузку в Google Suggest, проиллюстрирован ниже на примере загрузки исходного текста текущей страницы. (Работу с <IFRAME> я здесь не привожу, потому что она довольно сложна. Речь идет только о классе XMLHttpRequest и ActiveX-компоненте Microsoft.XMLHTTP).

Листинг 1: test/JsHttpRequest/ActiveX.htm
<script>
function doLoad() {
  var req = window.XMLHttpRequest? 
    new XMLHttpRequest() : 
    new ActiveXObject("Microsoft.XMLHTTP");
  req.onreadystatechange = function() {
    if (req.readyState == 4) 
      alert('Loaded:\n'+req.responseText);
  }
  req.open("GET", document.location, true);
  req.send(null);
}
</script>
<input type="button" value="Show me" onclick="doLoad()">

Этот код будет работать только в Mozilla (Firefox), а также в IE (при включенных ActiveX). Opera 7.x, а также пользователи, выключившие себе ActiveX по соображениям безопасности, "отдыхают".

Использование библиотеки

Чтобы вам не было чересчур скучно, я сразу демонстрирую библиотеку JsHttpRequest "в действии". Введите несколько слов в текстовом поле внизу — через 2 секунды появятся результаты поиска по этим словам на форуме http://forum.dklab.ru. Страница при этом перезагружена не будет.

Чайник 

Код подгрузки данных с форума для первого примера, пожалуй, слишком сложен. Поэтому далее мы разберем более простой код, специально написанный для облегчения понимания.

JavaScript: объект JsHttpRequest (frontend)

Использовать объект JsHttpRequest в JavaScript-программе совсем просто. Собственно, его интерфейс практически не отличается от интерфейсов Firefox-овского XMLHttpRequest или IE-шного Microsoft.XMLHTTP (он специально так разрабатывался).

Приведу пример страницы, которая обеспечивает генерацию хэш-кода MD5 для введенной пользователем строки. Само вычисление происходит на сервере, а браузер лишь обращается к последнему за данными, используя объект JsHttpRequest. (Этот же пример в действии.)

Листинг 2: test/JsHttpRequest/smpl_frontend.htm
<script src="../../lib/JsHttpRequest/JsHttpRequest.js"></script>
<script type="text/javascript" language="JavaScript">
function doLoad(value) {
    // Create new JsHttpRequest object.
    var req = new JsHttpRequest();
    // Code automatically called on load finishing.
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            // Write result to page element (_RESULT becomes responseJS). 
            document.getElementById('result').innerHTML = 
                '<b>MD5("'+req.responseJS.q+'")</b> = ' +
                '"' + req.responseJS.md5 + '"<br> ';
            // Write debug information too (output becomes responseText).
            document.getElementById('debug').innerHTML = req.responseText;
        }
    }
    // Prepare request object (automatically choose GET or POST).
    req.open(null, 'smpl_backend.php', true);
    // Send data to backend.
    req.send( { q: value } );
}
</script>

<form>
    Text: <input type="text" name="text">
    <input type="button" value="Calculate MD5" 
      onclick="doLoad(this.form.text.value)">
</form>

<div id="result" style="border:1px solid #000; padding:2px">
    Structured results
</div>
<div id="debug" style="border:1px dashed red; padding:2px">
    Debug info
</div>

Если внимательно посмотреть, хорошо видно, что применение JsHttpRequest ничем принципиальным не отличается от использования XMLHttpRequest или Microsoft.XMLHTTP. Однако имеется одна важная особенность библиотеки: результат работы smpl_backend.php удобно получать из свойства req.responseJS. Как видно из контекста, в него загрузчик помещает следующий объект:

Листинг 3: результирующий объект
{ 
  q:   'запрос', 
  md5: 'MD5-код введенной строки' 
}

В поле req.responseText хранятся данные, выданные скриптом smpl_backend.php в свой выходной поток (операторами echo). В большинстве случаев они содержат лишь сообщения об ошибках (если ошибки имели место), и именно поэтому данное свойство трактуется как отладочное.

Лирическое отступление 
Впрочем, ничто не мешает написать загрузчик так, чтобы он передавал основной результат своей работы именно в виде req.responseText (хотя это и не очень удобно — см. ниже).

Итак, после получения ответа от сервера у объекта req появляются следующие свойства:

  • responseJS: данные произвольной структуры (например, многомерные массивы), сгенерированные загрузчиком.
  • responseText: прочие данные и сообщения об ошибках.

PHP: класс JsHttpRequest (backend)

Теперь пришло время посмотреть, как выглядит загрузчик smpl_backend.php. Он крайне прост.

Листинг 4: test/JsHttpRequest/smpl_backend.php
<?php
// Load JsHttpRequest backend.
require_once "../../lib/JsHttpRequest/JsHttpRequest.php";
// Create main library object. You MUST specify page encoding!
$JsHttpRequest =& new JsHttpRequest("windows-1251");
// Store resulting data in $_RESULT array (will appear in req.responseJs).
$GLOBALS['_RESULT'] = array(
  "q"     => @$_REQUEST['q'],
  "md5"   => md5(@$_REQUEST['q']),
); 
// Below is unparsed stream data (will appear in req.responseText).
?>
<pre>
<b>Request method:</b> <?=$_SERVER['REQUEST_METHOD'] . "\n"?>
<b>Loader used:</b> <?=$JsHttpRequest->LOADER . "\n"?>
<b>_REQUEST:</b> <?=print_r($_REQUEST, 1)?>
</pre>

Как видите, нам достаточно лишь получить параметры, переданные JavaScript-частью, из стандартных переменных PHP ($_GET или $_POST или $_REQUEST), а затем заполнить специальный массив $_RESULT данными произвольной структуры. (В нашем случае это ассоциативный массив.) Этот массив будет передан в неизменном виде в JavaScript-код и попадет в свойство responseJS. К счастью, массивы PHP и объекты JavaScript устроены практически одинаково, поэтому можно безболезненно производить перевод PHP-массива:

Листинг 5: PHP-массив с результатом работы
$_RESULT === array(
  "q"   => 'query',
  "md5" => 'MD5-code of entered string'
)

...в идентичный ему объект JavaScript:

Листинг 6: идентичный ему JavaScript-объект
req.responseJS === { 
  q:   'query', 
  md5: 'MD5-code of entered string' 
}

Закачка файлов на сервер

Библиотека JsHttpRequest поддерживает возможность закачки файлов на сервер без перезагрузки страницы. Интерфейс для этого используется тот же самый, только вместо строкового или числового значения, передаваемого методу send(), вы должны передать ему ссылку на HTML-элемент типа <INPUT type="file">:

Листинг 7: test/JsHttpRequest/upl_frontend.htm
<script src="../../lib/JsHttpRequest/JsHttpRequest.js"></script>
<script type="text/javascript" language="JavaScript">
function doLoad(value) {
    // Create new JsHttpRequest object.
    var req = new JsHttpRequest();
    // Code automatically called on load finishing.
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            // Write result to page element (_RESULT becomes responseJS). 
            document.getElementById('result').innerHTML = 
                '<b>MD5("'+req.responseJS.q+'")</b> = ' +
                '"' + req.responseJS.md5 + '"<br> ';
            // Write debug information too (output becomes responseText).
            document.getElementById('debug').innerHTML = req.responseText;
        }
    }
    // Prepare request object (automatically choose GET or POST).
    req.open(null, 'upl_backend.php', true);
    // Send data to backend.
    req.send( { q: value } );
}
</script>

<!-- Please note that we must specify enctype to multipart/form-data! -->
<form method="post" enctype="multipart/form-data" onsubmit="return false">
    File: <input type="file" name="upl">
    <input type="button" value="Calculate MD5" 
      onclick="doLoad(this.form.upl)">
</form>

<div id="result" style="border:1px solid #000; padding:2px">
    Structured results
</div>
<div id="debug" style="border:1px dashed red; padding:2px">
    Debug info
</div>

См. этот пример в действии: файл upl_frontend.htm. Скрипт-загрузчик upl_backend.php выглядит обычным образом: используются стандартные возможности PHP по работе с закачанными файлами.

Листинг 8: test/JsHttpRequest/upl_backend.php
<?php
// Load JsHttpRequest backend.
require_once "../../lib/JsHttpRequest/JsHttpRequest.php";
// Create main library object. You MUST specify page encoding!
$JsHttpRequest =& new JsHttpRequest("windows-1251");
// Store resulting data in $_RESULT array (will appear in req.responseJs).
$GLOBALS['_RESULT'] = array(
  "q"     => 'file ' . $_FILES['q']['name'],
  "md5"   => md5(@file_get_contents($_FILES['q']['tmp_name'])),
); 
// Below is unparsed stream data (will appear in req.responseText).
?>
<pre>
<b>Uploaded files:</b> <?=print_r($_FILES, 1)?>
</pre>

Значение атрибута name тэга <INPUT type="file"> не имеет никакого значения: файл всегда приходит на сервер с идентификатором, указанным при вызове метода send() (в нашем примере это "q", хотя INPUT-поле называется "upl").

При организации закачки вам нужно учитывать следующие правила.

  • Закачивать можно только файлы, которые пользователь выбрал при помощи элемента формы <INPUT type="file">. Это единственный способ, разрешенный политикой безопасности браузеров.
  • Помещайте INPUT-поля для закачки внутрь контейнера <form>...</form>. В противном случае закачка будет работать далеко не во всех браузерах.
  • У тэга FORM обязательно устанавливайте атрибут enctype="multipart/form-data", иначе закачка не заработает в IE (браузер имеет ошибку, не позволяющую динамически изменять данный атрибут). Собственно, атрибут enctype — единственный необходимый; все остальные атрибуты не обязательны.
  • Если вы закачиваете сразу несколько файлов, все они должны принадлежать одной и той же форме. Закачка файлов, элементы INPUT которых расположены в разных формах, невозможна.

В случае нарушения любого из перечисленных выше правил библиотека JsHttpRequest выдаст сообщение об ошибке, которое вы сможете легко обнаружить.

Посылка целой формы на сервер

Часто требуется послать на сервер не отдельные значения, а множество элементов, уже сгруппированных в HTML-форму. Вместо того, чтобы вручную перечислять все эти элементы в параметре метода send(), вы можете просто передать ему ссылку на контейнер FORM в одном из элементов ассоциативного массива:

Листинг 9: test/JsHttpRequest/frm_frontend.htm
<script src="../../lib/JsHttpRequest/JsHttpRequest.js"></script>
<script type="text/javascript" language="JavaScript">
function doLoad(value) {
    // Create new JsHttpRequest object.
    var req = new JsHttpRequest();
    // Code automatically called on load finishing.
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            // Write result to page element (_RESULT become responseJS). 
            document.getElementById('result').innerHTML = 
                'MD5('+req.responseJS.q+') = ' +
                '"' + req.responseJS.md5 + '"<br> ';
            // Write debug information too (output become responseText).
            document.getElementById('debug').innerHTML = req.responseText;
        }
    }
    // Prepare request object (automatically choose GET or POST).
    req.open(null, 'frm_backend.php', true);
    // Send data to backend.
    req.send( { q: value } );
}
</script>

<!-- Please note that we must specify enctype to multipart/form-data! -->
<form method="post" id="f" enctype="multipart/form-data" onsubmit="return false">
    Text: <input type="text" name="txt">
    File: <input type="file" name="upl">
    <input type="button" value="Calculate MD5" 
     onclick="doLoad(document.getElementById('f'))">
</form>

<div id="result" style="border:1px solid #000; padding:2px">
    Structured results
</div>
<div id="debug" style="border:1px dashed red; padding:2px">
    Debug info
</div>

См. этот пример в действии: файл frm_frontend.htm.

Кэширование и выбор метода загрузки

Библиотека JsHttpRequest поддерживает несколько возможностей, которых нет в XMLHttpRequest.

Вы можете включить режим кэширования результата запросов к загрузчику:

Листинг 10: включение кэширования
// Enable caching of the query results
req.caching = true;

Теперь, если вы дважды вызовете метод send() с теми же данными в параметрах, во второй раз обращение на сервер не произойдет, а будет возвращен результат первого обращения. Такой режим удобен в случаях, когда с сервера подгружаются статичные данные (например, элементы выпадающих списков городов). Конечно, кэширование следует отключать, если вы отправляете на сервере какие-то данные, введенные пользователем.

По умолчанию библиотека JsHttpRequest старается сама выбрать оптимальный тип загрузчика данных, исходя из возможностей, поддеживаемых браузером. Например, в Firefox будет практически всегда использован встроенный объект XMLHttpRequest (но только не в случае, когда производится закачка файлов: в последнем случае всегда применяется загрузчик, основанный на FORM и IFRAME). Однако иногда (например, при написании скриптов тестирования библиотеки) требуется явно указать, какой загрузчик требуется использовать. Это можно сделать, присвоив значение свойству loader объекта-библиотеки:

Листинг 11: явное указание загрузчика
// Always use FORM loader.
req.loader = 'FORM';

Наконец, выбор оптимального метода загрузки (GET или POST) можно также поручить библиотеке, указав в первом параметре функции open() нетипичное для XMLHttpRequest значение null (обычно там указывают "GET" или "POST"; null предоставит выбор наилучшего метода библиотеке). Естественно, если данные невозможно загрузить явно указанным методом (или загрузчиком), библиотека сообщит об этом ошибкой.

Упрощенный интерфейс библиотеки

Приведенные выше примеры показывают, что контекст использования библиотеки JsHttpRequest практически всегда один и тот же. Вначале создается объект библиотеки, затем устанавливаются его свойства и указывается функция, которая будет вызвана при окончании загрузки. Далее осуществляется посылка запроса на сервер.

Чтобы не писать один и тот же код по многу раз, объект JsHttpRequest имеет специальный метод query(), реализующий описанную только что логику. Пример его использования (файл md5_frontend.htm).

Листинг 12: test/JsHttpRequest/md5_frontend.htm
<script src="../../lib/JsHttpRequest/JsHttpRequest.js"></script>
<script language="JavaScript">
    // Function is called when we need to calculate MD5.
    function calculate_md5() {
        JsHttpRequest.query(
            'md5_backend.php', // backend
            {
                // pass a text value 
                'str': document.getElementById("mystr").value,  
                // path a file to be uploaded
                'upl': document.getElementById("myupl")
            },
            // Function is called when an answer arrives. 
            function(result, errors) {
                // Write errors to the debug div.
                document.getElementById("debug").innerHTML = errors; 
                // Write the answer.
                if (result) {
                    document.getElementById("ans").innerHTML = 
                        'MD5("' + result["str"] + '") = ' + result["md5"];
                }
            },
            false  // do not disable caching
        );
    }
</script>

<!-- Please note that we must specify enctype to multipart/form-data! -->
<form method="post" enctype="multipart/form-data" onsubmit="return false">
    Enter a text: <input type="text" id="mystr"><br>
    ...or upload a file: <input type="file" id="myupl"><br>
    <input type="button" value="Calculate MD5" onclick="calculate_md5()">
</form>

<div id="ans" style="border:1px solid #000; padding:2px">
    Structured results
</div>
<div id="debug" style="border:1px dashed red; padding:2px">
    Debug info
</div>

Как видите, этот пример делает почти то же самое, что и smpl_frontend.htm, однако его код значительно короче и понятнее.

Работа совместно с библиотекой Prototype

Prototype — это популярное средство для упрощения работы JavaScript-программиста, включающее поддержку AJAX и другие возможности. Библиотека JsHttpRequest может быть использована в качестве ее серверной PHP-части (после подключение небольшого модуля совместимости JsHttpRequest-prototype.js). При этом все дополнительные возможности, присущие JsHttpRequest (кроссбраузерность, закачка файлов, работа с русскими кодировками и т. д.), остаются в силе.

Листинг 13: Использование стандартных функций Prototype
<script src="../../lib/JsHttpRequest/JsHttpRequest.js"></script>
<script src="../../lib/JsHttpRequest/JsHttpRequest-prototype.js></script>
<script language="JavaScript">
new Ajax.Request('your_ajax_script.php', {
  method: 'get',
  parameters: {
    name: 'Дмитрий',  
    file: $("my_upload_file")
  },
  onFailure: function(tr) {
    alert('Error code: ' + tr.status + '\n');
    alert(tr.responseText);
  },
  onSuccess: function(tr) {
    $("result").innerHTML = tr.responseJS.hello; 
    if (tr.responseText) alert(tr.responseText);
  }
});
</script>

Конечно, вы можете использовать и любые другие возможности prototype для работы с AJAX (например, функции динамической подгрузки данных в HTML-элемент).

Перехват ошибок в PHP-загрузчике

Ключевым отличием библиотеки JsHttpRequest от аналогов является то, что системы, написанные с ее помощью, очень легко отлаживать. Традиционно отладка AJAX-приложений считается достаточно сложной проблемой. Дело в том, что данные поступают от PHP-части в JavaScript-часть в определенном (и весьма строгом) формате, и любое нарушение этого формата (например, из-за ошибки в скрипте-загрузчике) приведет к ошибкам в JavaScript-коде.

Библиотека JsHttpRequest берет на себя всю "грязную работу" по конвертированию данных из формата, стандартного для PHP, в формат, принятый в JavaScript. Она перехватывает выходной поток PHP-скрипта (в том числе — сообщения о синтаксических и других ошибках) и накапливает его в специальном буфере, чтобы потом передать в свойство responseText объекта JavaScript. Вы также физически не сможете заполнить массив $_RESULT так, чтобы это нарушило формат передачи данных: массивы PHP однозначно транслируются в объекты JavaScript.

Т.к. все сообщения об ошибках (например, вызов несуществующей функции) PHP печатает прямо в выходной поток (как будто бы через echo), логично воспринимать все содержимое выходного потока скрипта в качестве отладочного текста. Если вы помните, этот текст доступен в свойстве req.responseText, пустом при корректном завершении загрузчика. Благодаря механизму перехвата ни одна, даже самая серьезная, ошибка в PHP-программе не сгенерирует некорректного JavaScript-кода. Вместо этого текст ошибки попадет в req.responseText.

Вы можете убедиться, что перехват ошибок работает, запустив пример md5_frontend.htm, приведенный выше, и введя в строке текста слово "error". Наверное, Вы уже обратили внимание, что в отладочном блоке выводится сообщение о фатальной ошибке PHP (вызов неопределенной функции), которая привела к немедленному завершению скрипта:

Листинг 14: пример перехваченной фатальной ошибки PHP
Fatal error: Call to undefined function 
error_demonstration__make_a_mistake_calling_undefined_function()
in load.php on line 15

Ну а чтобы все окончательно прояснилось, приведем примерный результат работы скрипта md5_backend.php, как его "видит" браузер (уже после перевода его библиотекой JsHttpRequest в формат, "понятный" для JavaScript-кода). Результат выглядит так даже в случае возникновения фатальной ошибки.

Листинг 15: пример формата результата
JsHttpRequest.dataReady({
  "id": "123", // this ID is passed from JavaScript frontend
  "js": { 
      "str": "строка", 
      "md5": "MD5-код введенной строки" 
  },
  "text": "Здесь идут отладочные сообщения и ошибки."
})

Принцип работы JsHttpRequest

В зависимости от возможностей браузера и особенностей передаваемых данных библиотека JsHttpRequest использует 3 различных способа загрузки данных ("загрузчика").

  1. Для IE5 и IE6 используется ActiveX-компонента Microsoft.XMLHTTP или Msxml2.XMLHTTP. Однако, чтобы она заработала, вы должны включить ActiveX в настройках браузера. И хотя по умолчанию данная функция как раз включена, многие пользователи, наслышанные о многочисленных дырах IE, вручную ее отключают. Для Mozilla, Firefox и Opera 8 (и старше), а также для IE7 применяется класс XMLHttpRequest. У него есть небольшой недостаток: по умолчанию запрещено загружать данные откуда-то, кроме как с текущего сайта. Т.е. вы не можете подгзузить какую-то информацию со стороннего сайта, даже если JsHttpRequest на нем установлен.
  2. Если ни один из описанных выше методов не доступен, производится попытка использовать динамически создаваемый тэг <SCRIPT>. Этот способ работает во всех браузерах и позволяет посылать запросы даже на отличающийся от текущего домен, но у него есть большой недостаток: данные возможно передавать только методом GET (POST недоступен), а значит, их размер сильно ограничен.

    Лирическое отступление 
    При использовании данного метода в поведении библиотеки JsHttpRequest имеются два отличия от стандартного XMLHttpRequest. Во-первых, библиотека поддерживает только асинхронную загрузку данных (работа без ожидания: скрипт никогда не "замораживается", если ответ от сервера еще не пришел). Во-вторых, в случае обрыва соединения при загрузке данных это нельзя обнаружить. Данные ограничения не могут быть обойдены, но, к счастью, почти не ограничивают область применимости библиотеки.

  3. Наконец, если требуется закачать на сервер тот или иной файл (upload), используется единственно возможный алгоритм: применение динамически создаваемых <IFRAME> и <FORM>. Главный недостаток этого метода: при изменении атрибута src у <IFRAME> раздается характерный щелчок и добавляется запись в "историю браузера", так что кнопка Back (Назад) начинает работать неправильно. К счастью, в современных браузерах эта проблема не возникает, но в старых она может наблюдаться.

Лирическое отступление 
Если вас не устраивает встроенный в JsHttpRequest алгоритм автоматического выбора загрузчика, вы можете явно указать, какой загрузчик и какой метод (GET или POST) использовать. Это делается при помощи присваивания значений "SCRIPT", "XML" или "FORM" свойству loader объекта библиотеки, а также указания метода ("GET" или "POST") в первом параметре функции open().

Протокол обмена данными

Рассмотрим, как работает библиотека, на примере самого кроссбраузерного способа загрузки данных. Речь о динамическом создании и присоединении к текущей странице тэга <SCRIPT>. Такому тэгу следует указать атрибут src, совпадающий с адресом серверного скрипта подгрузки данных (написанного, к примеру, на PHP).

Чайник 

Конечно, загружаемый скрипт должен выдавать корректный код на JavaScript. Обычный текст таким методом не подгрузишь. О том, чтобы обеспечить корректность JavaScript-кода, полностью заботится библиотека.

Предположим, что при нажатии на кнопку JavaScript-программа вставляет (c использованием DOM) в текущую страницу следующий тэг:

Листинг 16: обращение к загрузчику
<script language="JavaScript" src="load.php?room=303&JsHttpRequest=101-script"></script>

Что при этом произойдет? Браузер немедленно обратится к серверу со следующим запросом:

Листинг 17: URL запрошенного загрузчика
load.php?room=303&JsHttpRequest=101-script

В результате на сервере запустится скрипт load.php, который получит в QUERY_STRING параметры load.php?room=303&JsHttpRequest=101-script (конечно, аргументы могут быть произвольными). Программа отработает (к примеру, обратится к базе данных) и напечатает в качестве результирующей страницы следующий текст:

Листинг 18: пример результата работы загрузчика
JsHttpRequest.dataReady({
  "id": "101", // this ID is passed from JavaScript frontend
  "js": [
    "Это некоторые данные.",
    "Они могут иметь произвольную структуру...",
    { "test": "...и вложенность" }
  ],
  "text": "А здесь идет простой отладочный текст."
})

Чайник 

Внимание! Приведенный выше формат результирующих данных - это лишь пример одного из используемых способов передачи данных. Если Вас интересует подробное описание, см. документ Протокол передачи данных.

Итак, PHP-скрипт load.php напечатал в свой выходной поток текст, являющийся по совместительству корректной JavaScript-программой. Он будет использован браузером в качестве источника данных произвольной структуры.

Лирическое отступление 
М-ммм... "Программа, пишущая другие программы"... "Источник"... Определенно "Matrix has you".

В итоге код на JavaScript, сгенерированный PHP-скриптом load.php, будет выполнен браузером! Как видите, вызывается метод dataReady() объекта JsHttpRequest, которому передается:

  1. Уникальный идентификатор загрузки (чтобы не спутать одни данные с другими, ведь страница может одновременно запросить сведения сразу из нескольких источников). Этот идентификатор всегда попадает в backend-код из GET-параметра "JsHttpRequest" (вспомните QUERY_STRING из примера: load.php?room=303&JsHttpRequest=101-script) и представляет собой целое число. (После дефиса, который идет за числом, указывается использованный тип загрузчика: "xml" или "script" или "form".)
  2. Произвольные данные, полученные программой load.php, например, из БД.
  3. Некоторый текст, который может быть использован в отладочных целях (например, там удобно указывать сообщения об ошибках, возникших в PHP-программе, т.е. содержимое стандартного выходного потока скрипта).

Ну а уж функция JsHttpRequest.dataReady() заботится о доставке загруженых данных конечному потребителю, осуществляя также кэширование одинаковых запросов (если это разрешено).

Чайник 

Динамическая генерация тэга <SCRIPT> имеет одно важное достоинство: при использовании такого подхода "история" браузера (history) не засоряется лишними ссылками, а при загрузке не слышно щелчка, издаваемого многими браузерами во время перехода на другую страницу. Нужно также заметить, что в Firefox имеется небольшая ошибка, в результате которой статус-строка не очищается после загрузки <SCRIPT>-компонента (в ней остается сообщение "Loading ..."). Впрочем, эта ошибка ни на что не влияет и, вероятно, будет в скором времени исправлена разработчиками.

Состав библиотеки

Библиотека JsHttpRequest состоит из двух частей, работающих совместно друг с другом:

  • JsHttpRequest.js, 14 КБ: JavaScript-код, определяющий объект JsHttpRequest. Это — так называемый frontend системы ("передний проход"). Его следует подключать к страницам с помощью тэга:

    Листинг 19: подключение библиотеки в JavaScript
    <script language="JavaScript" src="JsHttpRequest/JsHttpRequest.js">
    </script>

  • JsHttpRequest.php, 16 КБ: PHP-код, в котором определяются функции для облегчения написания загрузчиков на PHP. Это — так называемый backend системы ("задний проход"). Его следует включать в самое начало программы оператором:

    Листинг 20: подключение библиотеки в PHP
    require_once "JsHttpRequest/JsHttpRequest.php";

В качестве языка для написания загрузчиков пока что поддерживается только PHP, однако в будущем возможно написание backend-модулей и на других языках. (На самом деле, версии для Perl и C++ уже есть, однако они недостаточно стабильны для того, чтобы включать их в дистрибутив.)

Для уменьшения размера ускорения загрузки основного frontend-модуля библиотеки ее JavaScript-код сжат при помощи утилит в составе пакета Dojo Toolkit. (Здесь Вы можете посмотреть, как он выглядит после сжатия.)

Модульная структура библиотеки

В дистрибутиве библиотеки имеются также две дополнительные директории: lib/JsHttpRequest/debug и lib/JsHttpRequest/mini.

  • В директории debug хранятся исходные тексты библиотеки frontend-поддержки, со всеми комментариями, отступами и пробелами. Если Вам кажется, что Вы нашли в библиотеке баг, пожалуйста, вначале попробуйте использовать эти версии для локализации проблемы, а уж только затем пишите в форум.
  • В директории mini хранятся "урезанные" версии библиотеки. Например, там можно найти версию, поддерживающую только метод загрузки SCRIPT (JsHttpRequest-script.js), удобную, если Вы загружаете данные только с "чужих" серверов. Она занимает всего 8 КБ.

Если Вы взглянете на исходный код frontend-модуля, Вы заметите, что можете самостоятельно скомпоновать необходимый Вам набор загрузочных модулей. Для этого просто удалите кусок кода, хранящий тот или иной загрузчик.

Решение проблемы с кодировками

При формировании запроса к загрузчику может потребоваться передать ему строки, содержащие русские буквы. Естественно, их нельзя напрямую передавать в URL, а вначале нужно URL-кодировать — преобразовать каждый символ русского алфавита к виду %XX, где XX — код символа.

В JavaScript имеется функция escape(), которая URL-кодирует строку данных. К сожалению, она возвращает результат только в виде Unicode. Например, строка "проба" представляется ей так "%u043F%u0440%u043E%u0431%u0430". В PHP нет функций, умеющих раскодировать такое представление данных (urldecode() тут плохой помощник, ибо она не поддерживает формат %uXXXX). Функция escape() позволяет закодировать совершенно любой символ, будь то русская буква, литера греческого алфавита или даже китайский иероглиф.

Лирическое отступление 
Вообще говоря, в последних версиях JavaScript имеется функция encodeURIComponent(), умеющая кодировать данные в обход Unicode. Однако она не поддерживается, например, в Internet Explorer 5.0, так что из соображения кроссбраузерности нам не подходит.

К счастью, популярное расширение iconv для PHP поддерживает функцию для преобразования данных во всевозможных кодировках, так что перекодировать из Unicode в windows-1251 не составляет для backend-библиотеки JsHttpRequest особых сложностей.

Лирическое отступление 
По многочисленным просьбам, библиотека JsHttpRequest может работать и без iconv, если основной кодировкой сайта является windows-1251, koi8-r или UTF-8. Функции и таблицы перевода из Unicode в одну из этих кодировок встроены в сам модуль.

Итак, вы можете вызывать метод send() объекта JsHttpRequest, не задумываясь о кодировках данных. Вам не нужно ничего перекодировать вручную ни в серверном, ни в клиентском коде: библиотека берет всю эту работу на себя.

Чайник 

Еще раз: если вы хотите использовать библиотеку JsHttpRequest с кодировками, отличными от windows-1251, koi8-r и UTF-8, на сервере должно быть установлено расширение PHP iconv. У большинства хостеров оно стоит, но, если вдруг окажется, что его нет (к позору провайдера), хостеру не составит труда установить модуль.

Резюме

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

Пример использования библиотеки:

Библиотека JsHttpRequest активно используется на форуме forum.dklab.ru. А именно, через нее реализованы следующие функции:

  • "Живой поиск": на каждой странице форума имеется поле, в которое можно ввести поисковый запрос и сразу же получить результат, минуя перезагрузку страницы.
  • "Живой поиск" в форме добавления нового топика: то же самое, но срабатывает при вводе темы нового сообщения.
  • "Живой предпросмотр": рядом со ссылками на топики приведена специальная пиктограмма, наведя мышь на которую, можно просмотреть первое сообщение топика.
  • "Живая карма": пользователи могут изменять карму (рейтинг) друг другу, не перезагружая страницу.

Статьи в Интернете:

Автор библиотеки выражает благодарность Юрию Насретдинову за перевод статьи на английский, а также ценные замечания, касающиеся библиотеки.







Дмитрий Котеров, Лаборатория dk. ©1999-2016
GZip
Добавить на Del.icio.us   Добавить на Digg.com   Добавить на reddit.com