import codecs
import errno
import os
import random
import re
import socket
import sys
import threading
import webbrowser
import time
from .__version__ import __version__
if sys.version_info[0] > 2:
from urllib.parse import urlparse
from urllib.parse import unquote
from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
else:
from urlparse import urlparse
from urlparse import unquote
from BaseHTTPServer import HTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
class HTTPRequestHandler(BaseHTTPRequestHandler):
def handler(self):
if not hasattr(self, 'mime_types_map'):
self.mime_types_map = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.png': 'image/png',
'.gif': 'image/gif',
'.jpg': 'image/jpeg',
'.ico': 'image/x-icon',
'.json': 'application/json',
'.pb': 'application/octet-stream',
'.ttf': 'font/truetype',
'.otf': 'font/opentype',
'.eot': 'application/vnd.ms-fontobject',
'.woff': 'font/woff',
'.woff2': 'application/font-woff2',
'.svg': 'image/svg+xml'
}
pathname = urlparse(self.path).path
folder = os.path.dirname(os.path.realpath(__file__))
location = folder + pathname
status_code = 0
headers = {}
buffer = None
data = '/data/'
if status_code == 0:
if pathname == '/':
meta = []
meta.append('')
meta.append('')
if self.file:
meta.append('')
with codecs.open(location + 'index.html', mode="r", encoding="utf-8") as open_file:
buffer = open_file.read()
buffer = re.sub(r'', '\n'.join(meta), buffer)
buffer = buffer.encode('utf-8')
headers['Content-Type'] = 'text/html'
headers['Content-Length'] = len(buffer)
status_code = 200
elif pathname.startswith(data):
file = pathname[len(data):]
if file == self.file and self.data:
buffer = self.data
else:
file = self.folder + '/' + unquote(file)
status_code = 404
if os.path.exists(file):
with open(file, 'rb') as binary:
buffer = binary.read()
if buffer:
headers['Content-Type'] = 'application/octet-stream'
headers['Content-Length'] = len(buffer)
status_code = 200
else:
if os.path.exists(location) and not os.path.isdir(location):
extension = os.path.splitext(location)[1]
content_type = self.mime_types_map[extension]
if content_type:
with open(location, 'rb') as binary:
buffer = binary.read()
headers['Content-Type'] = content_type
headers['Content-Length'] = len(buffer)
status_code = 200
else:
status_code = 404
if self.log:
sys.stdout.write(str(status_code) + ' ' + self.command + ' ' + self.path + '\n')
sys.stdout.flush()
self.send_response(status_code)
for key in headers:
self.send_header(key, headers[key])
self.end_headers()
if self.command != 'HEAD':
if status_code == 404 and buffer is None:
self.wfile.write(bytes(status_code))
elif (status_code in (200, 404)) and buffer is not None:
self.wfile.write(buffer)
def do_GET(self):
self.handler()
def do_HEAD(self):
self.handler()
def log_message(self, format, *args):
return
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
pass
class HTTPServerThread(threading.Thread):
def __init__(self, data, file, address, log):
threading.Thread.__init__(self)
self.address = address
self.url = 'http://' + address[0] + ':' + str(address[1])
self.file = file
self.server = ThreadedHTTPServer(address, HTTPRequestHandler)
self.server.timeout = 0.25
if file:
self.server.RequestHandlerClass.folder = os.path.dirname(file) if os.path.dirname(file) else '.'
self.server.RequestHandlerClass.file = os.path.basename(file)
else:
self.server.RequestHandlerClass.folder = ''
self.server.RequestHandlerClass.file = ''
self.server.RequestHandlerClass.data = data
self.server.RequestHandlerClass.log = log
self.terminate_event = threading.Event()
self.terminate_event.set()
self.stop_event = threading.Event()
def run(self):
self.stop_event.clear()
self.terminate_event.clear()
try:
while not self.stop_event.is_set():
self.server.handle_request()
except Exception:
pass
self.terminate_event.set()
self.stop_event.clear()
def stop(self):
if self.alive():
sys.stdout.write("Stopping " + self.url + "\n")
self.stop_event.set()
self.server.server_close()
self.terminate_event.wait(1000)
def alive(self):
return not self.terminate_event.is_set()
_thread_list = []
def _add_thread(thread):
global _thread_list
_thread_list.append(thread)
def _update_thread_list(address=None):
global _thread_list
_thread_list = [ thread for thread in _thread_list if thread.alive() ]
threads = _thread_list
if address is not None:
address = _make_address(address)
if address[1] is None:
threads = [ thread for thread in threads if address[0] == thread.address[0] ]
else:
threads = [ thread for thread in threads if address[0] == thread.address[0] and address[1] == thread.address[1] ]
return threads
def _make_address(address):
if address is None or isinstance(address, int):
port = address
address = ('localhost', port)
if isinstance(address, tuple) and len(address) == 2:
host = address[0]
port = address[1]
if isinstance(host, str) and (port is None or isinstance(port, int)):
return address
raise ValueError('Invalid address.')
def _make_port(address):
if address[1] is None or address[1] == 0:
ports = []
if address[1] != 0:
ports.append(8080)
ports.append(8081)
rnd = random.Random()
for _ in range(4):
port = rnd.randrange(15000, 25000)
if port not in ports:
ports.append(port)
ports.append(0)
for port in ports:
temp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
temp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
temp_socket.settimeout(1)
try:
temp_socket.bind((address[0], port))
sockname = temp_socket.getsockname()
address = (address[0], sockname[1])
return address
except:
pass
finally:
temp_socket.close()
if isinstance(address[1], int):
return address
raise ValueError('Failed to allocate port.')
def stop(address=None):
'''Stop serving model at address.
Args:
address (tuple, optional): A (host, port) tuple, or a port number.
'''
threads = _update_thread_list(address)
for thread in threads:
thread.stop()
_update_thread_list()
def status(adrress=None):
'''Is model served at address.
Args:
address (tuple, optional): A (host, port) tuple, or a port number.
'''
threads = _update_thread_list(adrress)
return len(threads) > 0
def wait():
'''Wait for console exit and stop all model servers.'''
try:
while len(_update_thread_list()) > 0:
time.sleep(1000)
except (KeyboardInterrupt, SystemExit):
sys.stdout.write('\n')
sys.stdout.flush()
stop()
def serve(file, data, address=None, browse=False, log=False):
'''Start serving model from file or data buffer at address and open in web browser.
Args:
file (string): Model file to serve. Required to detect format.
data (bytes): Model data to serve. None will load data from file.
log (bool, optional): Log details to console. Default: False
browse (bool, optional): Launch web browser. Default: True
address (tuple, optional): A (host, port) tuple, or a port number.
Returns:
A (host, port) address tuple.
'''
if not data and file and not os.path.exists(file):
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), file)
_update_thread_list()
address = _make_address(address)
if isinstance(address[1], int) and address[1] != 0:
stop(address)
else:
address = _make_port(address)
_update_thread_list()
thread = HTTPServerThread(data, file, address, log)
thread.start()
while not thread.alive():
time.sleep(10)
_add_thread(thread)
if file:
sys.stdout.write("Serving '" + file + "' at " + thread.url + "\n")
else:
sys.stdout.write("Serving at " + thread.url + "\n")
sys.stdout.flush()
if browse:
webbrowser.open(thread.url)
return address
def start(file=None, address=None, browse=True, log=False):
'''Start serving model file at address and open in web browser.
Args:
file (string): Model file to serve.
log (bool, optional): Log details to console. Default: False
browse (bool, optional): Launch web browser, Default: True
address (tuple, optional): A (host, port) tuple, or a port number.
Returns:
A (host, port) address tuple.
'''
return serve(file, None, browse=browse, address=address, log=log)