text: import os
  import re
  import time
  import json
  import stat
  import email
  try:  # python 2
      from Queue import Queue
  except ImportError:  # python 3
      from queue import Queue
  import struct
  import socket
  import logging
  import binascii
  import base64
  import datetime
  import mimetypes
  import functools
  import threading
  
  import numpy as np
  import tornado.web
  import tornado.ioloop
  import tornado.httpserver
  from tornado import websocket
  from tornado.web import HTTPError
  
  cwd = os.path.split(os.path.abspath(__file__))[0]
  hostname = socket.gethostname()
  
  def make_base64(imgfile):
      with open(imgfile, 'rb') as img:
          mtype = mimetypes.guess_type(imgfile)[0]
          imbytes = base64.encodebytes(img.read())
          data = imbytes.decode('utf-8').strip()
          return u"data:{mtype};base64,{data}".format(mtype=mtype, data=data)
  
  class NPEncode(json.JSONEncoder):
      def default(self, obj):
          if isinstance(obj, np.ndarray):
              if obj.dtype == np.float:
                  obj = obj.astype(np.float32)
              elif obj.dtype == np.int:
                  obj = obj.astype(np.int32)
  
              return dict(
                  __class__="NParray",
                  dtype=obj.dtype.descr[0][1], 
                  shape=obj.shape, 
                  data=binascii.b2a_base64(obj.tostring()).decode('utf-8'))
          elif isinstance(obj, (np.int64, np.int32, np.int16, np.int8,
                                np.uint64, np.uint32, np.uint16, np.uint8)):
              return int(obj)
          elif isinstance(obj, (np.float64, np.float32)):
              return float(obj)
          else:
              return super(NPEncode, self).default(obj)
  
  class StaticFileHandler(tornado.web.RequestHandler):
      """A simple handler that can serve static content from a directory.
  
      To map a path to this handler for a static data directory /var/www,
      you would add a line to your application like::
  
          application = web.Application([
              (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
          ])
  
      The local root directory of the content should be passed as the "path"
      argument to the handler.
  
      To support aggressive browser caching, if the argument "v" is given
      with the path, we set an infinite HTTP expiration header. So, if you
      want browsers to cache a file indefinitely, send them to, e.g.,
      /static/images/myimage.png?v=xxx. Override ``get_cache_time`` method for
      more fine-grained cache control.
      """
      CACHE_MAX_AGE = 86400*365*10 #10 years
  
      _static_hashes = {}
  
      def initialize(self, path, default_filename=None):
          self.root = os.path.abspath(path) + os.path.sep
          self.default_filename = default_filename
  
      def head(self, path):
          self.get(path, include_body=False)
  
      def get(self, path, include_body=True):
          self._auto_finish = False
  
          #logging.info('static request %s, %s' % (self.request.uri,  self.request.headers))
          if os.path.sep != "/":
              path = path.replace("/", os.path.sep)
          abspath = os.path.abspath(os.path.join(self.root, path))
          # os.path.abspath strips a trailing /
          # it needs to be temporarily added back for requests to root/
          if not (abspath + os.path.sep).startswith(self.root):
              raise HTTPError(403, "%s is not in root static directory", path)
          if os.path.isdir(abspath) and self.default_filename is not None:
              # need to look at the request.path here for when path is empty
              # but there is some prefix to the path that was already
              # trimmed by the routing
              if not self.request.path.endswith("/"):
                  self.redirect(self.request.path + "/")
                  return
              abspath = os.path.join(abspath, self.default_filename)
          if not os.path.exists(abspath):
              raise HTTPError(404)
          if not os.path.isfile(abspath):
              raise HTTPError(403, "%s is not a file", path)
          self.set_extra_headers(path)
  
          stat_result = os.stat(abspath)
          
          mime_type, encoding = mimetypes.guess_type(abspath)
          if mime_type:
              self.set_header("Content-Type", mime_type)
  
          self.set_header('Accept-Ranges','bytes')
  
          self.file = open(abspath, "rb")
          self._transforms = []
  
          if 'Range' not in self.request.headers:
              modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
              self.set_header("Last-Modified", modified)
  
              cache_time = self.get_cache_time(path, modified, mime_type)
              if cache_time > 0:
                  self.set_header("Expires", datetime.datetime.utcnow() + \
                                             datetime.timedelta(seconds=cache_time))
                  self.set_header("Cache-Control", "max-age=" + str(cache_time))
              else:
                  self.set_header("Cache-Control", "public")
  
              # Check the If-Modified-Since, and don't send the result if the
              # content has not been modified
              ims_value = self.request.headers.get("If-Modified-Since")
              if ims_value is not None:
                  date_tuple = email.utils.parsedate(ims_value)
                  if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple))
                  if if_since >= modified:
                      self.set_status(304)
                      self.finish()
                      return
  
              self.bytes_start = 0
              self.bytes_end = stat_result.st_size - 1
              if not include_body:
                  self.file.close()
                  self.finish()
                  return
          else:
              logging.info('got range string %s' % self.request.headers['Range'])
              self.set_status(206)
              rangestr = self.request.headers['Range'].split('=')[1]
              start, end = rangestr.split('-')
              logging.info('seeking to start %s' % start)
              self.bytes_start = int(start)
              self.file.seek(self.bytes_start)
              if not end:
                  self.bytes_end = stat_result.st_size - 1
              else:
                  self.bytes_end = int(end)
  
              clenheader = 'bytes %s-%s/%s' % (self.bytes_start, self.bytes_end, stat_result.st_size)
              self.set_header('Content-Range', clenheader)
              self.set_header('Content-Length', self.bytes_end-self.bytes_start+1)
              logging.info('set content range header %s' % clenheader)
  
          if 'If-Range' in self.request.headers:
              logging.debug('staticfilehandler had if-range header %s' % self.request.headers['If-Range'])
  
  
          self.bytes_remaining = self.bytes_end - self.bytes_start + 1
          self.set_header('Content-Length', str(self.bytes_remaining))
          self.bufsize = 4096 * 16
          #logging.info('writing to frontend: %s' % self._generate_headers())
          self.flush() # flush out the headers
          self.stream_one()
  
      def stream_one(self):
          if self.request.connection.stream.closed():
              self.file.close()
              return
  
          if self.bytes_remaining == 0:
              self.file.close()
              self.finish()
          else:
              data = self.file.read(min(self.bytes_remaining, self.bufsize))
              self.bytes_remaining -= len(data)
              #logging.info('read from disk %s, remaining %s' % (len(data), self.bytes_remaining))
              self.request.connection.stream.write( data, self.stream_one )
  
      def set_extra_headers(self, path):
          """For subclass to add extra headers to the response"""
          pass
  
      def get_cache_time(self, path, modified, mime_type):
          """Override to customize cache control behavior.
  
          Return a positive number of seconds to trigger aggressive caching or 0
          to mark resource as cacheable, only.
  
          By default returns cache expiry of 10 years for resources requested
          with "v" argument.
          """
          return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
  
      @classmethod
      def make_static_url(cls, settings, path):
          """Constructs a versioned url for the given path.
  
          This method may be overridden in subclasses (but note that it is
          a class method rather than an instance method).
          
          ``settings`` is the `Application.settings` dictionary.  ``path``
          is the static path being requested.  The url returned should be
          relative to the current host.
          """
          hashes = cls._static_hashes
          abs_path = os.path.join(settings["static_path"], path)
          if abs_path not in hashes:
              try:
                  f = open(abs_path, "rb")
                  hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
                  f.close()
              except Exception:
                  logging.error("Could not open static file %r", path)
                  hashes[abs_path] = None
          static_url_prefix = settings.get('static_url_prefix', '/static/')
          if hashes.get(abs_path):
              return static_url_prefix + path + "?v=" + hashes[abs_path][:5]
          else:
              return static_url_prefix + path
  
  
  class ClientSocket(websocket.WebSocketHandler):
      def initialize(self, parent):
          self.parent = parent
  
      def open(self):
          self.parent.sockets.append(self)
  
      def on_close(self):
          self.parent.sockets.remove(self)
          if self.parent.n_clients == 0 and self.parent.disconnect_on_close:
              self.parent.stop()
  
      def on_message(self, message):
          if (message == "connect"):
              self.parent.connect.set()
          else:
              self.parent.response.put(message)
  
  class WebApp(threading.Thread):
      daemon = True
      disconnect_on_close = True
  
      def __init__(self, handlers, port):
          super(WebApp, self).__init__()
          self.handlers = handlers + [
              (r"/wsconnect/", ClientSocket, dict(parent=self)),
              (r"/(.*)", tornado.web.StaticFileHandler, dict(path=cwd)),
          ]
          self.port = port
          self.response = Queue()
          self.connect = threading.Event()
          self.sockets = []
  
      @property
      def n_clients(self):
          num = len(self.sockets)
          return num
  
      def run(self):
          ioloop = tornado.ioloop.IOLoop()
          ioloop.clear_current()
          ioloop.make_current()
          application = tornado.web.Application(self.handlers, gzip=True)
          # If tornado version is 5.0 or greater, io_loop arg does not exist
          if tornado.version_info[0] < 5:
              self.server = tornado.httpserver.HTTPServer(application, io_loop=ioloop)
          else:
              self.server = tornado.httpserver.HTTPServer(application)
          self.server.listen(self.port)
          ioloop.start()
  
      def stop(self):
          print("Stopping server")
          self.server.stop()
          tornado.ioloop.IOLoop.current().stop()
  
      def send(self, **msg):
          if not isinstance(msg, str):
              msg = json.dumps(msg, cls=NPEncode, ensure_ascii=False)
  
          for sock in self.sockets:
              sock.write_message(msg)
  
          try:
              return [json.loads(self.response.get(timeout=2)) for _ in range(self.n_clients)]
          except:
              return [None for _ in range(self.n_clients)]
  
      def get_client(self):
          self.connect.wait()
          self.connect.clear()
          return JSProxy(self.send)
  
  class JSProxy(object):
      def __init__(self, sendfunc, name="window"):
          super(JSProxy, self).__setattr__('send', sendfunc)
          super(JSProxy, self).__setattr__('name', name)
          
          self.attrs = self.send(method='query', params=[self.name])[0]
      
      def __getattr__(self, attr):
          if attr == 'attrs':
              return self.send(method='query', params=[self.name])[0]
  
          assert attr in self.attrs
          if self.attrs[attr][0] in ["object", "function"]:
              return JSProxy(self.send, "%s.%s"%(self.name, attr))
          else:
              return self.attrs[attr][1]
  
      def __setattr__(self, attr, value):
          if hasattr(self, "attrs") and self.attrs is None:
              return super(JSProxy, self).__setattr__(attr, value)
          if not hasattr(self, "attrs") or attr not in self.attrs:
              return super(JSProxy, self).__setattr__(attr, value)
  
          assert self.attrs[attr] not in ["object", "function"]
          resp = self.send(method='set',
                           params=["%s.%s"%(self.name, attr), value])
          if isinstance(resp[0], dict) and "error" in resp[0]:
              raise Exception(resp[0]['error'])
          else:
              return resp
  
      def __repr__(self):
          return "<JS: %s>"%self.name
  
      def __dir__(self):
          return list(self.attrs)
  
      def __call__(self, *args):
          resp = self.send(method='run', params=[self.name, args])
          if isinstance(resp[0], dict) and "error" in resp[0]:
              raise Exception(resp[0]['error'])
          else:
              return resp
  
      def __getitem__(self, idx):
          assert not isinstance(idx, (slice, list, tuple, np.ndarray))
          return JSProxy(self.send, "%s.%d"%(self.name, idx))
  
  if __name__ == "__main__":
      app = WebApp([], 8888)
      app.start()
      app.join()
  