server.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import codecs
  2. import errno
  3. import os
  4. import platform
  5. import re
  6. import sys
  7. import threading
  8. import webbrowser
  9. import time
  10. import urllib.parse
  11. from .__version__ import __version__
  12. if sys.version_info[0] > 2:
  13. from urllib.parse import urlparse
  14. from http.server import HTTPServer
  15. from http.server import BaseHTTPRequestHandler
  16. from socketserver import ThreadingMixIn
  17. else:
  18. from urlparse import urlparse
  19. from BaseHTTPServer import HTTPServer
  20. from BaseHTTPServer import BaseHTTPRequestHandler
  21. from SocketServer import ThreadingMixIn
  22. class HTTPRequestHandler(BaseHTTPRequestHandler):
  23. def handler(self):
  24. if not hasattr(self, 'mime_types_map'):
  25. self.mime_types_map = {
  26. '.html': 'text/html',
  27. '.js': 'text/javascript',
  28. '.css': 'text/css',
  29. '.png': 'image/png',
  30. '.gif': 'image/gif',
  31. '.jpg': 'image/jpeg',
  32. '.ico': 'image/x-icon',
  33. '.json': 'application/json',
  34. '.pb': 'application/octet-stream',
  35. '.ttf': 'font/truetype',
  36. '.otf': 'font/opentype',
  37. '.eot': 'application/vnd.ms-fontobject',
  38. '.woff': 'font/woff',
  39. '.woff2': 'application/font-woff2',
  40. '.svg': 'image/svg+xml'
  41. }
  42. pathname = urlparse(self.path).path
  43. folder = os.path.dirname(os.path.realpath(__file__))
  44. location = folder + pathname
  45. status_code = 0
  46. headers = {}
  47. buffer = None
  48. data = '/data/'
  49. if status_code == 0:
  50. if pathname == '/':
  51. meta = []
  52. meta.append('<meta name="type" content="Python">')
  53. meta.append('<meta name="version" content="' + __version__ + '">')
  54. if self.file:
  55. meta.append('<meta name="file" content="/data/' + self.file + '">')
  56. with codecs.open(location + 'index.html', mode="r", encoding="utf-8") as open_file:
  57. buffer = open_file.read()
  58. buffer = re.sub(r'<meta name="version" content="\d+.\d+.\d+">', '\n'.join(meta), buffer)
  59. buffer = buffer.encode('utf-8')
  60. headers['Content-Type'] = 'text/html'
  61. headers['Content-Length'] = len(buffer)
  62. status_code = 200
  63. elif pathname.startswith(data):
  64. file = pathname[len(data):]
  65. if file == self.file and self.data:
  66. buffer = self.data
  67. else:
  68. file = self.folder + '/' + urllib.parse.unquote(file)
  69. status_code = 404
  70. if os.path.exists(file):
  71. with open(file, 'rb') as binary:
  72. buffer = binary.read()
  73. if buffer:
  74. headers['Content-Type'] = 'application/octet-stream'
  75. headers['Content-Length'] = len(buffer)
  76. status_code = 200
  77. else:
  78. if os.path.exists(location) and not os.path.isdir(location):
  79. extension = os.path.splitext(location)[1]
  80. content_type = self.mime_types_map[extension]
  81. if content_type:
  82. with open(location, 'rb') as binary:
  83. buffer = binary.read()
  84. headers['Content-Type'] = content_type
  85. headers['Content-Length'] = len(buffer)
  86. status_code = 200
  87. else:
  88. status_code = 404
  89. if self.log:
  90. sys.stdout.write(str(status_code) + ' ' + self.command + ' ' + self.path + '\n')
  91. sys.stdout.flush()
  92. self.send_response(status_code)
  93. for key in headers:
  94. self.send_header(key, headers[key])
  95. self.end_headers()
  96. if self.command != 'HEAD':
  97. if status_code == 404 and buffer is None:
  98. self.wfile.write(bytes(status_code))
  99. elif (status_code == 200 or status_code == 404) and buffer != None:
  100. self.wfile.write(buffer)
  101. return
  102. def do_GET(self):
  103. self.handler()
  104. def do_HEAD(self):
  105. self.handler()
  106. def log_message(self, format, *args):
  107. return
  108. class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): pass
  109. class HTTPServerThread(threading.Thread):
  110. def __init__(self, data, file, log, port, host, url):
  111. threading.Thread.__init__(self)
  112. self.port = port
  113. self.host = host
  114. self.file = file
  115. self.url = url
  116. self.server = ThreadedHTTPServer((host, port), HTTPRequestHandler)
  117. self.server.timeout = 0.25
  118. if file:
  119. self.server.RequestHandlerClass.folder = os.path.dirname(file) if os.path.dirname(file) else '.'
  120. self.server.RequestHandlerClass.file = os.path.basename(file)
  121. else:
  122. self.server.RequestHandlerClass.folder = ''
  123. self.server.RequestHandlerClass.file = ''
  124. self.server.RequestHandlerClass.data = data
  125. self.server.RequestHandlerClass.log = log
  126. self.terminate_event = threading.Event()
  127. self.terminate_event.set()
  128. self.stop_event = threading.Event()
  129. def run(self):
  130. self.stop_event.clear()
  131. self.terminate_event.clear()
  132. try:
  133. while not self.stop_event.is_set():
  134. self.server.handle_request()
  135. except Exception:
  136. pass
  137. self.terminate_event.set()
  138. self.stop_event.clear()
  139. def stop(self):
  140. if self.alive():
  141. sys.stdout.write("\nStopping " + self.url + "\n")
  142. self.stop_event.set()
  143. self.server.server_close()
  144. self.terminate_event.wait(1000)
  145. def alive(self):
  146. return not self.terminate_event.is_set()
  147. thread_list = []
  148. def stop(port=8080, host='localhost'):
  149. '''Stop serving model at host:port.
  150. Args:
  151. port (int, optional): port to stop. Default: 8080
  152. host (string, optional): host to stop. Default: ''
  153. '''
  154. global thread_list
  155. for thread in thread_list:
  156. if port == thread.port and host == thread.host:
  157. thread.stop()
  158. thread_list = [ thread for thread in thread_list if thread.alive() ]
  159. def wait():
  160. '''Wait for console exit and stop all model servers.'''
  161. global thread_list
  162. try:
  163. while len(thread_list) > 0:
  164. thread_list = [ thread for thread in thread_list if thread.alive() ]
  165. time.sleep(1000)
  166. except (KeyboardInterrupt, SystemExit):
  167. for thread in thread_list:
  168. thread.stop()
  169. thread_list = [ thread for thread in thread_list if thread.alive() ]
  170. def serve(file, data, log=False, browse=False, port=8080, host='localhost'):
  171. '''Start serving model from file or data buffer at host:port and open in web browser.
  172. Args:
  173. file (string): Model file to serve. Required to detect format.
  174. data (bytes): Model data to serve. None will load data from file.
  175. log (bool, optional): Log details to console. Default: False
  176. browse (bool, optional): Launch web browser, Default: True
  177. port (int, optional): Port to serve. Default: 8080
  178. host (string, optional): Host to serve. Default: ''
  179. '''
  180. global thread_list
  181. if not data and file and not os.path.exists(file):
  182. raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), file)
  183. stop(port, host)
  184. url = 'http://' + host + ':' + str(port)
  185. thread_list = [ thread for thread in thread_list if thread.alive() ]
  186. thread = HTTPServerThread(data, file, log, port, host, url)
  187. thread.start()
  188. while not thread.alive():
  189. time.sleep(10)
  190. thread_list.append(thread)
  191. if file:
  192. sys.stdout.write("Serving '" + file + "' at " + url + "\n")
  193. else:
  194. sys.stdout.write("Serving at " + url + "\n")
  195. sys.stdout.flush()
  196. if browse:
  197. webbrowser.open(url)
  198. def start(file=None, log=False, browse=True, port=8080, host='localhost'):
  199. '''Start serving model file at host:port and open in web browser
  200. Args:
  201. file (string): Model file to serve.
  202. log (bool, optional): Log details to console. Default: False
  203. browse (bool, optional): Launch web browser, Default: True
  204. port (int, optional): Port to serve. Default: 8080
  205. host (string, optional): Host to serve. Default: ''
  206. '''
  207. serve(file, None, log=log, browse=browse, port=port, host=host)