Как стать автором
Обновить

Асинхронный обмен данными поверх HTTP

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

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


Кстати, схема по которой это работало:



А вот немного кода со стороны клиента(ничего сверхестественного: таймер + посылка 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

Теги:
Хабы:
Всего голосов 61: ↑53 и ↓8+45
Комментарии46

Публикации

Истории

Ближайшие события