Содержание


Различные механизмы клиент-серверного взаимодействия в web-приложениях, основанных на Ajax

Все современные web-приложения основаны на различных концепциях, связанных с Ajax. Использование Ajax привело к распространению интерактивных и динамических интерфейсов на web-страницах. Революция Ajax началась с понимания того, что web-приложения могут извлекать данные с сервера асинхронно в фоновом режиме и что взаимодействие между web-страницей и сервером не ограничивается моментом загрузки страницы. Концепция web-страницы расширилась до “долгоживущего” web-приложения, которое постоянно обменивается данными со своим сервером. Приведем несколько примеров того, что можно делать посредством такого взаимодействия:

  • Отправка и получение информации
  • Быстрая проверка введенной информации (например, надежности пароля)
  • Автодополнение пользовательского ввода на основе правил и анализа, выполняемого на сервере

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

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

Традиционные способы

В прошлом стандартных средств реализации постоянного взаимодействия между web-страницей и сервером не существовало. Его можно было осуществлять лишь за счет использования различных HTML-элементов нетрадиционными способами. Ключевой особенностью этих элементов, позволявшей их использовать (порой против правил), являлось то, что они были предназначены для извлечения файла с сервера. Далее ответственность за интерпретацию файла на основе типа элемента ложится на браузер: Вот эти элементы:

img
для извлечения файла с изображением
script
для извлечения файла JavaScript™
iframe
для отображения и возможного извлечения HTML-файла

Предполагается, что эти элементы должны быть частью разметки web-сайта. Однако, манипулируя DOM-структурой страницы с помощью JavaScript, эти элементы можно добавлять динамически и взаимодействовать с сервером в рамках жизненного цикла страницы. В трех следующих разделах мы опишем, как использовать эти элементы.

Запустить и забыть: злоупотребляем элементом <img>

В основном элемент img используется для извлечения изображений с сервера и их вывода на экран. Для извлечения изображения браузер посылает GET-запрос на URL-адрес, указанный в значении атрибута src. Это значение не ограничено политикой браузеров "same origin policy"; поэтому URL-адреса изображений не ограничены доменом страницы, на которой они находятся. .

Что можно и что нельзя сделать

Элемент img можно использовать для выполнения GET-запросов на любой URL-адрес. Таким образом, можно обращаться к сервису, работающему на каком-либо другом сервере.

Однако браузер предполагает, что ответом на данный GET-запрос будет графический файл, и будет пытаться его соответствующим образом обработать (показать на экране). Если на самом деле ответ не является графическим файлом, можно просто не добавлять элемент img в DOM-дерево, так как GET-запрос осуществляется при задании атрибута src (независимо от того, был ли элемент img добавлен на страницу).

Элемент img можно использовать в основном для сервисов типа “fire-and-forget” (запустил и забыл), в которых возвращаемый сервером ответ не представляет интереса для клиента. Он подходит для сервисов, работающих согласно правилу “что происходит на сервере, остается на сервере”.

В листинге 1 показано, как выполняется вызов fire-and-forget.

Листинг 1. Вызываем сервис типа fire and forget
<script type=”text/javascript”>
function fireAndForgetService(targetUrl){
  // сначала создаем объект img
  var imgNode = document.createElement(“img”);
  // затем задаем в атрибуте src этого объекта url-адрес сервиса,
  // который мы хотим вызвать
  // теперь при выполнении этой строки кода браузер создаст
  // GET-запрос и пошлет его на адрес targetURL
  imgNode.src = targetUrl;
  
}
// вызываем данную функцию с произвольным url-адресом, даже с другого домена
  fireAndForgetService(“http://www.theTargetUrl.com/doSomething?param1Name:param1Value”);
</script>

Извлечь и выполнить: используем элемент <script>

Почти все написанное ранее об элементе img верно и при использовании вместо него элемента script. Этот механизм взаимодействия также не ограничивается политикой браузеров "same origin policy".

Однако элементы <img> и <script> отличаются тем, что при использовании элемента script браузер ожидает получить JavaScript-код, который можно выполнить. Это очень мощный механизм, который следует использовать обдуманно. Полученный сценарий будет выполняться браузером, он сможет получить доступ ко всему, к чему есть доступ у страницы, в том числе к файлам cookie, DOM-структуре, истории и т.д.

Что можно и что нельзя делать

Следует определить какой-либо протокол между кодом, выполняемым браузером, и извлекаемым сценарием. Если вы извлекаете сценарий из своего собственного домена, скорее всего этот код осведомлен о различных функциях, утилитах и ограничениях кода приложения. Этот протокол может быть просто интеграцией двух компонентов одного приложения. Сервер может получать в запросе фрагменты кода, которую следует выполнить в браузере, производить с ним различные манипуляции, например, обрабатывать ограничения интернационализации или каким-либо другим образом адаптировать его для конкретного пользователя, после чего возвращать код браузеру.

Все становится интереснее, когда вам нужно извлечь код с другого сервера. В этом случае (и если домен доверенный) извлеченный сценарий незнаком с конструкциями приложения, поэтому в данном случае можно извлечь лишь статические данные. Для обработки этих данных в своем приложении вам нужно каким-либо образом интегрировать извлеченные данные в код приложения. Для этого можно использовать концепцию JSONP. В ее основе лежит предположение, что извлеченный код является JSON-объектом, обернутым в вызов функции обратного вызова, принимающей этот объект в качестве аргумента. Имя функции посылается в параметре URL-адреса извлекаемого сценария, а сервер, получивший запрос, должен предоставить JSON-объект, обернутый в вызов этой функции.

В листинге 2 демонстрируется клиент-серверное взаимодействие на основе концепции JSONP.

Листинг 2. Взаимодействие с применением JSONP
<script type=”text/javascript”>
// приведенная ниже функция принимает следующие аргументы:
// targetUrl – url-адрес для извлечения данных, которые в дальнейшем будут
// обработаны как данные в формате jsonp
// Name – параметра url-адреса, обозначающего имя функции обратного вызова
// callbackName – имя функции, которая будет обрабатывать возвращенный 
// json-объект
function invokeJSONP(targetUrl, jsonpName ,callbackName){
  // сначала мы создаем объект сценария
  var scriptNode = document.createElement(“script”);
  // задаем его тип, чтобы выполнить его при возвращении
  scriptNode.type = “text/javascript”;
  // в его атрибуте src url-адрес извлекаемого сценария
  // и добавляем к url-адресу имя функции обратного вызова 
  scriptNode.src = targetUrl+”?”+ targetDomainJsonpName+”=”+callbackName;
  // добавляем сценарий на страницу
  document.getElementsByTagName(“head”)[0].appendChild(scriptNode);
  
}
// вызываем функцию с любым url-адресом; здесь допустимо кросс-доменное 
// взаимодействие

function handleJsonp(infoObject){
  validateInfoObject(infoObject);
  handleInfoObject(infoObject);
}

  invokeJSONP (“http://targetUrl.com/provideJsonpData”,“jsonpCallback”, “handleJsonp”);
</script>

Следует упомянуть об одном нюансе. При использовании элемента <img> нет нужды добавлять его в DOM-дерево. При извлечении сценария, напротив, GET-запрос не будет отправлен, если элемент <script> не добавить в DOM-дерево.

Целевой домен может опубликовать свой API для JSONP-вызовов, в частности имя параметра, посредством которого указывается имя функции обратного вызова. Другая часть API должна включать структуру JSON-объекта, посылаемого в теле ответа.

В листинге 2 сервер принимает URL-параметр, называемый jsonpCallback (имя может быть разным в различных доменах), и ожидается, что в ответ сервер вернет сценарий, представляющий собой вызов функции handleJsonp. Этот сценарий может выглядеть так, как показано в листинге 3.

Листинг 3. JSONP-объект, возвращаемый с сервера
handleJsonp( {
  ‘height’:185,
  ‘units’:’cm’,
  ‘age’: 30,
  ‘favoriteFruit’:’apple’,
  ‘likesDogs’: true
  }
  );

Просим помощи у посредника: используем элемент <iframe>

iframe – это элемент, который позволяет встраивать страницу в страницу. Если две страницы получены с одного домена, они могут взаимодействовать друг с другом и обмениваться информацией.

В приложениях Ajax распространена парадигма разделения ролей взаимодействия с пользователем и клиент-серверного обмена данными за счет использования нескольких элементов iframe внутри основной страницы. Этот элемент можно скрыть от пользователя, и он не будет влиять на то, как пользователь взаимодействует с приложением.

Такой способ позволяет получать с сервера любые данные и заполнять формы «за кулисами», без обновления страницы.

Что можно и что нельзя делать

В отличие от предыдущих механизмов, в которых используются элементы, предназначенные быть частью страницы, элемент iframe сам является страницей. Он не ограничен каким-либо специфичным типом содержимого (например, изображением) или процессом (выполнение полученного содержимого). Поэтому с его помощью можно отправлять или получать данные с любого сервера. И поскольку нет ограничений на тип получаемых данных, можно осуществлять взаимодействие на основе данных в любом формате, что дает больше гибкости на серверной стороне. Отправка данных на сервер может осуществляться на основе заполнения формы. Также помимо POST-запросов можно использовать и GET-запросы. Кроме того, можно посылать запросы с множественным содержимым (multipart), а значит, есть возможность отправлять файлы с клиентской машины на сервер. (И помните, что все обновления страницы блокируют остальное взаимодействие с пользователем.)

Использование элемента iframe имеет свои изъяны. Так как допускаются кросс-доменные вызовы, безопасность приложения может оказаться под угрозой. Кроме того, любое взаимодействие на основе этого механизма помещается в объект истории страницы, что может смутить пользователей при навигации с помощью кнопок Back/Forward.

Используем iframe

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

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

В листинге 4 показано, как отправлять файлы на сервер, используя элемент iframe, являющийся частью разметки приложения, вместо программного клиент-серверного взаимодействия.

Листинг 4. Отправляем файл на сервер с помощью элемента <iframe>
<!—скрытый элемент iframe, являющийся получателем данных формы, 
используемой для отправки файла -->
	 
<iframe id="IFrame" name="IFrame"
	  style="width:0px; height:0px; border:0px"
	  src="blank.html">
</iframe>

<!—форма соединяется с показанным выше элементом iframe с помощью 
атрибута target таким образом, при отправке формы происходит просто 
перезагрузка этого элемента -->

<form name="UploadFile"  target="IFrame" method="POST"
    action="http://myServer/fileUploadServiceURL"
    enctype="multipart/form-data">
<input type="file" name="uploadFileNameId"/>
<input type="submit" value="Upload" name="submit"/>
</form>

Взаимодействие с помощью Ajax

Web-приложение, основанное на Ajax, обычно является клиентом приложения, выполняемого на сервере, с которым оно интенсивно обменивается данными. Для подобной работы необходимо иметь механизм транспортировки данных. Этот механизм является ключевым компонентом приложения, а потому он должен быть как можно более нетребовательным к ресурсам и безопасным. К счастью, все современные браузеры предоставляют объект, служащий как раз для этой цели: XMLHttpRequest (XHR). Объект XHR, который действительно является экономичным по отношению к ресурсам, создает запрос, привязанный к серверу, с которого данная страница была извлечена. Он исключает все проблемы, связанные с кросс-доменными сценариями, и позволяет передавать только текст.

Что можно и что нельзя делать

С помощью объекта XHR приложения могут посылать информацию на свой сервер и извлекать с него данные. Этот механизм имеет несколько преимуществ по сравнению с механизмами, описанными выше:

Можно использовать любой HTTP-метод
Так как самые распространенные современные серверные архитектуры основаны на принципах REST, вам нужно использовать запросы GET, PUT, POST и DELETE. Применение объекта XHR в качестве средства клиент-серверного взаимодействия позволяет работать с серверами, основанными на REST-архитектуре.
Оповещение при завершении
Объект XHR с момента своего создания до окончательной загрузки ответа может находиться в нескольких состояниях. Каждое изменение состояния является событием, для которого можно задать функцию обратного вызова, выполняемую в момент срабатывания.

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

Нет влияния на историю страницы
Вызовы объекта XHR не отражаются на объекте истории страницы, поэтому навигация с помощью действий браузера Back и Forward работает как обычно.
Не допускаются кросс-доменные сценарии (cross-site scripting или XSS)
Приложение является более безопасным, так как не может работать со сценариями с других доменов (XSS).
Блокирующее или неблокирующее исполнение
Вы может задать, каким должен быть цикл запрос-ответ:
  • синхронным, т.е. браузер не будет выполнять другой код, пока не получит ответ.
  • асинхронным, т.е. при получении ответа будет выполнена функция обратного вызова.
XML или любой другой формат
Если ответ представляет собой данные в формате XML, для этих данных формируется полноценное DOM-дерево. Данные ответа также доступны и в текстовом виде (с ними можно работать, когда ответ представляет собой данные в другом формате, например JSON).

Тем не менее в текущем состоянии дел с объектом XHR не все безоблачно. Так как допускается передавать только текст, для отправки файлов на сервер по-прежнему приходится использовать элемент iframe. Кроме того, данный объект по-разному создается в разных браузерах.

Заключение

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

Многие инфраструктуры JavaScript включают в себя частичную или полную реализацию набора механизмов взаимодействия, основанных на Ajax. Учитывайте это, принимая решение о том, какую инфраструктуру использовать и какие механизмы реализовывать.


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи: Various client-server communication mechanisms in an Ajax-based web application (developerWorks, июнь 2010 г.).
  • Узнайте больше о JSON - экономичном формате обмена данными.
  • Язык JSON Schema позволяет формализовать структуру JSON-объекта.
  • Прочитайте на сайте W3C спецификацию XMLHttpRequest, в которой описывается API, позволяющий функциям клиентских сценариев обмениваться данными с сервером.
  • В спецификации Cross-Origin Resource Sharing на сайте W3C описывается механизм, позволяющий генерировать в коде клиента кросс-доменные запросы. Спецификации, которые позволяют API делать кросс-доменные запросы к ресурсам, могут использовать алгоритмы, определенные в этой спецификации.
  • Cross-domain Ajax with Cross-Origin Resource Sharing - отличная заметка в блоге о механизме CORS.
  • Читайте об Ajax в Википедии.
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура
ArticleID=769470
ArticleTitle=Различные механизмы клиент-серверного взаимодействия в web-приложениях, основанных на Ajax
publish-date=11032011