Multipart XMLHTTPRequest

Этот способ, как ни странно, поддерживается только Firefox. На момент написания Opera и Safari под Windows не поддерживали его. Он представляет собой XHR-запрос, ответ на который может быть разделен на части. При получении новой части вызывается onload().

Вот - пример, можете попробовать его в любом браузере, и увидите, изменилось ли что-то в поддержке. Эталонное поведение: Firefox.

Тут будут сообщения

Вот - клиентский код

function runMultipart() {
	// не кросс-браузерно, все равно способ Firefox only
	var req = new XMLHttpRequest(); 
	
	req.multipart = true;
	// асинхронный запрос
	req.open("GET","/server_push/multipart.php?r="+Math.random(), true);
	req.onload = function(event) {
		var result = event.target.responseText
		
		var d = document.createElement("div")
		d.innerHTML = "onload:"+result;

		document.getElementById('xhr_multipart_dump1').appendChild(d)
	}
	req.onreadystatechange = function() {
		if (req.readyState!=4) return
		var d = document.createElement("div")
		d.innerHTML = "State:"+req.readyState+' Status:'+req.status
		document.getElementById('xhr_multipart_dump1').appendChild(d)
	}
	
	req.send(null);
}

Асинхронность запроса в случае с Multipart XMLHTTPRequest - вещь довольно условная, при установке синхронности - реально браузер подвиснет только до
первой части multipart-сообщения. Думается, это самое правильное, что можно было сделать, т.к получать весь multipart-запрос синхронно никому не нужно, для этого
есть обычные запросы.

Для обработки данных, как видно из примера, в Multipart XMLHTTPRequest используется функция onload, которая получает последний кусок из event.

Есть аналогичная функция onerror, но при вызове из javascript она не работает. Поэтому для обработки ошибок используем старый добрый onreadystatechange, который
вызывается при каждом куске данных или при ошибке.

Чтобы ответить на Multipart XMLHTTPRequest, сервер должен выбрать уникальный разделитель, и первым делом - сообщить его браузеру вместе со спец. заголовком:

header('Content-type: multipart/x-mixed-replace;boundary="УнИкАлЬНЫЙРАздеЛитЕЛЬ"');

Далее сервер может слать данные, и информировать браузер о конце каждой части ответа посылкой разделителя. Перед каждой частью ответа должен быть заголовок Content-Type.
Более подробно - вот, например, серверный код из примера выше.

// уникальный разделитель, можете использовать конкат нескльких uniqid() и т.п.
$delimiter = "boundary";

header('Content-type: multipart/x-mixed-replace;boundary="'.$delimiter.'"');

// полностью отрубаем буферизацию
while (@ob_end_flush()) {}

$header = "Content-type: text/html\r\n\r\n"; // перед каждой частью

$boundary = "--{$delimiter}\n"; // между частями

$footer = "--{$delimiter}--\n"; // в конце

$max = 10;
 
for ($i = 1; $i <= $max; $i++){	
	echo $header; # Заголовок перед каждой частью ответа	
	echo "$i\n"; # пишем сообщение	
	echo $boundary; # и выводим границу между сообщениями
	sleep(1);    
}

// завершающее сообщение
echo $header;
echo "-1";
echo $footer;
  • минимальная задержка и трафик
  • нет индикации загрузки
  • данные нельзя сжимать
  • Firefox(Gecko) only (?)

Замечательный способ, но работает только в движке Gecko, т.е Firefox и родственные браузеры.