diff --git a/README.rst b/README.rst index 6da05fe..a25f2b2 100644 --- a/README.rst +++ b/README.rst @@ -89,6 +89,7 @@ Usage --server SERVER Specify a server ID to test against --mini MINI URL of the Speedtest Mini server --source SOURCE Source IP address to bind to + --timeout TIMEOUT HTTP timeout in seconds. Default 10 --version Show the version number and exit Inconsistency diff --git a/speedtest_cli.py b/speedtest_cli.py index 105c390..16d4341 100755 --- a/speedtest_cli.py +++ b/speedtest_cli.py @@ -18,6 +18,7 @@ __version__ = '0.3.1' # Some global variables we use +user_agent = 'speedtest-cli/%s' % __version__ source = None shutdown_event = None @@ -165,6 +166,17 @@ def distance(origin, destination): return d +def build_request(url, data=None, headers={}): + """Build a urllib2 request object + + This function automatically adds a User-Agent header to all requests + + """ + + headers['User-Agent'] = user_agent + return Request(url, data=data, headers=headers) + + class FileGetter(threading.Thread): """Thread class for retrieving a URL""" @@ -178,7 +190,8 @@ class FileGetter(threading.Thread): self.result = [0] try: if (timeit.default_timer() - self.starttime) <= 10: - f = urlopen(self.url) + request = build_request(self.url) + f = urlopen(request) while 1 and not shutdown_event.isSet(): self.result.append(len(f.read(10240))) if self.result[-1] == 0: @@ -242,7 +255,8 @@ class FilePutter(threading.Thread): try: if ((timeit.default_timer() - self.starttime) <= 10 and not shutdown_event.isSet()): - f = urlopen(self.url, self.data) + request = build_request(self.url, data=self.data) + f = urlopen(request) f.read(11) f.close() self.result = len(self.data) @@ -305,7 +319,8 @@ def getConfig(): we are interested in """ - uh = urlopen('http://www.speedtest.net/speedtest-config.php') + request = build_request('http://www.speedtest.net/speedtest-config.php') + uh = urlopen(request) configxml = [] while 1: configxml.append(uh.read(10240)) @@ -337,12 +352,15 @@ def getConfig(): return config -def closestServers(client, all=False): - """Determine the 5 closest speedtest.net servers based on geographic - distance +def closestServers(client, numServers=5): + """Determine the closest speedtest.net servers based on geographic + distance. The default number of servers to return is 5. If the + number of servers is specified as 0 then all servers are returned. """ - uh = urlopen('http://www.speedtest.net/speedtest-servers-static.php') + url = 'http://www.speedtest.net/speedtest-servers-static.php' + request = build_request(url) + uh = urlopen(request) serversxml = [] while 1: serversxml.append(uh.read(10240)) @@ -382,7 +400,7 @@ def closestServers(client, all=False): for d in sorted(servers.keys()): for s in servers[d]: closest.append(s) - if len(closest) == 5 and not all: + if len(closest) == numServers and numServers != 0: break else: continue @@ -408,8 +426,9 @@ def getBestServer(servers): h = HTTPSConnection(urlparts[1]) else: h = HTTPConnection(urlparts[1]) + headers = {'User-Agent': user_agent} start = timeit.default_timer() - h.request("GET", urlparts[2]) + h.request("GET", urlparts[2], headers=headers) r = h.getresponse() total = (timeit.default_timer() - start) except (HTTPError, URLError, socket.error): @@ -469,7 +488,7 @@ def speedtest(): except AttributeError: pass parser.add_argument('--bytes', dest='units', action='store_const', - const=('bytes', 1), default=('bits', 8), + const=('byte', 1), default=('bit', 8), help='Display values in bytes instead of bits. Does ' 'not affect the image generated by --share') parser.add_argument('--share', action='store_true', @@ -478,12 +497,29 @@ def speedtest(): parser.add_argument('--simple', action='store_true', help='Suppress verbose output, only show basic ' 'information') + parser.add_argument('--showconfig', action='store_true', + help='Display the client configuration') + parser.add_argument('--saveconfig', help='Specify a file to save the ' + 'speedtest.net configuration') + parser.add_argument('--loadconfig', help='Specify a file to load the ' + 'speedtest.net configuration') parser.add_argument('--list', action='store_true', help='Display a list of speedtest.net servers ' 'sorted by distance') + parser.add_argument('--listservers', action='store_true', + help='Display a list of speedtest.net servers ' + 'sorted by distance. Synonym for --list') + parser.add_argument('--saveservers', help='Specify a file to save the ' + 'speedtest.net servers list') + parser.add_argument('--loadservers', help='Specify a file of speedtest.net' + ' servers to use instead of downloading the list') parser.add_argument('--server', help='Specify a server ID to test against') + parser.add_argument('--saveresults', help='Specify a file to save the ' + 'speedtest.net results') parser.add_argument('--mini', help='URL of the Speedtest Mini server') parser.add_argument('--source', help='Source IP address to bind to') + parser.add_argument('--timeout', default=10, type=int, + help='HTTP timeout in seconds. Default 10') parser.add_argument('--version', action='store_true', help='Show the version number and exit') @@ -498,24 +534,62 @@ def speedtest(): if args.version: version() + socket.setdefaulttimeout(args.timeout) + # If specified bind to a specific IP address if args.source: source = args.source socket.socket = bound_socket - if not args.simple: - print_('Retrieving speedtest.net configuration...') - try: - config = getConfig() - except URLError: - print_('Cannot retrieve speedtest configuration') - sys.exit(1) + # Retrieve speedtest configuration + if args.loadconfig is None: + if not args.simple: + print_('Retrieving speedtest.net configuration...') + try: + config = getConfig() + except URLError: + print_('Cannot retrieve speedtest configuration') + sys.exit(1) - if not args.simple: - print_('Retrieving speedtest.net server list...') - if args.list or args.server: - servers = closestServers(config['client'], True) - if args.list: + if args.saveconfig is not None: + if not args.simple: + print_('Saving speedtest.net configuration to %s' + % args.saveconfig) + try: + configfile = open(args.saveconfig, 'w') + except: + print_('Unable to open configuration file %s' + % args.saveconfig) + sys.exit(1) + configfile.write(str(config)) + configfile.close() + else: + if not args.simple: + print_('Loading speedtest.net configuration from %s' + % args.loadconfig) + try: + confFile = open(args.loadconfig, 'r') + except: + print_('Unable to open configuration file %s' % args.loadconfig) + sys.exit(1) + config = eval(confFile.read()) + confFile.close() + + if args.showconfig: + print_('Speedtest.net configuration:') + for configitem in config: + print_(' %s: %s' % (configitem, config[configitem])) + sys.exit(0) + + # Retrieve speedtest server list + if (args.list or args.listservers or args.saveservers is not None + or (args.server and args.loadservers is None)): + if not args.simple: + print_('Retrieving speedtest.net server list...') + servers = closestServers(config['client'], 0) # return all servers + + # Display the server list and exit + if args.list or args.listservers: serverList = [] for server in servers: line = ('%(id)4s) %(sponsor)s (%(name)s, %(country)s) ' @@ -532,8 +606,38 @@ def speedtest(): except IOError: pass sys.exit(0) + elif args.saveservers is not None: + if not args.simple: + print_('Saving speedtest.net server list to %s...' + % args.saveservers) + try: + svrFile = open(args.saveservers, 'w') + except: + print_('Unable to open server file') + sys.exit(1) + svrFile.write(str(servers)) + svrFile.close() + print_('Done') + sys.exit(0) + elif args.loadservers is not None: + if not args.simple: + print_('Loading speedtest.net server list from %s' + % args.loadservers) + try: + svrFile = open(args.loadservers, 'r') + except: + print_('Unable to open server file') + sys.exit(1) + allServers = eval(svrFile.read()) + svrFile.close() + if len(allServers) > 10: + servers = allServers[:5] + else: + servers = allServers else: - servers = closestServers(config['client']) + if not args.simple: + print_('Retrieving speedtest.net server list...') + servers = closestServers(config['client']) # return closest 5 servers if not args.simple: print_('Testing from %(isp)s (%(ip)s)...' % config['client']) @@ -553,7 +657,8 @@ def speedtest(): url = args.mini urlparts = urlparse(url) try: - f = urlopen(args.mini) + request = build_request(args.mini) + f = urlopen(request) except: print_('Invalid Speedtest Mini URL') sys.exit(1) @@ -564,7 +669,9 @@ def speedtest(): if not extension: for ext in ['php', 'asp', 'aspx', 'jsp']: try: - f = urlopen('%s/speedtest/upload.%s' % (args.mini, ext)) + request = build_request('%s/speedtest/upload.%s' % + (args.mini, ext)) + f = urlopen(request) except: pass else: @@ -617,10 +724,11 @@ def speedtest(): if not args.simple: print_('Testing download speed', end='') dlspeed = downloadSpeed(urls, args.simple) + dlspdstr = '%0.2f M%s/s' % ((dlspeed / 1000 / 1000) + * args.units[1], args.units[0]) if not args.simple: print_() - print_('Download: %0.2f M%s/s' % - ((dlspeed / 1000 / 1000) * args.units[1], args.units[0])) + print_('Download: %s' % dlspdstr) sizesizes = [int(.25 * 1000 * 1000), int(.5 * 1000 * 1000)] sizes = [] @@ -630,10 +738,25 @@ def speedtest(): if not args.simple: print_('Testing upload speed', end='') ulspeed = uploadSpeed(best['url'], sizes, args.simple) + ulspdstr = '%0.2f M%s/s' % ((ulspeed / 1000 / 1000) + * args.units[1], args.units[0]) if not args.simple: print_() - print_('Upload: %0.2f M%s/s' % - ((ulspeed / 1000 / 1000) * args.units[1], args.units[0])) + print_('Upload: %s' % ulspdstr) + + # Save test results + if args.saveresults is not None: + if not args.simple: + print_('Saving test results to %s' % args.saveresults) + try: + resfile = open(args.saveresults, 'a') + except: + print_('Unable to open results file') + sys.exit(1) + resfile.write(('%(id)s,%(sponsor)s,%(name)s,%(country)s,' + '%(latency)s ms,' % best) + ('%s,%s\n' + % (dlspdstr, ulspdstr))) + resfile.close() if args.share and args.mini: print_('Cannot generate a speedtest.net share results image while ' @@ -659,10 +782,11 @@ def speedtest(): (ping, ulspeedk, dlspeedk, '297aae72')) .encode()).hexdigest()] - req = Request('http://www.speedtest.net/api/api.php', - data='&'.join(apiData).encode()) - req.add_header('Referer', 'http://c.speedtest.net/flash/speedtest.swf') - f = urlopen(req) + headers = {'Referer': 'http://c.speedtest.net/flash/speedtest.swf'} + request = build_request('http://www.speedtest.net/api/api.php', + data='&'.join(apiData).encode(), + headers=headers) + f = urlopen(request) response = f.read() code = f.code f.close()