Статьи

Использование Push браузера в Grails

Browser Push — это собирательный термин для техник, которые позволяют серверу отправлять асинхронные обновления данных практически в реальном времени в браузер. В этой статье представлен обзор push -уведомлений в браузере, а затем приведен пример использования Grails путем расширения примера проекта из статьи « Использование JMS в Grails » в выпуске за июнь 2011 года для отправки обновлений на основе событий в браузер.

Эта статья первоначально появилась в июльском выпуске GroovyMag .

Когда вам нужны данные в реальном времени?

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

  • системы диспетчерской службы аварийных служб
  • информация о ценах в торговой платформе свопов
  • операторы колл-центра

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

Как работает браузер Push?

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

голосование

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

Рисунок 1: Запрос-ответ на опрос

Длинный опрос

Это не настоящий толчок, но хорошая замена. Сервер не ответит на запрос, если у него нет данных для клиента. После получения данных или тайм-аута клиент отправит новый запрос.

Рисунок 2 иллюстрирует поведение запроса-ответа и когда события сервера достигают клиента (в том числе в случае тайм-аута).

Рисунок 2: Длинный опрос

Потоковый

В этом случае сервер оставляет ответ открытым, то есть браузер остается в состоянии загрузки страницы и может получать обновления (рисунок 3). Этот подход может быть реализован с помощью скрытых iframes, многокомпонентных ответов на XMLHttpRequest и т. Д., Но часто уязвим для тайм-аутов и несовместимой поддержки между браузерами и прокси-серверами.

Рисунок 3: Потоковая передача HTTP

HTML5

Спецификация HTML5 включает в себя два механизма: отправленные события сервера и WebSockets.

Отправленные сервером события имеют JavaScript API под названием EventSource и работают по традиционному HTTP. Отправленные сервером события открывают однонаправленный канал от сервера к клиенту. Основное различие между отправленными сервером событиями и длительным опросом заключается в том, что SSE обрабатываются непосредственно браузером, и пользователь просто должен прослушивать сообщения.

WebSockets включают полнодуплексные каналы связи, поэтому им уделяется больше внимания (хотя это не всегда хорошо, поскольку некоторые браузеры теперь отключают WebSockets по умолчанию из-за проблем безопасности). Рисунок 4 иллюстрирует поведение запроса-ответа для WebSockets.

Рисунок 4: Распространение событий WebSocket

А как насчет кометы / обратного AJAX?

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

Это то же самое, что продолжения?

Continuations — это имя, использованное в оригинальной реализации Jetty Comet; В спецификации Servlet 3.0 добавлена ​​асинхронная поддержка, которая предоставляет стандартный API для консолидации фрагментированных реализаций собственных контейнеров.

И Байё паб / суб протокол?

Он был создан Фондом Додзё (при участии разработчиков Jetty) и назван в честь гобелена Байе, который содержит комету (предположительно, Галлея). Это спецификация, определяющая механизм публикации / подписки для асинхронных сообщений, в основном по HTTP, который не зависит от какого-либо языка программирования реализации и отделяет согласование транспорта от обмена сообщениями.

Каковы основные проблемы?

Основная проблема для push-уведомлений браузера связана с лимитом 2 в спецификации HTTP / 1.1 (RFC2616) для HTTP-клиента, связывающегося с конкретным HTTP-сервером.

Какие плагины Grails доступны?

Два основных плагина — это Atmosphere и CometD, которые используют Java-проекты с одинаковыми именами. На момент написания плагин Atmosphere был более зрелым и самым последним обновленным (версия 0.4.0 поддерживает Grails 1.3.5+ против CometD 0.2.2 с поддержкой Grails 1.2.1+), поэтому он используется для примера проекта.

Обратите внимание, что CometD построен поверх проекта Atmosphere, добавив поддержку протокола Bayeux.

Основы атмосферы

Атмосфера описывает себя как переносимый AjaxPush / Comet и WebSocket Framework. Он имеет клиентский компонент JQuery Plugin и предоставляет серверные компоненты, которые могут работать внутри любого контейнера, удаляя зависимости реализации контейнера от вашего приложения. Атмосфера также может быть кластеризована (например, с помощью JGroups), но это выходит за рамки данной статьи.

У Atmosphere есть несколько доступных модулей, но в Grails мы будем использовать ядро ​​Atmosphere Runtime. Основным компонентом этого модуля является AtmosphereHandler, который можно использовать для приостановки, возобновления и трансляции. Действие трансляции включает в себя распространение события на один или несколько приостановленных ответов. Приостановленный ответ может затем решить отменить событие или отправить его обратно в браузер.

Предупреждение. Вы должны понимать, будет ли Atmosphere блокировать поток для каждого приостановленного соединения для вашего целевого контейнера из-за потенциальных последствий для производительности / емкости.

Атмосфера jQuery плагин

Плагин Atmosphere jQuery обеспечивает кросс-браузерную поддержку для подписки и публикации сообщений из клиентского JavaScript. Он поддерживает различные виды транспорта, главным образом веб-сокет, длинный опрос, потоковую передачу и опрос; где websocket эффективно работает как режим «автоопределения» и возвращается соответствующим образом, если браузер еще не поддерживает WebSockets — это означает, что Atmosphere выберет лучший доступный режим для вашего браузера.

Хотя он предоставляет функцию публикации / подписки, мы сосредоточимся только на последнем.

Плагин Grails для атмосферы

Плагин Grails Atmosphere предоставляет ряд ключевых функций:

  • Возможность писать обработчики с использованием сервисов Grails
  • Автоматическая генерация файлов конфигурации
  • Taglib для включения ресурсов JavaScript
  • Расширение StratosphereServlet для AtmosphereServlet для регистрации статически настроенных обработчиков

Обзор практического примера

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

Хотя это было функционально, пользователю требовалось обновить свой браузер для обновления списка. Для удобства использования было бы гораздо приятнее, если бы после создания сообщения происходило обновление страницы по событию — такова наша цель в этом упражнении.

На рисунке 5 показан поток данных пересмотренного примера проекта с использованием нотации Hohpe EIP.

Рисунок 5: Пример потока данных

Как обычно, полный исходный код для примера проекта Grails доступен на GitHub, код, сопровождающий эту статью, находится по адресу https://github.com/rbramley/GroovyMagJMS.

контроллер

Это только незначительное изменение для удаления flash.message из операции сохранения.

MessageStoreService

Раздел JMS будет использоваться для отправки асинхронного события из MessageStoreService, когда сообщение сохраняется. Для отправки сообщения требуется def jmsService, чтобы автоматическое подключение контейнера Spring могло внедрить нашу зависимость.

Мы опубликуем событие в теме «msgevent», используя jmsService.send (topic: «msgevent», [id: messageInstance.id, body: messageInstance.body]), как только экземпляр сообщения будет сохранен.

Мы также введем 2-секундный спящий режим перед сохранением, чтобы гарантировать, что сообщение доставляется только по маршруту «обратный AJAX».

AtmosphereService

Это реализует наш обработчик, так что мы можем приостановить соединение (листинг 1) и впоследствии возобновить его, вызвав обратный вызов с данными (листинг 2).

Как показано в листинге 3, служба также управляется сообщениями, подписываясь на тему JMS «msgevent», преобразуя сообщение карты в JSON и транслируя его в Atmosphere.

Критически, сервис объявляет отображение атмосферы, которое также используется клиентским JavaScript:

static atmosphere = [mapping:'/atmosphere/messages']

def onRequest = { event ->
   // We should only have GET requests here
   log.info "onRequest, method: ${event.request.method}"
 
   // Mark this connection as suspended.
   event.suspend()
}

Listing 1: Suspending a request

def onStateChange = { event ->
  if (event.message) {
    log.info "onStateChange, message: ${event.message}"
 
    if (event.isSuspended()) {
      event.resource.response.writer.with {
        write "parent.callback('${event.message}');"
        flush()
       }
      event.resume()
    }
  }
}

 

Листинг 2: Возобновление соединения

static exposes = ['jms']
 
@Subscriber(topic='msgevent')
def onEvent(msg) {
  def payload = msg
  if(msg instanceof Map) {
    // convert map messages to JSON
    payload = msg.encodeAsJSON()
  }
 
  // broadcast to the atmosphere
  broadcaster['/atmosphere/messages'].broadcast(payload)
 
  return null
}

 

Листинг 3: Обработка сообщений темы

Внешний интерфейс

Внешний интерфейс для примера предоставляется сгенерированным представлением списка, а элемент заголовка этого файла был дополнен добавлением ресурсов плагина Atmosphere taglib <mosp: resources />.

В листинге 4 показан JavaScript (с использованием jQuery) для регистрации подписки на обработчик Atmosphere вместе с функцией обратного вызова. Обратите внимание, что URI обычно получается из JavaScript window.location, но это было опущено для простоты.

var location = 'http://localhost:8080/GroovyMagJMS/atmosphere/messages';
$.atmosphere.subscribe(location, callback, $.atmosphere.request = {transport: 'websocket'});

 

Листинг 4: Подписка на плагин Atmosphere jQuery

В листинге 5 показана функция обратного вызова, которая проанализирует JSON успешного ответа 200 и добавит новую строку в таблицу списка сообщений (HTML-код был очень упрощен для ясности).
Обратный вызов также обрабатывает случай, когда JSON-анализ завершается неудачно, поскольку Atmosphere отправляет «ненужные» данные на сервер для установления соединения.

function callback(response) {
  if (response.status == 200) {
    var data = response.responseBody;
    if (data.length > 0) {
      try {
        var msgObj = jQuery.parseJSON(data);
        if (msgObj.id > 0) {
          var row = '<tr><td>' + msgObj.id + '</td><td>' + msgObj.body + '</td><td></td></tr>'
          $('tbody').append(row);
        }
      } catch (e) {
        // Atmosphere sends commented out data to WebKit based browsers
      }
    }
  }
}

 

Листинг 5: Код обратного вызова

Листинги 4 и 5 содержат блок <script type = «text / javascript»> и jQuery $ (document) .ready (function () {…});

конфигурация

В примере проекта не настроен файл grails-app / conf / AtmosphereConfig.groovy — вы можете использовать этот файл для передачи параметров init-param в AtmosphereServlet (см. Http://atmosphere.java.net/nonav/apidocs/org/ Атмосфера / cpr / AtmosphereServlet.html ).

Плагин Grails Atmosphere также создает web-app / META-INF / context.xml и web-app / WEB-INF / context.xml — эти файлы включены для переносимости между контейнерами сервлетов (например, Tomcat использует META-INF / context. xml и JBoss используют WEB-INF / context.xml).

Плагин также создает некоторые дополнительные XML-файлы и устанавливает исключение SiteMesh во время компиляции — это описано в вики плагина: https://bitbucket.org/bgoetzmann/grails-atmosphere-plugin/wiki/Home

в действии

Мы запустим приложение, используя grails run-app, а затем перейдем к списку сообщений по адресу http: // localhost: 8080 / GroovyMagJMS / message / list и увидим пустой список (рисунок 6).

Рисунок 6: Пустой список сообщений

Рисунок 6: Пустой список сообщений

Выбор пункта меню «Новое сообщение» на рисунке 6 приведет нас к форме «Создать сообщение», как показано на рисунке 7. Если мы введем значение тела и нажмем «Создать», мы будем перенаправлены в список сообщений (изначально пустой согласно рисунку 6 и впоследствии обновлен согласно рисунку 8).

Рисунок 7: Форма создания сообщения

Рисунок 8: Динамически обновляемый список сообщений

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

Рисунок 9: Обновление списка сообщений второго прохода

Как показано на рисунке 10, вы также можете попробовать это с несколькими браузерами, открытыми по адресу http: // localhost: 8080 / GroovyMagJMS / message / list, и наблюдать за подписчиками, получающими их обновления.

Рисунок 10: Несколько потребителей используют разные браузеры

Как показано на рисунке 11, если вы измените параметр транспорта в листинге 4 с «websocket» на «long-polling» и попробуете его в Chrome, вы увидите, что страница загружается бесконечно, однако IE 8 теперь работает правильно.

Рисунок 11: браузеры с длинным опросом

Этот эксперимент приводит нас к пересмотренной версии кода клиентской подписки из листинга 4. В листинге 6 теперь указывается транспорт как «websocket» с резервным транспортом «long-polling».

var location = 'http://localhost:8080/GroovyMagJMS/atmosphere/messages';
$.atmosphere.subscribe(location, callback, $.atmosphere.request = {transport: 'websocket', fallbackTransport: 'long-polling'});

 

Листинг 6: Клиентская подписка с резервным вариантом

Резюме

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

Однако обратите внимание, что в этом примере не предпринимались попытки аутентификации, поскольку это обычно зависит от используемой инфраструктуры безопасности. В качестве следующего шага для тех, у кого есть требования к аутентификации, вики-сайт CometD предоставляет практические рекомендации по адресу http://cometd.org/documentation/2.x/howtos/authentication

 

От http://leanjavaengineering.wordpress.com/2011/11/18/grails-push/