speedtest/Frontend/server_selector.js
2019-01-31 12:45:48 +01:00

131 lines
4.2 KiB
JavaScript

/*
HTML5 Speedtest v4.7.1 MPOT - Server selector
by Federico Dossena
https://github.com/adolfintel/speedtest/
GNU LGPLv3 License
*/
//pings the specified URL, then calls the function result. Result will receive a parameter which is either the time it took to ping the URL, or -1 if something went wrong.
var PING_TIMEOUT = 1000;
var USE_PING_TIMEOUT = true; //will be disabled on unsupported browsers
if (/MSIE.(\d+\.\d+)/i.test(navigator.userAgent)) {
//IE11 doesn't support XHR timeout
USE_PING_TIMEOUT = false;
}
function ping(url, result) {
var xhr = new XMLHttpRequest();
var t = new Date().getTime();
xhr.onload = function() {
if (xhr.responseText.length == 0) {
//we expect an empty response
var instspd = new Date().getTime() - t; //rough timing estimate
try {
//try to get more accurate timing using performance API
var p = performance.getEntries();
p = p[p.length - 1];
var d = p.responseStart - p.requestStart;
if (d <= 0) d = p.duration;
if (d > 0 && d < instspd) instspd = d;
} catch (e) {}
result(instspd);
} else result(-1);
}.bind(this);
xhr.onerror = function() {
result(-1);
}.bind(this);
xhr.open("GET", url);
if (USE_PING_TIMEOUT) {
try {
xhr.timeout = PING_TIMEOUT;
xhr.ontimeout = xhr.onerror;
} catch (e) {}
}
xhr.send();
}
//this function repeatedly pings a server to get a good estimate of the ping. When it's done, it calls the done function without parameters. At the end of the execution, the server will have a new parameter called pingT, which is either the best ping we got from the server or -1 if something went wrong.
var PINGS = 3, //up to 3 pings are performed, unless the server is down...
SLOW_THRESHOLD = 500; //...or one of the pings is above this threshold
function checkServer(server, done) {
var i = 0;
server.pingT = -1;
if (server.server.indexOf(location.protocol) == -1) done();
else {
var nextPing = function() {
if (i++ == PINGS) {
done();
return;
}
ping(
server.server + server.pingURL,
function(t) {
if (t >= 0) {
if (t < server.pingT || server.pingT == -1) server.pingT = t;
if (t < SLOW_THRESHOLD) nextPing();
else done();
} else done();
}.bind(this)
);
}.bind(this);
nextPing();
}
}
/*this function goes through a list of servers, each with this format:
{
name: "User friendly name",
server:"http://yourBackend.com/", <---- make sure there's a / at the end!
dlURL:"garbage.php" <----- path to garbage.php or its replacement on the server
ulURL:"empty.php" <----- path to empty.php or its replacement on the server
pingURL:"empty.php" <----- path to empty.php or its replacement on the server. This is used to ping the server by this selector
getIpURL:"getIP.php" <----- path to getIP.php or its replacement on the server
}
For each server, the ping is measured, then the server with the function result is called with the best server, or null if all the servers were down.
*/
function selectServer(serverList, result) {
var i = 0;
var done = function() {
var bestServer = null;
for (var i = 0; i < serverList.length; i++) {
if (serverList[i].pingT != -1 && (bestServer == null || serverList[i].pingT < bestServer.pingT)) bestServer = serverList[i];
}
result(bestServer);
}.bind(this);
var nextServer = function() {
if (i == serverList.length) {
done();
return;
}
checkServer(serverList[i++], nextServer);
}.bind(this);
nextServer();
}
/*
this function is a faster version of selectServer that tests multiple servers concurrently. Useful for large server lists
*/
var CONCURRENCY = 4; //4 seems to be the safest value
function fastSelectServer(serverList, result) {
var serverLists = [];
for (var i = 0; i < CONCURRENCY; i++) {
serverLists[i] = [];
}
for (var i = 0; i < serverList.length; i++) {
serverLists[i % CONCURRENCY].push(serverList[i]);
}
var completed = 0;
var bestServer = null;
for (var i = 0; i < CONCURRENCY; i++) {
selectServer(
serverLists[i],
function(server) {
if (server != null) {
if (bestServer == null || server.pingT < bestServer.pingT) bestServer = server;
}
completed++;
if (completed == CONCURRENCY) result(bestServer);
}.bind(this)
);
}
}