Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated to new Active server list #809

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 84 additions & 127 deletions speedtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,23 +653,7 @@ def get_exception():
return sys.exc_info()[1]


def distance(origin, destination):
"""Determine distance between 2 sets of [lat,lon] in km"""

lat1, lon1 = origin
lat2, lon2 = destination
radius = 6371 # km

dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = (math.sin(dlat / 2) * math.sin(dlat / 2) +
math.cos(math.radians(lat1)) *
math.cos(math.radians(lat2)) * math.sin(dlon / 2) *
math.sin(dlon / 2))
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
d = radius * c

return d


def build_user_agent():
Expand Down Expand Up @@ -1083,6 +1067,25 @@ def json(self, pretty=False):
return json.dumps(self.dict(), **kwargs)


def parse_custom_server_response(response):
"""Parse the custom server response format and return a list of servers."""
servers = []
server_blocks = re.findall(r'{(.*?)}', response, re.DOTALL)
for block in server_blocks:
server = {}
for line in block.split('\n'):
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip().strip('"')
if key == 'serverid':
key = 'id'

server[key] = value
server["url"] = f"http://{server['host']}/speedtest/upload.php"
servers.append(server)
return servers

class Speedtest(object):
"""Class for performing standard speedtest.net testing operations"""

Expand All @@ -1105,7 +1108,7 @@ def __init__(self, config=None, source_address=None, timeout=10,
if config is not None:
self.config.update(config)

self.servers = {}
self.servers = []
self.closest = []
self._best = {}

Expand Down Expand Up @@ -1238,8 +1241,8 @@ def get_config(self):
return self.config

def get_servers(self, servers=None, exclude=None):
"""Retrieve a the list of speedtest.net servers, optionally filtered
to servers matching those specified in the ``servers`` argument
"""Retrieve the list of speedtest.net servers from the new API URL,
optionally filtered to servers matching those specified in the `servers` argument
"""
if servers is None:
servers = []
Expand All @@ -1258,105 +1261,67 @@ def get_servers(self, servers=None, exclude=None):
'%s is an invalid server type, must be int' % s
)

urls = [
'://www.speedtest.net/speedtest-servers-static.php',
'http://c.speedtest.net/speedtest-servers-static.php',
'://www.speedtest.net/speedtest-servers.php',
'http://c.speedtest.net/speedtest-servers.php',
]
url = 'https://www.speedtest.net/api/embed/vz0azjarf5enop8a/config' # New API URL

headers = {}
if gzip:
headers['Accept-Encoding'] = 'gzip'

errors = []
for url in urls:
try:
request = build_request(
'%s?threads=%s' % (url,
self.config['threads']['download']),
headers=headers,
secure=self._secure
)
uh, e = catch_request(request, opener=self._opener)
if e:
errors.append('%s' % e)
raise ServersRetrievalError()

stream = get_response_stream(uh)

serversxml_list = []
while 1:
try:
serversxml_list.append(stream.read(1024))
except (OSError, EOFError):
raise ServersRetrievalError(get_exception())
if len(serversxml_list[-1]) == 0:
break

stream.close()
uh.close()

if int(uh.code) != 200:
raise ServersRetrievalError()

serversxml = ''.encode().join(serversxml_list)

printer('Servers XML:\n%s' % serversxml, debug=True)

try:
request = build_request(url, headers=headers, secure=self._secure)
uh, e = catch_request(request, opener=self._opener)

if e:
errors.append('%s' % e)
raise ServersRetrievalError()

stream = get_response_stream(uh)


serversjson_list = []
while 1:
try:
try:
try:
root = ET.fromstring(serversxml)
except ET.ParseError:
e = get_exception()
raise SpeedtestServersError(
'Malformed speedtest.net server list: %s' % e
)
elements = etree_iter(root, 'server')
except AttributeError:
try:
root = DOM.parseString(serversxml)
except ExpatError:
e = get_exception()
raise SpeedtestServersError(
'Malformed speedtest.net server list: %s' % e
)
elements = root.getElementsByTagName('server')
except (SyntaxError, xml.parsers.expat.ExpatError):
raise ServersRetrievalError()

for server in elements:
try:
attrib = server.attrib
except AttributeError:
attrib = dict(list(server.attributes.items()))

if servers and int(attrib.get('id')) not in servers:
continue

if (int(attrib.get('id')) in self.config['ignore_servers']
or int(attrib.get('id')) in exclude):
continue

try:
d = distance(self.lat_lon,
(float(attrib.get('lat')),
float(attrib.get('lon'))))
except Exception:
continue

attrib['d'] = d
serversjson_list.append(stream.read(1024))
except (OSError, EOFError):
raise ServersRetrievalError(get_exception())
if len(serversjson_list[-1]) == 0:
break

try:
self.servers[d].append(attrib)
except KeyError:
self.servers[d] = [attrib]
stream.close()
uh.close()

if int(uh.code) != 200:
raise ServersRetrievalError()

serversjson = b''.join(serversjson_list)

if not serversjson:
raise SpeedtestServersError('Empty server list received')

printer('Servers JSON:\n%s' % serversjson, debug=True)
servers_response = serversjson.decode('utf-8')


try:
elements = parse_custom_server_response(servers_response)
except Exception as e:
raise SpeedtestServersError(
'Malformed speedtest.net server list: %s' % e
)

for server in elements:
if servers and int(server.get('id')) not in servers:
continue

break
if (int(server.get('id')) in self.config['ignore_servers']
or int(server.get('id')) in exclude):
continue

except ServersRetrievalError:
continue
self.servers.append(server)

except ServersRetrievalError:
raise

if (servers or exclude) and not self.servers:
raise NoMatchedServers()
Expand Down Expand Up @@ -1425,14 +1390,7 @@ def get_closest_servers(self, limit=5):
if not self.servers:
self.get_servers()

for d in sorted(self.servers.keys()):
for s in self.servers[d]:
self.closest.append(s)
if len(self.closest) == limit:
break
else:
continue
break
self.closest = self.servers[:limit]

printer('Closest Servers:\n%r' % self.closest, debug=True)
return self.closest
Expand Down Expand Up @@ -1898,16 +1856,15 @@ def shell():
printer('Cannot retrieve speedtest server list', error=True)
raise SpeedtestCLIError(get_exception())

for _, servers in sorted(speedtest.servers.items()):
for server in servers:
line = ('%(id)5s) %(sponsor)s (%(name)s, %(country)s) '
'[%(d)0.2f km]' % server)
try:
printer(line)
except IOError:
e = get_exception()
if e.errno != errno.EPIPE:
raise
for server in speedtest.servers:

line = ('%(id)5s) %(sponsor)s (%(name)s, %(country)s)' % server)
try:
printer(line)
except IOError:
e = get_exception()
if e.errno != errno.EPIPE:
raise
sys.exit(0)

printer('Testing from %(isp)s (%(ip)s)...' % speedtest.config['client'],
Expand Down Expand Up @@ -1941,7 +1898,7 @@ def shell():

results = speedtest.results

printer('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: '
printer('Hosted by %(sponsor)s (%(name)s): '
'%(latency)s ms' % results.server, quiet)

if args.download:
Expand Down