<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  </head>
  <body>
<h1>RxRest JS client: Ratpack Test Case</h1>
<div><h2 id="myDiv">status will be updated here....</h2></div>
<progress min="0" max="100" value="30">0% complete</progress>
<button type="button" onclick="call()">call</button>
<button type="button" onclick="abort()">abort</button>
<button type="button" onclick="clean()">clean</button>
<div><h2>Click call to start or abort to stop the data flow</h2></div>
<ul id="myList"></ul>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script>
        var progressBar = document.querySelector('progress');
        var receivedIndex = 0;

        var url = "/chunked";
        var url = "http://stream-sandbox.oanda.com/v1/prices?accountId=12345&instruments=AUD_CAD%2CAUD_CHF";
        var timeout = 4000; //FIXME not supported by Safari
        var xhr = new XMLHttpRequest();

        xhr.onreadystatechange=function() {
            console.log("STATECHANGE: readyState: " + xhr.readyState);
            console.log("STATECHANGE: status: " + xhr.status);
        };

        xhr.onloadstart = function () {
            console.log('started');
            $("#myDiv").html("started");
        }

        xhr.onprogress = function (pe) {
            console.log("PROGRESS: response: " + xhr.response);
            console.log("PROGRESS: responseType: " + xhr.responseType);
            console.log("PROGRESS: allResponseHeaders: " + xhr.getAllResponseHeaders());

            if (pe.lengthComputable) {
                console.log("PROGRESS: loaded: " + pe.loaded );
                console.log("PROGRESS: total: " + pe.total );
                //progressBar.max = pe.total;
                //progressBar.value = pe.loaded;
                progressBar.value = (pe.loaded / pe.total) * 100;
                progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
            }

            if((xhr.readyState == 3 || xhr.readyState == 2) && xhr.status == 200) {
                //slice the response(Text) to show chunk by chunk
                var chunk = xhr.response.slice(receivedIndex);
                if(chunk) { //not empty
                    $("#myList").append("<li>"+ chunk +"</li>");
                }
                receivedIndex = xhr.response.length; //if Blob or other use size
            } else {
                console.error("PROGRESS: Chrome! don't call onprogress for readyState ==  " +
                xhr.readyState + " and status == "+ xhr.status);
            }

        };

        xhr.onload = function (pe) {
            if(pe.lengthComputable) {
                progressBar.value = pe.loaded;
                progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
            }
            console.log("ONLOAD: the request has successfully completed..");
            console.log("ONLOAD: readyState: " + xhr.readyState);
            console.log("ONLOAD: status: " + xhr.status);
            console.log("ONLOAD: responseText: " + xhr.responseText);
            console.log("ONLOAD: responseType: " + xhr.responseType);
            console.log("ONLOAD: allResponseHeaders: " + xhr.getAllResponseHeaders());
        };

        xhr.onloadend = function (pe) { // like finally
            console.log("ONLOADED: the request has completed (either in success or failure).");
            console.log("ONLOADED: readyState: " + xhr.readyState);
            console.log("ONLOADED: status: " + xhr.status);
            console.log("ONLOADED: responseText: " + xhr.responseText);
        };

        xhr.onerror = function () {
            console.log("An error occurred...");
            $("#myDiv").html("An error occurred...");
        };

        xhr.ontimeout = function () {
            console.log("The request timed out...");
            $("#myDiv").html("The request timed out...");
        };

        xhr.onabort = function () {
            console.log("The request aborted by the user....");
            $("#myDiv").html("The request aborted by the user...");
        };


        function call() {
            console.log('starting....');
            receivedIndex = 0;
            $("#myDiv").html("starting....");

            xhr.open("GET", url, true);
            //xhr.setRequestHeader("cache-control", "no-cache"); // to fix safari error
            //assume the server sent response as text otherwise set it to text
            //trick the browser to treat response type as text //http://www.html5rocks.com/en/tutorials/file/xhr2/
            //xhr.overrideMimeType('text/plain; charset=x-user-defined');
            xhr.responseType = "text";

            xhr.timeout = timeout;
            xhr.send();
        }

        function abort() {
            xhr.abort()
        }

        function clean() {
            $("#myList").empty()
        }
    </script>

  </body>

</html>
browser as client testcase for Ratpack chunked / streaming REST API 

testing with  

1. http://stream-sandbox.oanda.com/v1/prices?accountId=12345&instruments=AUD_CAD%2CAUD_CHF
2. Ratpack backend.

Ratpack chunked API url is not working in Chrome. 
same works fine in firefox and safari.
stream-sandbox.oanda.com chunked API works fine with all browsers. 
May be problem with Ratpack???
```groovy
ratpack {
    handlers {
        get('chunked') {
            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

            Publisher<String> strings = Streams.periodically(executor, 500, TimeUnit.MILLISECONDS, new Function<Integer, String>() {
                public String apply(Integer i) {
                    if (i.intValue() < 20) {
                        return i;
                    } else {
                        return null;
                    }
                }
            })
            strings = map(strings) {"${it}x\n";}  

            render stringChunks(strings);
        }
    }
}
```