Не так давно на работе передо мной была поставлена задача реализовать механизм асинхронного обмена данными между веб-приложением на Java и веб фронт-эндом на стороне клиента. Задача заключалась в том, чтобы клиент получал апдейты с минимальной задержкой, при этом апдейты могли приходить со скоростью 100 апдейтов в секунду, так и 1 апдейд в минуту, т.е. желательно не слать лишних запросов со стороны клиента.
Вначале я набросал тестовый сервлет, который мгновенно отвечал на запрос, присланный с клиента, на котором данный запрос формировался каждую секунду. Конечно, эта схема была далека от идеала, поэтому я принялся гуглить.
Кстати, схема по которой это работало:

А вот немного кода со стороны клиента(ничего сверхестес��венного: таймер + посылка JSON request):
И со стороны сервера(по этическим аспектам не могу разглашать код, отвечающий за бизнес-логику приложения, да смысл статьи не в нем):
Первое, на что я наткнулся — были статьи about Long Polling (WebSockets я исключил сразу, потому как не у всех клиентов могут быть установлены современные браузеры). Меня очень заинтересовала статья на сайте IBM Developerworks, где подробно расписаны основные моменты организации асинхронного обмена данными между сервером и клиентом при помощи http протокола. В статье я обратил внимание на интересный пример. Советую и вам c ним ознакомиться(конечно если вам интересно).
Как оказалось далее в Tomcat'e, который используется у заказчиков, также есть поддержка Сomet(Pushing) механизма. Реализуется она имплементацией интерфейса CometProcessor. Подробный пример есть на сайте Tomcat, поэтому рекомендую обратиться к документации с примерами.
В итоге я решил имплементировать этот движок на строне сервера. Пример приводить не буду, потому как он мало чем отличается от приведенных выше (в ссылках на статьи).
Схема работы long polling соединения:

Главное отличие от схемы, которую вы видели выше это то, что сервер отправляет response не сразу, а ждет определенного события. Это достигается путем выставления в request header тега keep-alive. Данный тег заставляет сервер не рвать соединение раньше времени. После того как был отправлен response и клиент его получил, клиент снова отправляет ещё один запрос и ждет ответа. По сути здесь мы наблюдаем рекурсивный вызов.
Реализация long polling клиента на javascpirt:
А так бы это могло выглядеть с использованием JQuery:
Схема работы stream соединения:

Здесь, как вы видите, клиент посылает только 1 запрос в самом начале. А далее сервер на каждое событие отправляет клиенту кусочек информации. Это достигается благодаря тому, что writer в response не закрывается, посылка выполняется только через метод flush(). Благодаря этому клиент продолжает вычитывать информацию из потока.
Реализация stream клиента на javascript:
Дополнительные материалы:
1. Статья в википедии о Push Technology
2. Статья на английском языке, откуда были позамствованы картинки
3. AJAX Patterns (Название говорит само за себя)
4. Спецификация на XMLHttpRequest
Вначале я набросал тестовый сервлет, который мгновенно отвечал на запрос, присланный с клиента, на котором данный запрос формировался каждую секунду. Конечно, эта схема была далека от идеала, поэтому я принялся гуглить.
Кстати, схема по которой это работало:

А вот немного кода со стороны клиента(ничего сверхестес��венного: таймер + посылка JSON request):
function onLoad() { // Invoke request update each second intervalId = setInterval ( requestUpdate(), 1000 ); } function requestUpdate() { $.getJSON(URL, JSONParams, function(data) { $.each(data, function(key, val) { // Process data }) }).success(function(){ // Succes handler }).error(function(){ // Error handler }); }; function onUnload() { clearInterval(intervalID); }; $(document).ready(function() { // Disable caching for ajax $.ajaxSetup({ cache: false }); });
И со стороны сервера(по этическим аспектам не могу разглашать код, отвечающий за бизнес-логику приложения, да смысл статьи не в нем):
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { //Get reqeust parameters JSONObject jsonObj = JSONObject.fromObject(req.getParameterMap()); //Some code //Get writer PrintWriter out = res.getWriter(); //Create json object for response Map<String, String[]> map = retriever.getUpdates(); JSONObject jsonObject = JSONObject.fromObject(map); //Send response out.println(jsonObject); //Finish response! out.close(); }
Первое, на что я наткнулся — были статьи about Long Polling (WebSockets я исключил сразу, потому как не у всех клиентов могут быть установлены современные браузеры). Меня очень заинтересовала статья на сайте IBM Developerworks, где подробно расписаны основные моменты организации асинхронного обмена данными между сервером и клиентом при помощи http протокола. В статье я обратил внимание на интересный пример. Советую и вам c ним ознакомиться(конечно если вам интересно).
Как оказалось далее в Tomcat'e, который используется у заказчиков, также есть поддержка Сomet(Pushing) механизма. Реализуется она имплементацией интерфейса CometProcessor. Подробный пример есть на сайте Tomcat, поэтому рекомендую обратиться к документации с примерами.
В итоге я решил имплементировать этот движок на строне сервера. Пример приводить не буду, потому как он мало чем отличается от приведенных выше (в ссылках на статьи).
Схема работы long polling соединения:

Главное отличие от схемы, которую вы видели выше это то, что сервер отправляет response не сразу, а ждет определенного события. Это достигается путем выставления в request header тега keep-alive. Данный тег заставляет сервер не рвать соединение раньше времени. После того как был отправлен response и клиент его получил, клиент снова отправляет ещё один запрос и ждет ответа. По сути здесь мы наблюдаем рекурсивный вызов.
Реализация long polling клиента на javascpirt:
function go(){ var url = "your url" var request = new XMLHttpRequest(); request.open("GET", url, true); request.setRequestHeader("Content-Type","application/x-javascript;"); request.onreadystatechange = function() { if (request.readyState == 4) { if (request.status == 200){ if (request.responseText) { //Something } } go(); } }; request.send(null); }
А так бы это могло выглядеть с использованием JQuery:
function poll(){ $.ajax({ url: "your url", success: function(data){ //Something }, dataType: "json", complete: poll, timeout: 30000 }); });
Схема работы stream соединения:

Здесь, как вы видите, клиент посылает только 1 запрос в самом начале. А далее сервер на каждое событие отправляет клиенту кусочек информации. Это достигается благодаря тому, что writer в response не закрывается, посылка выполняется только через метод flush(). Благодаря этому клиент продолжает вычитывать информацию из потока.
Реализация stream клиента на javascript:
function switchXHRState() { switch (this.readyState) { case 0: $("#messages").append("open() has not been called yet."); break; case 1: $("#messages").append("send() has not been called yet."); break; case 2:$("#messages").append("send() has been called, headers and status are available."); break; case 3: if (this.status == 200) {Some lines of code} break; case 4: $("#messages").append("Complete!"); break; } }; function go() { var url = "comet"; var request = new XMLHttpRequest(); request.onreadystatechange = switchXHRState; request.open("GET", url, true); request.setRequestHeader("Content-Type", "application/x-javascript;"); request.setRequestHeader("Cache-Control", "no-cache"); request.setRequestHeader("Cache-Control", "no-store"); request.setRequestHeader("Cache-Control", "no-store"); request.send(null); }
Дополнительные материалы:
1. Статья в википедии о Push Technology
2. Статья на английском языке, откуда были позамствованы картинки
3. AJAX Patterns (Название говорит само за себя)
4. Спецификация на XMLHttpRequest
