Не так давно на работе передо мной была поставлена задача реализовать механизм асинхронного обмена данными между веб-приложением на 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