Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Шаблоны проектирования Ajax. Выпуск 1.


Шаблоны проектирования Ajax. Выпуск 1.

Здравствуйте, мои дорогие читатели!
Приветствую тех, кто ещё не в отпуске и остался в пыльном жарком городе. Зато вы сможете узнать кое-что новое, а пляж, я надеюсь, от нас не убежит. =)

В сегодняшнем выпуске мы развиваем затронутую в предыдущем выпуске архинужную тему шаблонов проектирования Ajax. Шаблоны проектирования, да простят меня любители точных формулировок, это стандартные решения типовых задач, те самые велоспеды, которые не стоит изобретать дважды. И хоть Ajax появился относительно недавно, однако шаблонов уже накопилось на целый wiki-справочник.

Ниже описаны два шаблона, HTTP Streaming и IFrame Call, оба на одну и ту же тему: взаимодействие браузера и сервера. Да-да, вы правильно поняли: речь в них идёт о том за что Ajax и любят - о возможности обмениваться данными с сервером, не перезагружая страницу!

Эти переводы появятся через некоторое время на сайте "Популярная веб-механика", а пока что вы будете первыми, кто их прочтёт.


Оригинал: HTTP Streaming
Переводчик: Алексей Викторович Казьмин, email: alexkmoto@yandex.ru, ICQ: 290079346, cайт: http://www.tigger.jino-net.ru/

HTTP Streaming (Поток HTTP)

Вкратце

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

Проблема

Как браузер может взаимодействовать с сервером?

Мотивы

Контент многих веб-сервисов находится в состоянии постоянного обновления. Изменения могут поступать из множества источников: от других пользователей, сторонних новостных ресурсов, как результат вычислений или в зависимости от даты и времени. Но запросы на обновление отображаемой информации могут поступать только от пользователя. Если данные изменились, у сервера нет возможности самому установить соединение с заинтересованным клиентом.

Решение

Потоковое вещание с сервера через долгоживущее HTTP-соединение. Большинство веб-сервисов обрабатываеют запрос, посылают назад ответ и немедленно прерывают коннект. Но по рассматриваемой здесь схеме они оставляют соединение активным, запуская долгий цикл. Сценарий на сервере использует регистрацию событий или какой-либо иной способ для отслеживания изменений состояния данных. Как только состояние изменилось, сервер направляет новые данные в исходящий поток и “сливает” их клиенту, не закрывая, однако, соединение по окончании этого процесса. Тем временем браузер должен удостоверить, что пользователь увидел информацию. Эта схема рассматривает две техники для потокового вещания: “Поток страницы” (Page Streaming) и “Поток сервиса” (Service Streaming).

Хочу предупредить вас, что данная методика пока еще экспериментальна, особенно вариант “Поток сервиса”. Здесь по-прежнему остается много вопросов, связанных с маштабируемостью и осуществимостью в реальных условиях. Например, прокси, расположенный между браузером и сервером может буфферизовать http-запросы, создавая тем самым помехи нормальному течению потока информации от сервера к клиенту

Page Streaming подразумевает потоковую передачу страницы - первичного ответа на http-запрос. Сервер, в этом случае, немедленно выводит исходную страницу и направляет ее в поток, но соединение оставляет открытым. В дальнейшем он изменяет ее путем присоединения к потоку скриптов, модифицирующих DOM (Document Object Model). Браузер формально как бы продолжает загружать страницу, так что когда он встретит закрывающий тэг <script>, он немедленно выполнит этот скрипт.

Посмотрите простенькое демо: http://ajaxify.com/run/streaming/

К примеру, сначала сервер может выдать блок <div>, который будет содержать последние новости:

print ("<div id='news'></div>");

Но после этого скрипт не заканчивается, а начинает цикл, обновляющий блок каждые 10 секунд:

  <?
    while (true) {
  ?>
      <script type="text/javascript">
        $('news').innerHTML = '<?= getLatestNews() ?>';
      </script>
  <?
      flush();
      sleep(10);
    }
  ?>

Каждый язык и среда будут иметь свои отличительные особенности при реализации этой схемы. В случае PHP следует воспользоваться советом с сайта php.net: перед вызовом функции flush () выполните выполнить ob_end_flush(); . Также есть параметр max_execution_time, значение которого, возможно, придется увеличить, и у веб-сервера тоже есть параметер определяющий максимальное время, которое может длится соединение.

Это иллюстрация основ техники, а ниже обсудим некоторые тонкости. У вас мог возникнуть вопрос, как же браузер инициирует связь с сервером, если соединение уже находится в “бесконечном” режиме? Ответ – мы используем тайный второй канал, то есть создадаём параллельное HTTP-соединение. Этого легко достичь с помощью XMLHttpRequest Call или IFrame Call. Поток может производить изменения в пользовательском интерфейсе до тех пор, пока сервер имеет какие-то средства узнать о наличии запроса. Примеры: session object, global application object (такой как applicationContext в контейнере Java Servlet) или база данных.

Page Streaming предусматривает, что браузер узнает об изменениях на сервере практически немедленно. Это открывает возможность обновлений в нем в режиме реального времени и позволяет осуществить двунаправленный информационный поток, что, однако, полностью расходится со стандартным использованием HTTP и как следствие приводит к ряду проблем. Во-первых, мы имеем дело с переполнением оперативной памяти у пользователя: браузер хранит в ней модель отображаемой страницы, дописывая ее по мере обновления. Так что, для того, чтобы избежать кэширования памяти на винчестер (или чего похуже, особенно в тяжелых веб-приложениях с большим количеством обновляемой информации), придется время от времени прибегать к помощи кнопки Refresh. Во-вторых, в реальных условиях долгоживущие HTTP-соединения неизбежно оборвутся, так что нужно подготовить методику их восстановления. В-третьих, сервер не может поддерживать большое количество одновременно “висящих” соединений. Работа нескольких скриптов, каждый из которых представляет собой один процесс, даже в сложных многопоточных средах будет вызывать быстрое истощение вычислительных ресурсов сервера.

Service Streaming - путь к решению всех этих проблем, хотя он и не поддерживается всеми браузерами. Эта техника использует XMLHttpRequest Call (или аналогичную технологию удаленного доступа типа IFrame Call). Но на этот раз имеется долгоживущее соединение посредством XMLHttpRequest, вместо потоковой загрузки исходной страницы. Здесь мы имеем большую гибкость относительно количества и продолжительности соединений. Вы можете нормально загрузить страницу, а затем, когда пользователь нажмет кнопку, начать потоковое вещание в течении 30 секунд. А можете сразу запустить поток и потом перезапускать его каждые 30 секунд. Обилие опций помогает обойти физические ограничения сервера, браузера и сети.

Механизм “потока сервиса” использует тот же трюк с зацикливанием и для поддержания соединения открытым, и для периодического “сбрасывания” потока на страницу. Ответ сервера не может быть более тэгами “<script>”, так как браузер не станет их выполнять автоматически. Как же тогда сервер управляется с потоком? Ответ – он опрашивает браузер о последнем переданном ответе и действует соотвественно.

Свойство responseText для XMLHttpRequest всегда содержит контент, который был отправлен браузеру, даже когда соединение все еще открыто. Так что браузер может периодически запускать проверку, например, не изменилась ли длина данных. Тем не менее есть одна проблема: сервер лишается возможности стереть то, что уже отправил в поток. К примеру свойство responseText при использовании в качестве генератора данных таймера будет выдавать следующее: "12:00:00 12:01:05 12:01:10" и далее, создавая непрерывную строку. Для решения этой проблемы выдачу разделяют и потом берут во внимание только последнее нужное значение. Допустим, значения (новости) разделяются символом "@END@", тогда выражение, которое будет осуществлять выборку последней новости будет таким:

 function periodicXHReqCheck() {
   var fullResponse = util.trim(xhReq.responseText);
   ** var responsePatt = /^(.*@END@)*(.*)@END@.*$/; **
   if (fullResponse.match(responsePatt)) { // По крайней мере один полный ответ до сих пор
     var mostRecentDigit = fullResponse.replace(responsePatt, "$2");
     $("response").innerHTML = mostRecentDigit;
   }
 }

Хорошо если нас заботит только последняя новость, но что, если браузер должен сохранять все сообщения или обрабатывать их каким-либо образом? Ведь если мы опрашиваем с частотой 10 секунд, то, например, для предыдущей последовательности, было бы потеряно значение за 12:00:05. Значит нам нужно отслеживать позицию, до которой новости “дочитаны” к настоящему моменту. Об этом ниже в разделе о рефакторинге.

В целом, техника Service Streaming делает потоковое вещание более гибким потому, что вы можете передавать произвольный контент, а не только команды Javascript, и также контролировать жизненные циклы соединений. Однако она объединяет две технологии, которые поддерживаются не всеми браузерами. Эксперимент показывает , что “Поток страницы” работает и в IE и в Firefox, но “Поток сервиса” работает только в Firefox, причем независимо от того, используется ли XMLHttpRequest или IFrame. IE в обоих случаях запрещает ответ до завершения запроса. Можно назвать это и багом, и фичей, но, в любом случае, оно работает против HTTP Streaming. Так что для модификации страницы у нас есть несколько вариантов:

  • Использовать ограниченную форму Service Streaming, когда сервер блокируется до выявления изменений в данных. То есть после вывода сообщения он прерывает связь. Это не идеальное решение, но зато довольно легко осуществимое (см. Примеры).
  • Использовать “Поток страницы”.
  • Вместо HTTP Streaming пользоваться периодическим обновлением страницы(Periodic Refresh).
  • Применять гибридный метод: использовать “Поток сервиса”, когда это возможно (например в Firefox), и один из вариантов, предложеных выше - когда нет.

Ответы на вопросы

Как долго нужно сохранять http-соединение?

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

  • Какие ресурсы: сеть, сервер, браузеры и служебное ПО вовлечены в процесс.
  • Как много клиентов подключено одновременно (важны именно пиковые, а не усредненные значения).
  • Насколько сильно будет использоваться система – как много данных надо будет выводить, как этот показатель будет меняться со временем.
  • Каковы будут последствия перегрузки системы, насколько критично то, что пользователи потеряют доступ к информации.

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

Как решить, когда надо закрывать соединение?

Это может зависить от следующих условий:

  • истек лимит времени на соединение;
  • выдано первое сообщение;
  • случилось определенное событие (например если поток служит индикатором процесса вычисления(смотри Progress Indicator), то он, естественно, прервется вместе с выдачей результата);
  • никогда. Соединение должен прервать сам клиент.

Как браузеру находить отличия между сообщениями?

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

Примеры применения

Live Page – часть Nevow Framework(автор Donovan Preston), основаный на python'e фреймворк для создания веб-приложений. Здесь используется “Поток сервиса”, а из соображений совместимости применена следующая схема. Когда главная страница загружается, XHR (XMLHttpRequest) делает запрос на “исходящий канал” (output conduit). Если сервер получил какие-то изменения в промежутке между выводом главной страницы и этим запросом, он немедленно отправляет их. Если запроса нет, то в его ожидании эти изменения накапливаются. После каждой передачи события от сервера к клиенту сервер закрывает исходящий канал, а клиент немедленно устанавливает новое соединение. Если клиент не пересоединяется в течение 30 секунд, сервер посылает ему noop(это "null" на javascript) и закрывает запрос.

Realtime on Rails – чат-сервис Мартина Шеффлера (Martin Scheffler), который использует Service streaming с Firefox и периодическое обновление с другими браузерами.

Lightstreamer – коммерческий движок, применяющийся в финансовых и других приложениях, для выполнения крупномасштабного HTTP Streaming. Так как он работает на отдельном сервере, возможна тонкая настройка. Компания-производитель заявляет, что осуществимо одновремененное обслуживание до 10000 пользователей на стандартном 2.4GHz Pentium 4.

Демонстрация рефакторинга

Демонстрация потока Wiki

Basic Wiki Demo использует для обновления Periodic Refresh, опрашивающее сервер с переодичностью 5 секунд. В этом демо Ajax Demo механизм обновления заменён на Service Streaming . Продолжает использоваться библиотека AjaxCaller для загрузки новых сообщений, что делает её действующим тайным каналом.

Сервис по прежнему остается не зависящим от типа клиента. Все, что ему остается делать, это выводить новые сообщения по мере их поступления. Таким образом он выводит поток примерно таким образом:

<message> содержание сообщения </message>...ждем какое-то время...<message> содержание сообщения </message>...

Чтобы проиллюстрировать как сервер может быть полностью не зависим от клиентского приложения, представим, что клиент должен сам разрывать соединение (в практических условиях лучше, если соединение будет завершаться, скажем, раз в минуту). Сервер ищет разницу между старыми и новыми сообщениями(он может также ориентироваться на изменения timestamp, если в сообщение входит такое поле):

  while (true) {
    ...
    foreach ($allIds as $messageId) {
      ...
      if ($isNew || $isChanged) {
        print getMessageXML($newMessage); // пишет "<сообщение >..."
        flush();
      }
    }
    sleep(1);
  }

Браузер посылает запрос, используя стандартные методы open() и send(). Интересно, что у нас нет оператора onreadystatechange, так как мы собираемся использовать таймер, чтобы проверять текст. (В Firefox использование этого оператора могло бы иметь смысл, так как он вызывался бы каждый раз, когда сервер сбрасывал бы поток на страницу.

xhReq.open("GET", "content.phtml", true);
 xhReq.send(null);
 // Не трогаем onreadystatechange
 // и опрашиваем responsetext
 ...
 pollTimer = setInterval(pollLatestResponse, 2000);

pollLatestResponse() продолжает читать выводимый текст. Он хранит информацию о последенем полном сообщении, которое он зафиксировал в переменной nextReadPos. Например, если было выведено два сообщения по 500 символов, то nextReadPos будет равен 1001. Отсюда и начнется поиск нового сообщения. Каждая полная новость обрабатывается последовательно, как на конвейере. Если сообщение не полное (в его конце не стоит тэг ), оно будет просто проигнорировано.

  function pollLatestResponse() {
    var allMessages = xhReq.responseText;
    ...
    do {
      var unprocessed = allMessages.substring(nextReadPos);
      var messageXMLEndIndex = unprocessed.indexOf("");
      if (messageXMLEndIndex!=-1) {
        var endOfFirstMessageIndex = messageXMLEndIndex + "".length;
        var anUpdate = unprocessed.substring(0, endOfFirstMessageIndex);
        renderMessage(anUpdate);
        nextReadPos += endOfFirstMessageIndex;
      }
    } while (messageXMLEndIndex != -1);

Спустя какое-то время браузер вызовет xhReq.abort(), прерывая соединение, но, как упоминалось ранее, с таким же успехом это может сделать и сам сервер.

Заметьте также, что для дописывания страницы тоже используется библиотека AjaxCaller. Таким образом, если пользователь загружает новость на сервер, она также скоро попадет в поток и будет получена браузером.

Альтернативы

Periodic refresh (периодическое обновление) – это очевидная замена для HTTP Streaming. Оно “прикидывается” долго-живущим соединением, время от времени опрашивая сервер. В общем, Periodic refresh - лучше настраиваемый, легче совместимый и более надежный способ организовать вещание. Однако, все же в случае использования в системах с небольшим числом одновременно работающих пользователей(intranet), когда каждое соединение ценится довольно высоко, и вы можете настроить инфраструктуру, стоит предпочесть HTTP Streaming.

Связанные шаблоны

Distributed Events (Распределённые события).

Вы можете использовать Distributed Events чтобы координировать действия браузера после ответа.

Ассоциативный образ

Представьте себе блогера на конференции непрерывно сообщающего, что происходит в данный момент.


Оригинал: IFrame Call
Переводчик: Черноусов Андрей Васильевич, email: anroland@yandex.ru, ICQ: 345538024

IFrame Call (Вызов IFrame)

Вкратце.

Схема: скрипт – сервер – iframe – скрипт - body.

Описание

Cтюарт выбирает себе автомобиль через Internet, и окончательная цена обновляется мгновенно, вслед за выбором. Это стало возможным благодаря использованию скрытого iframe в структуре web-приложения. Как только Стюарт вносит изменения в данные, браузер тот час вносит полученную информацию в специальную форму во встроенном фрейме, и запоминает ее. Вскоре, во встроенном фрейме содержится уже новый показатель цены, который браузер выводит на экран.

Проблема.

Каким образом браузер может взаимодействовать с сервером?

Мотивы

Такие же как и для XMLHttpRequest.

Решение.

Используйте встроенные фреймы(IFrames) для связи браузера с сервером. Iframe это элементы, типа страницы, которые могут быть внедрёны в другие страницы. У них есть свой URL, отличный от родительской страницы, и его можно динамически изменять. Вызов встроенного фрейма работает посредством обращения к интересующему нас URL этого фрейма, и последующим «чтением» обновленной информации.

Для начала вам нужен iframe внедрённые в исходный HTML код вашей страницы, а также обработчик событий для события onload. (Самый ненавязчивый способ зарегестрировать обработчик –воспользоваться JavaScript, но из-за «фич» одного хитрого браузера, это не всегда срабатывает).


<iframe id='iFrame' onload='onIFrameLoad();'></iframe>

Фрейм используется в целях получения и накопления данных, а не как пользовательский интерфейс, так что нужены соответствующие стили в CSS, чтобы скрыть этот фрейм. (Более естественно было бы использовать display:none для скрытия фрейма, но как показывает практика, этот вариант не рабочий из-за странностей некоторых браузеров).


#iFrame {
   visibility: hidden;
   height: 1px;
}

Связь с сервером осуществляется при помощи сервиса описанного в XMLHttpRequest Call: sumGet.phtml. Чтобы вызвать сервис мы изменим URL нашего IFrame.

Мы уже зарегистрировали обработчик события onload для нашей страницы. Обработчик вызывается при загрузке нового контента и на этой стадии тело IFrame будет отражать контент, который мы будем использовать после. Следующий скрипт вызывает функцию extractIFrameBody, которая извлекает содержание <body> из IFrame наиболее удобным способом (основано на [1]).


 function onIFrameLoad() {
   var serverResponse = extractIFrameBody($("iFrame")).innerHTML;
   $("response").innerHTML = serverResponse;
 }

Вышесказанное относилось к GET-запросу. Для наглядного примера передачи данных методом POST, используем такой скрипт: sumPost.phtml, также описанный в XMLHttpRequest Call. Этот метод несколько сложнее, так как недостаточно просто изменить URL у IFrame. Вместо этого мы динамически поместим во фрейм специальную форму и отправим её. В ниже приведенном примере аргументы '5' и '2' жёстко заданы, но они могут быть легко перезаписаны в скрипте с помощью манипуляций с элементами input


   var iFrameBody = extractIFrameBody($("iFrame"));
   iFrameBody.innerHTML =
      "<form action='sumPostForm.phtml' method='POST'>" +
        "<input type='text' name='figure1' value='5'>" +
        "<input type='text' name='figure2' value='2'>" +
      "</form>";
   var form = iFrameBody.firstChild;
   form.submit();

Как и в случае с обычной отправкой формы, IFrame перезагрузится с выдачей данных получателю формы. Значит наш предыдущий обработчик события onLoad будет работать прекрасно и в этом случае.

Другой подход с IFrames заключается в том, чтобы сервер выводил Javascript, с помощью которого можно изменять данные или вызывать функции на родительской странице. Этот код будет автоматически вызываться при загрузке страницы, что делает не нужным обработчик события onload. К сожалению, этот метод создает слишком тесную связь между сервером и браузером. Напротив, в примерах приведённых выше используются полностью универсальные веб-сервисы, которые знают только как сложить числа, и ничего не знают о контексте запроса. Это возможно, потому что вся логика взаимодействия содержится в функции onload()

Как отмечено в разделе Call Tracking, существует много проблем (равно и путей как их обойти), касающихся XMLHttpRequest Calls, они же относятся и к IFrames. Для начала, нужно создать отдельный скрытый фрейм для каждого запроса, ведь вы не хотите каждый раз изменять адрес фрейма, в тот момент, когда он ожидает ответа на запрос. Во-вторых, вы должны исследовать и проверить ограничения, потому что приложение может не выдержать слишком большого количества вызовов.

Не ошибитесь с выбором: IFrames, является хаком в чистом виде. Фреймы были созданы вовсе не для облегчения взаимодействия браузера с сервером. Вероятно эта методика будет медленно исчезать по мере роста популярности XMLHttpRequest. Этому поспособствует и замена браузеров на более новые версии. Но, несмотря ни на что, фреймы все еще обладают несколькими преимуществами над XMLHttpRequestCall, про что пойдет речь в разделе Альтернативы.

Примеры применения

Программа Google Maps.

Данная программа наверно является самым известным приложением, разработанным c помощью Ajax. Она предоставляет пользователю возможность передвигаться по виртуальной карте мира. Регулировка панорамы и масштаба изображения требуют дистанционной связи с сервером для максимально точного и быстрого получения изображения и данных о местоположении объекта прямо с сервера. Для этого и используют встроенные фреймы. Пока неизвестно почему, но компания Google параллельно использует и XMLHttpRequest для загрузки таблиц стилей XSLT (подробней об этом в разделе Browser-Side XSLT).

Демо на сайте PXL8.

Мишель Транквилли предоставит вам здесь отличный учебник по IFrames с конкретными примерами

JSRS

Это система Брэнта Эшли является, по сути, виртуальной библиотекой, построенной с помощью IFrame. Эта программа интересна тем, что является вероятно самым ранним подобным приложением, начальная версия которого вышла еще в 2000 году, и продолжает использоваться и поныне. Также доступна и базовая демо-версия данного приложения.

HTMLHttpRequest

Еще один пример подобной виртуальной библиотеки - система получения данных Ангуса Тюрнбала. Данная система проверяет поддерживается ли приложением XMLHttpRequest, и, если нет, обращается к IFrame. Этот инструмент записан в секции "Примеры кода", являющейся частью Cross-Browser Component.

Демонстрация рефакторинга

Данное приложение (Basic Sum Demo) использует XMLHttpRequest, но есть отдельная версия, которая переделана под использование IFrame. Разбора кода здесь не будет, так как пример похож на принцип работы GET-запроса в приложении описанном в разделе Решение.

Альтернативы

XMLHttpRequest

Это, по сути, альтернатива вызову IFrame. В разделе Альтернатива для XMLHttpRequest сравниваются эти два подхода.

Вызов фрейма

До появления плавающих фреймов существовали добрые старые обычные фреймы. Долгие годы они пользовались в народе недоброй славой , но тем не менее давали способ удалённого вызова скрипта подобно вызову IFrame. (хотя это была и случайность). С тех пор как повсеместно поддерживаются плавающие фреймы, маловероятно, что вы когда либо будете использовать обычные фреймы для удалённого запуска скриптов.

Ассоциативный образ

Скрытый IFrame походит на помощника, которому вы можете делегировать запросы.

Хотите знать больше?



Не забудьте, что оригиналы этих статей находятся на Wiki-сайте, и постоянно дописываются!

А ещё, напоследок, я хочу поделится своей находкой: сайтом "Web 2.0 - Что нового?" Автор очень оперативно переводит свежие новости из мира web 2.0 с сайта techcrunch.com, плюс знакомит с новыми русскими web 2.0 проектами.

Вот и всё на сегодня!
По прежнему жду от вас пожеланий и замечаний, которые я учту, а также ссылок на другие хорошие переводы статей про новые веб-технологии. Мой адрес: adodonov@gmail.com

Счастливого вам отпуска мои дорогие читатели, за сим прощаюсь
Додонов Александр.


В избранное