diff --git a/doc.md b/doc.md
index f94e52efd942f0cfd1ed7df7794cb5d335462b7c..67aa26bff1e6b9aa483f48eefbb9806f460a528b 100644
--- a/doc.md
+++ b/doc.md
@@ -1,7 +1,7 @@
 # HTML5 Speedtest
 
 > by Federico Dossena  
-> Version 4.2.2, June 13 2017  
+> Version 4.2.3, June 14 2017  
 > [https://github.com/adolfintel/speedtest/](https://github.com/adolfintel/speedtest/)
 
 
@@ -206,7 +206,7 @@ It is important here to turn off compression, and generate incompressible data.
 A symlink to `/dev/urandom` is also ok.
 
 #### Replacement for `empty.php`
-Your replacement must simply respond with a HTTP code 200 and send nothing else. You may want to send additional headers to disable caching.
+Your replacement must simply respond with a HTTP code 200 and send nothing else. You may want to send additional headers to disable caching. The test assumes that Connection:keep-alive is sent by the server.
 
 #### Replacement for `getIP.php`
 Your replacement must simply respond with the client's IP as plaintext. Nothing fancy.
@@ -220,6 +220,7 @@ w.postMessage('start {"url_dl": "newGarbageURL", "url_ul": "newEmptyURL", "url_p
 
 
 ## Known bugs and limitations
+* The ping/jitter test is measured by seeing how long it takes for an empty XHR to complete. It is not an acutal ICMP ping
 * __Chrome:__ high CPU usage from XHR requests with very fast connections (like gigabit).
   For this reason, the test may report inaccurate results if your CPU is too slow. (Does not affect most computers)
 * __IE11:__ the upload test is not precise on very fast connections
diff --git a/empty.php b/empty.php
index 7395f6cc717fdd08391a443f37e6d2705aae0c87..eb1fc85b6da96ae58138ce7c766be7f68f3dca45 100644
--- a/empty.php
+++ b/empty.php
@@ -3,4 +3,5 @@ header( "HTTP/1.1 200 OK" );
 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
 header("Cache-Control: post-check=0, pre-check=0", false);
 header("Pragma: no-cache");
+header("Connection: keep-alive");
 ?>
\ No newline at end of file
diff --git a/speedtest_worker.js b/speedtest_worker.js
index 777c0bf6a68a3a91c0155cc3312173ccae63533c..63e9d76d4723c540cb1a528107b08b74bf9d1c01 100644
--- a/speedtest_worker.js
+++ b/speedtest_worker.js
@@ -1,5 +1,5 @@
 /*
-	HTML5 Speedtest v4.2.2
+	HTML5 Speedtest v4.2.3
 	by Federico Dossena
 	https://github.com/adolfintel/speedtest/
 	GNU LGPLv3 License
@@ -325,7 +325,7 @@ function pingTest (done) {
       if (i === 0) {
         prevT = new Date().getTime() // first pong
       } else {
-        var instspd = (new Date().getTime() - prevT) / 2
+        var instspd = (new Date().getTime() - prevT)
         var instjitter = Math.abs(instspd - prevInstspd)
         if (i === 1) ping = instspd; /* first ping, can't tell jiutter yet*/ else {
           ping = ping * 0.9 + instspd * 0.1 // ping, weighted average
diff --git a/speedtest_worker.min.js b/speedtest_worker.min.js
index 2591c25cee4732fa4ba381424d29d060f5eb1795..8bc21e5bc8134d05ce9b56bce46566e17898d54a 100644
--- a/speedtest_worker.min.js
+++ b/speedtest_worker.min.js
@@ -1 +1 @@
-function clearRequests(){if(xhr){for(var i=0;i<xhr.length;i++){if(useFetchAPI)try{xhr[i].cancelRequested=!0}catch(e){}try{xhr[i].onprogress=null,xhr[i].onload=null,xhr[i].onerror=null}catch(e){}try{xhr[i].upload.onprogress=null,xhr[i].upload.onload=null,xhr[i].upload.onerror=null}catch(e){}try{xhr[i].abort()}catch(e){}try{delete xhr[i]}catch(e){}}xhr=null}}function getIp(done){xhr=new XMLHttpRequest,xhr.onload=function(){clientIp=xhr.responseText,done()},xhr.onerror=function(){done()},xhr.open("GET",settings.url_getIp+"?r="+Math.random(),!0),xhr.send()}function dlTest(done){if(!dlCalled){dlCalled=!0;var totLoaded=0,startT=(new Date).getTime(),failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(1===testStatus)if(useFetchAPI)xhr[i]=fetch(settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize).then(function(response){var reader=response.body.getReader(),consume=function(){return reader.read().then(function(result){return result.done?testStream(i):(totLoaded+=result.value.length,xhr[i].cancelRequested&&reader.cancel()),consume()}.bind(this))}.bind(this);return consume()}.bind(this));else{var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x,xhr[i].onprogress=function(event){if(1!==testStatus)try{x.abort()}catch(e){}var loadDiff=event.loaded<=0?0:event.loaded-prevLoaded;isNaN(loadDiff)||!isFinite(loadDiff)||0>loadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].onload=function(){try{xhr[i].abort()}catch(e){}testStream(i,0)}.bind(this),xhr[i].onerror=function(){0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this);try{settings.xhr_dlUseBlob?xhr[i].responseType="blob":xhr[i].responseType="arraybuffer"}catch(e){}xhr[i].open("GET",settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,!0),xhr[i].send()}}.bind(this),1+delay)}.bind(this),i=0;i<settings.xhr_dlMultistream;i++)testStream(i,100*i);interval=setInterval(function(){var t=(new Date).getTime()-startT;if(!(200>t)){var speed=totLoaded/(t/1e3);dlStatus=(8*speed/925e3).toFixed(2),(t/1e3>settings.time_dl||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),done())}}.bind(this),200)}}function ulTest(done){if(!ulCalled){ulCalled=!0;var totLoaded=0,startT=(new Date).getTime(),failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(3===testStatus){var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x;var ie11workaround;try{xhr[i].upload.onprogress,ie11workaround=!1}catch(e){ie11workaround=!0}ie11workaround?(xhr[i].onload=function(){totLoaded+=262144,testStream(i,0)},xhr[i].onerror=function(){0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStatus(i,100)},xhr[i].open("POST",settings.url_ul+"?r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(reqsmall)):(xhr[i].upload.onprogress=function(event){if(3!==testStatus)try{x.abort()}catch(e){}var loadDiff=event.loaded<=0?0:event.loaded-prevLoaded;isNaN(loadDiff)||!isFinite(loadDiff)||0>loadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].upload.onload=function(){testStream(i,0)}.bind(this),xhr[i].upload.onerror=function(){0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this),xhr[i].open("POST",settings.url_ul+"?r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(req))}}.bind(this),1)}.bind(this),i=0;i<settings.xhr_ulMultistream;i++)testStream(i,100*i);interval=setInterval(function(){var t=(new Date).getTime()-startT;if(!(200>t)){var speed=totLoaded/(t/1e3);ulStatus=(8*speed/925e3).toFixed(2),(t/1e3>settings.time_ul||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),done())}}.bind(this),200)}}function pingTest(done){if(!ptCalled){ptCalled=!0;var prevT=null,ping=0,jitter=0,i=0,prevInstspd=0;xhr=[];var doPing=function(){prevT=(new Date).getTime(),xhr[0]=new XMLHttpRequest,xhr[0].onload=function(){if(0===i)prevT=(new Date).getTime();else{var instspd=((new Date).getTime()-prevT)/2,instjitter=Math.abs(instspd-prevInstspd);1===i?ping=instspd:(ping=.9*ping+.1*instspd,jitter=instjitter>jitter?.2*jitter+.8*instjitter:.9*jitter+.1*instjitter),prevInstspd=instspd}pingStatus=ping.toFixed(2),jitterStatus=jitter.toFixed(2),i++,i<settings.count_ping?doPing():done()}.bind(this),xhr[0].onerror=function(){0===settings.xhr_ignoreErrors&&(pingStatus="Fail",jitterStatus="Fail",clearRequests(),done()),1===settings.xhr_ignoreErrors&&doPing(),2===settings.xhr_ignoreErrors&&(i++,i<settings.count_ping?doPing():done())}.bind(this),xhr[0].open("GET",settings.url_ping+"?r="+Math.random(),!0),xhr[0].send()}.bind(this);doPing()}}var testStatus=0,dlStatus="",ulStatus="",pingStatus="",jitterStatus="",clientIp="",settings={time_ul:15,time_dl:15,count_ping:35,url_dl:"garbage.php",url_ul:"empty.php",url_ping:"empty.php",url_getIp:"getIP.php",xhr_dlMultistream:10,xhr_ulMultistream:3,xhr_ignoreErrors:1,xhr_dlUseBlob:!1,garbagePhp_chunkSize:20,enable_quirks:!0,allow_fetchAPI:!1,force_fetchAPI:!1},xhr=null,interval=null,useFetchAPI=!1;this.addEventListener("message",function(e){var params=e.data.split(" ");if("status"===params[0]&&postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp+";"+jitterStatus),"start"===params[0]&&0===testStatus){testStatus=1;try{var s=JSON.parse(e.data.substring(5));if("undefined"!=typeof s.url_dl&&(settings.url_dl=s.url_dl),"undefined"!=typeof s.url_ul&&(settings.url_ul=s.url_ul),"undefined"!=typeof s.url_ping&&(settings.url_ping=s.url_ping),"undefined"!=typeof s.url_getIp&&(settings.url_getIp=s.url_getIp),"undefined"!=typeof s.time_dl&&(settings.time_dl=s.time_dl),"undefined"!=typeof s.time_ul&&(settings.time_ul=s.time_ul),"undefined"!=typeof s.enable_quirks&&(settings.enable_quirks=s.enable_quirks),"undefined"!=typeof s.allow_fetchAPI&&(settings.allow_fetchAPI=s.allow_fetchAPI),settings.enable_quirks){var ua=navigator.userAgent;/Firefox.(\d+\.\d+)/i.test(ua)&&(settings.xhr_ulMultistream=1),/Edge.(\d+\.\d+)/i.test(ua)&&(settings.xhr_dlMultistream=3),/Safari.(\d+)/i.test(ua)&&!/Chrome.(\d+)/i.test(ua)&&(settings.xhr_ulMultistream=10,settings.garbagePhp_chunkSize=5),/Chrome.(\d+)/i.test(ua)&&self.fetch&&(settings.allow_fetchAPI&&(useFetchAPI=!0),settings.xhr_dlMultistream=5)}"undefined"!=typeof s.count_ping&&(settings.count_ping=s.count_ping),"undefined"!=typeof s.xhr_dlMultistream&&(settings.xhr_dlMultistream=s.xhr_dlMultistream),"undefined"!=typeof s.xhr_ulMultistream&&(settings.xhr_ulMultistream=s.xhr_ulMultistream),"undefined"!=typeof s.xhr_dlUseBlob&&(settings.xhr_dlUseBlob=s.xhr_dlUseBlob),"undefined"!=typeof s.garbagePhp_chunkSize&&(settings.garbagePhp_chunkSize=s.garbagePhp_chunkSize),"undefined"!=typeof s.force_fetchAPI&&(settings.force_fetchAPI=s.force_fetchAPI),settings.allow_fetchAPI&&settings.force_fetchAPI&&self.fetch&&(useFetchAPI=!0)}catch(e){}console.log(settings),console.log("Fetch API: "+useFetchAPI),getIp(function(){dlTest(function(){testStatus=2,pingTest(function(){testStatus=3,ulTest(function(){testStatus=4})})})})}"abort"===params[0]&&(clearRequests(),interval&&clearInterval(interval),testStatus=5,dlStatus="",ulStatus="",pingStatus="",jitterStatus="")});var dlCalled=!1,r=new ArrayBuffer(1048576);try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random()}catch(e){}for(var req=[],reqsmall=[],i=0;20>i;i++)req.push(r);req=new Blob(req),r=new ArrayBuffer(262144);try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random()}catch(e){}reqsmall.push(r),reqsmall=new Blob(reqsmall);var ulCalled=!1,ptCalled=!1;
+function clearRequests(){if(xhr){for(var i=0;i<xhr.length;i++){if(useFetchAPI)try{xhr[i].cancelRequested=!0}catch(e){}try{xhr[i].onprogress=null,xhr[i].onload=null,xhr[i].onerror=null}catch(e){}try{xhr[i].upload.onprogress=null,xhr[i].upload.onload=null,xhr[i].upload.onerror=null}catch(e){}try{xhr[i].abort()}catch(e){}try{delete xhr[i]}catch(e){}}xhr=null}}function getIp(done){xhr=new XMLHttpRequest,xhr.onload=function(){clientIp=xhr.responseText,done()},xhr.onerror=function(){done()},xhr.open("GET",settings.url_getIp+"?r="+Math.random(),!0),xhr.send()}function dlTest(done){if(!dlCalled){dlCalled=!0;var totLoaded=0,startT=(new Date).getTime(),failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(1===testStatus)if(useFetchAPI)xhr[i]=fetch(settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize).then(function(response){var reader=response.body.getReader(),consume=function(){return reader.read().then(function(result){return result.done?testStream(i):(totLoaded+=result.value.length,xhr[i].cancelRequested&&reader.cancel()),consume()}.bind(this))}.bind(this);return consume()}.bind(this));else{var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x,xhr[i].onprogress=function(event){if(1!==testStatus)try{x.abort()}catch(e){}var loadDiff=event.loaded<=0?0:event.loaded-prevLoaded;isNaN(loadDiff)||!isFinite(loadDiff)||0>loadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].onload=function(){try{xhr[i].abort()}catch(e){}testStream(i,0)}.bind(this),xhr[i].onerror=function(){0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this);try{settings.xhr_dlUseBlob?xhr[i].responseType="blob":xhr[i].responseType="arraybuffer"}catch(e){}xhr[i].open("GET",settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,!0),xhr[i].send()}}.bind(this),1+delay)}.bind(this),i=0;i<settings.xhr_dlMultistream;i++)testStream(i,100*i);interval=setInterval(function(){var t=(new Date).getTime()-startT;if(!(200>t)){var speed=totLoaded/(t/1e3);dlStatus=(8*speed/925e3).toFixed(2),(t/1e3>settings.time_dl||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),done())}}.bind(this),200)}}function ulTest(done){if(!ulCalled){ulCalled=!0;var totLoaded=0,startT=(new Date).getTime(),failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(3===testStatus){var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x;var ie11workaround;try{xhr[i].upload.onprogress,ie11workaround=!1}catch(e){ie11workaround=!0}ie11workaround?(xhr[i].onload=function(){totLoaded+=262144,testStream(i,0)},xhr[i].onerror=function(){0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStatus(i,100)},xhr[i].open("POST",settings.url_ul+"?r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(reqsmall)):(xhr[i].upload.onprogress=function(event){if(3!==testStatus)try{x.abort()}catch(e){}var loadDiff=event.loaded<=0?0:event.loaded-prevLoaded;isNaN(loadDiff)||!isFinite(loadDiff)||0>loadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].upload.onload=function(){testStream(i,0)}.bind(this),xhr[i].upload.onerror=function(){0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this),xhr[i].open("POST",settings.url_ul+"?r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(req))}}.bind(this),1)}.bind(this),i=0;i<settings.xhr_ulMultistream;i++)testStream(i,100*i);interval=setInterval(function(){var t=(new Date).getTime()-startT;if(!(200>t)){var speed=totLoaded/(t/1e3);ulStatus=(8*speed/925e3).toFixed(2),(t/1e3>settings.time_ul||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),done())}}.bind(this),200)}}function pingTest(done){if(!ptCalled){ptCalled=!0;var prevT=null,ping=0,jitter=0,i=0,prevInstspd=0;xhr=[];var doPing=function(){prevT=(new Date).getTime(),xhr[0]=new XMLHttpRequest,xhr[0].onload=function(){if(0===i)prevT=(new Date).getTime();else{var instspd=(new Date).getTime()-prevT,instjitter=Math.abs(instspd-prevInstspd);1===i?ping=instspd:(ping=.9*ping+.1*instspd,jitter=instjitter>jitter?.2*jitter+.8*instjitter:.9*jitter+.1*instjitter),prevInstspd=instspd}pingStatus=ping.toFixed(2),jitterStatus=jitter.toFixed(2),i++,i<settings.count_ping?doPing():done()}.bind(this),xhr[0].onerror=function(){0===settings.xhr_ignoreErrors&&(pingStatus="Fail",jitterStatus="Fail",clearRequests(),done()),1===settings.xhr_ignoreErrors&&doPing(),2===settings.xhr_ignoreErrors&&(i++,i<settings.count_ping?doPing():done())}.bind(this),xhr[0].open("GET",settings.url_ping+"?r="+Math.random(),!0),xhr[0].send()}.bind(this);doPing()}}var testStatus=0,dlStatus="",ulStatus="",pingStatus="",jitterStatus="",clientIp="",settings={time_ul:15,time_dl:15,count_ping:35,url_dl:"garbage.php",url_ul:"empty.php",url_ping:"empty.php",url_getIp:"getIP.php",xhr_dlMultistream:10,xhr_ulMultistream:3,xhr_ignoreErrors:1,xhr_dlUseBlob:!1,garbagePhp_chunkSize:20,enable_quirks:!0,allow_fetchAPI:!1,force_fetchAPI:!1},xhr=null,interval=null,useFetchAPI=!1;this.addEventListener("message",function(e){var params=e.data.split(" ");if("status"===params[0]&&postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp+";"+jitterStatus),"start"===params[0]&&0===testStatus){testStatus=1;try{var s=JSON.parse(e.data.substring(5));if("undefined"!=typeof s.url_dl&&(settings.url_dl=s.url_dl),"undefined"!=typeof s.url_ul&&(settings.url_ul=s.url_ul),"undefined"!=typeof s.url_ping&&(settings.url_ping=s.url_ping),"undefined"!=typeof s.url_getIp&&(settings.url_getIp=s.url_getIp),"undefined"!=typeof s.time_dl&&(settings.time_dl=s.time_dl),"undefined"!=typeof s.time_ul&&(settings.time_ul=s.time_ul),"undefined"!=typeof s.enable_quirks&&(settings.enable_quirks=s.enable_quirks),"undefined"!=typeof s.allow_fetchAPI&&(settings.allow_fetchAPI=s.allow_fetchAPI),settings.enable_quirks){var ua=navigator.userAgent;/Firefox.(\d+\.\d+)/i.test(ua)&&(settings.xhr_ulMultistream=1),/Edge.(\d+\.\d+)/i.test(ua)&&(settings.xhr_dlMultistream=3),/Safari.(\d+)/i.test(ua)&&!/Chrome.(\d+)/i.test(ua)&&(settings.xhr_ulMultistream=10,settings.garbagePhp_chunkSize=5),/Chrome.(\d+)/i.test(ua)&&self.fetch&&(settings.allow_fetchAPI&&(useFetchAPI=!0),settings.xhr_dlMultistream=5)}"undefined"!=typeof s.count_ping&&(settings.count_ping=s.count_ping),"undefined"!=typeof s.xhr_dlMultistream&&(settings.xhr_dlMultistream=s.xhr_dlMultistream),"undefined"!=typeof s.xhr_ulMultistream&&(settings.xhr_ulMultistream=s.xhr_ulMultistream),"undefined"!=typeof s.xhr_dlUseBlob&&(settings.xhr_dlUseBlob=s.xhr_dlUseBlob),"undefined"!=typeof s.garbagePhp_chunkSize&&(settings.garbagePhp_chunkSize=s.garbagePhp_chunkSize),"undefined"!=typeof s.force_fetchAPI&&(settings.force_fetchAPI=s.force_fetchAPI),settings.allow_fetchAPI&&settings.force_fetchAPI&&self.fetch&&(useFetchAPI=!0)}catch(e){}console.log(settings),console.log("Fetch API: "+useFetchAPI),getIp(function(){dlTest(function(){testStatus=2,pingTest(function(){testStatus=3,ulTest(function(){testStatus=4})})})})}"abort"===params[0]&&(clearRequests(),interval&&clearInterval(interval),testStatus=5,dlStatus="",ulStatus="",pingStatus="",jitterStatus="")});var dlCalled=!1,r=new ArrayBuffer(1048576);try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random()}catch(e){}for(var req=[],reqsmall=[],i=0;20>i;i++)req.push(r);req=new Blob(req),r=new ArrayBuffer(262144);try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random()}catch(e){}reqsmall.push(r),reqsmall=new Blob(reqsmall);var ulCalled=!1,ptCalled=!1;