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

29 Дек
2011

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

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


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

А вот немного кода со стороны клиента:
		
                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();
		
}


Первое на что я наткнулся — это были статьи про Long Polling (WebSockets я исключил сразу потому как не у всех ещё установлены современные браузеры). Меня очень заинтересовала статья на сайте IBM Developerworks, где подробно расписаны основные моменты организации асинхронного обмена данными между сервером и клиентом при помощи http протокола. В статье я обратил внимание на интересный пример. Советую и вам его изучить.

Как оказалось далее в Tomcat’e, который используется у заказчиков, также есть поддержка Сomet(Pushing) механизма. Реализуется она имплементацией CometProcessor. Рассказывать долго, поэтому рекомендую обратится к документации с примерами, которая есть на сайте Tomcat.

В итоге я решил имплементировать этот движок на строне сервера. Пример приводить не буду, потому как он мало чем отличается от приведенных выше (в ссылках на статьи).

Схема по которой работает long polling соединение:
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 });
})();


Здесь при завершинии запроса в обоих случаях мы инициализируем новое соединение/новый запрос.

Схема по которой работает Streaming соединение:
streaming
Здесь как вы видите, клиент посылает только 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);
			
		};


Разницу в работе на сетевом уровне различных имплементаций клиента легко увидеть при помощи Wireshark.

Дополнительные материалы:
1. Статья в википедии о Push Technology
2. Статья на английском языке, откуда были позамствованы картинки
AJAX Patterns (Название говорит само за себя)
Спецификация на XMLHttpRequest

P.S. Не смог найти(заставить работать) прозрачную имплментацию stream client на JQuery (даже с использованием плагинов). Может блогосообщество подскажет?
P.S.S. В целом статья наверное получилась довольно сумбурная, как и моя работа в последние дни.
По материалам Хабрахабр.



загрузка...

Комментарии:

Наверх