I have a XMLHttpRequest
with a progress
event handler that is requesting a chunked page which continuously sends adds message chunks. If I do not set a responseType
, I can access the response
property of the XMLHttpRequest
in each progress
event and handle the additional message chunk. The problem of this approach is that the browser must keep the entire response in memory, and eventually, the browser will crash due to this memory waste.
So, I tried a responseType
of arraybuffer
in the hope that I can slice the buffer to prevent the previous excessive memory waste. Unfortunately, the progress
event handler is no longer capable of reading the response
property of the XMLHttpRequest
at this point. The event parameter of the progress
event does not contain the buffer, either. Here is a short, self-contained example of my attempt at this (this is written for node.js
):
var http = require('http');
// -- The server.
http.createServer(function(req, res) {
if (req.url === '/stream') return serverStream(res);
serverMain(res);
}).listen(3000);
// -- The server functions to send a HTML page with the client code, or a stream.
function serverMain(res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<html><body>Hello World</body><script>');
res.end(client.toString() + ';client();</script></html>');
}
function serverStream(res) {
res.writeHead(200, {'Content-Type': 'text/html'});
setInterval(function() {
res.write('Hello World<br />\n');
}, 1000);
}
// -- The client code which runs in the browser.
function client() {
var xhr = new XMLHttpRequest();
xhr.addEventListener('progress', function() {
if (!xhr.response) return console.log('progress without response :-(');
console.log('progress: ' + xhr.response.size);
}, false);
xhr.open('GET', '/stream', true);
xhr.responseType = 'arraybuffer';
xhr.send();
}
The progress
event handler has no access to the response
I wanted. How can I handle the message chunks in the browser in a memory-efficient way? Please do not suggest a WebSocket
. I do not wish to use one just to process a read-only stream of message chunks.
XMLHttpRequest
doesn't seem really designed for this kind of usage. The obvious solution is polling, which is a popular use ofXMLHttpRequest
but I'm guessing you don't want to miss data from your stream that would slip between the calls.To my question
Can the "real" data chunks be identified in some way or is it basically random data ?
, you answeredWith some effort, the chunks could be identified by adding an event-id of sorts to the server-side
Based on this premise, I propose:
The idea: cooperating concurrent listeners
listenerA()
).listenerA()
. Count how many chunkslistenerA()
has received.listenerA()
has received a certain amount of chunks, spawn another "thread" (connection + listener,listenerB()
) doing the steps 1 and 2 in parallel to the first one but keep the processed data in a buffer instead of outputting it.listenerA()
receives the chunk with the same id as the first chunk received bylistenerB()
, send a signal tolistenerB()
, drop the first connection and killlistenerA()
.listenerB()
receives the termination signal from thelistenerA()
, dump the buffer to the output and keep processing normally.listenerB()
spawnlistenerC()
on the same conditions as before.By using two overlapping connections, you can prevent the possible loss of chunks that would result from dropping a single connection and then reconnecting.
Notes
responseType
ofXMLHttpRequest
must be set to its default value of""
or "text", to return text. Other datatypes will not return a partialresponse
. See https://xhr.spec.whatwg.org/#the-response-attributeTest server in node.js
The following code is a node.js server that outputs a consistent stream of elements for testing purposes. You can open multiple connections to it, the output will be the same accross sessions, minus possible server lag.
will return data where id is an incremented number
will return data where id is a random 40 characters long string. This is meant to test a scenario where the id can not be relied upon for ordering the data.
Proof of concept, using aforementioned node.js server code