Wednesday, January 21, 2015

Reading an AJAX response gradually

I wanted to display a progress bar on a Webpage using Javascript/jQuery while a time-consuming process on the server was taking place. It wasn't uploading or downloading significant amounts of data, but it did take time. For this reason I couldn't use the progress events in Ajax, or their jQuery implementation. People said it couldn't be done, that it exposed "the limitations of the HTTP protocol itself". In fact it has nothing to do with HTTP, but with TCP. When I make an Ajax call the client first establishes a TCP connection using the SYN,SYN+ACK,ACK exchange. Then the server sends data back to the client until it is finished and then sends a FIN packet, which the client acknowledges, to signify "end of flow". So if we provide a callback that gets called on "success" it will wait for the end of flow and not report any data meanwhile. But that doesn't mean that data is not available. At the socket level in Java I can call something like "myStream.available()" to see if there is data to be read, and in Ajax we can test the ready state to see if it is 3 (not 4). If the server is writing data out gradually, in my case the percentage of process completion, and flushing at the ends of lines, then data will be available for the onreadystate function. Here's an example. I provided a button to make the Ajax call, whose id is "rebuild". My service is at "/search/build":

jQuery("#rebuild").click( function() {
    var readSoFar = 0;
    client = new XMLHttpRequest();
    client.open("GET", "http://"+window.location.hostname+"/search/build");
    client.send();
    // Track the state changes of the request
    client.onreadystatechange = function(){
        // Ready state 3 means that data is ready 
        if(client.readyState == 3){
            if(client.status ==200) {
                var len = client.responseText.length-readSoFar;
                console.log(client.responseText.substr(readSoFar,len));
                readSoFar = client.responseText.length;
            }
        }
    };
});

This prints out the text received from the server at the same rate that it was sent. It doesn't appear to be possible to do this in jQuery, because there is no "onreadystatechange" field in jQuery's jqXHR object, so I have used raw Javascript instead. This text can then be used to implement a progress bar.