diff options
Diffstat (limited to 'fg21sim')
| -rw-r--r-- | fg21sim/webui/app.py | 13 | ||||
| -rw-r--r-- | fg21sim/webui/websocket.py | 87 | 
2 files changed, 88 insertions, 12 deletions
diff --git a/fg21sim/webui/app.py b/fg21sim/webui/app.py index 63d80be..4fa0a05 100644 --- a/fg21sim/webui/app.py +++ b/fg21sim/webui/app.py @@ -2,16 +2,20 @@  # MIT license  """ -Web user interface (UI) of "fg21sim" based upon Tornado_. +Web user interface (UI) of "fg21sim" based upon Tornado_ web server and +using the WebSocket_ protocol.  .. _Tornado: http://www.tornadoweb.org/ + +.. _WebSocket: https://en.wikipedia.org/wiki/WebSocket , +   http://caniuse.com/#feat=websockets  """  import os  import tornado.web -from .websocket import EchoWSHandler +from .websocket import FG21simWSHandler  class IndexHandler(tornado.web.RequestHandler): @@ -20,7 +24,8 @@ class IndexHandler(tornado.web.RequestHandler):  _settings = { -    # The static files will be served from the default "/static/" URI +    # The static files will be served from the default "/static/" URI. +    # Recommend to use `{{ static_url(filepath) }}` in the templates.      "static_path": os.path.join(os.path.dirname(__file__), "static"),      "template_path": os.path.join(os.path.dirname(__file__), "templates"),  } @@ -32,6 +37,6 @@ def make_application(**kwargs):      appplication = tornado.web.Application(          handlers=[              (r"/", IndexHandler), -            (r"/ws", EchoWSHandler), +            (r"/ws", FG21simWSHandler),          ], **settings)      return appplication diff --git a/fg21sim/webui/websocket.py b/fg21sim/webui/websocket.py index ab846df..5b9d5ea 100644 --- a/fg21sim/webui/websocket.py +++ b/fg21sim/webui/websocket.py @@ -3,25 +3,96 @@  """  Communicate with the "fg21sim" simulation program through the Web UI using -the WebSocket_ technique, which provides full-duplex communication channels +the WebSocket_ protocol, which provides full-duplex communication channels  over a single TCP connection. -.. _WebSocket: https://en.wikipedia.org/wiki/WebSocket , -   http://caniuse.com/#feat=websockets +.. _WebSocket: https://en.wikipedia.org/wiki/WebSocket + + +References +---------- +- Tornado WebSocket: +  http://www.tornadoweb.org/en/stable/websocket.html +- Can I Use: WebSocket: +  http://caniuse.com/#feat=websockets  """ +from urllib.parse import urlparse +import logging +  import tornado.websocket -class EchoWSHandler(tornado.websocket.WebSocketHandler): +logger = logging.getLogger(__name__) + + +class FG21simWSHandler(tornado.websocket.WebSocketHandler): +    """ +    WebSocket for bi-directional communication between the Web UI and +    the server, which can deal with the configurations and execute the +    simulation task. + +    Generally, WebSocket send and receive data as *string*.  Therefore, +    the more complex data are stringified as JSON string before sending, +    which will be parsed after receive. + +    Each message (as a JSON object or Python dictionary) has a ``type`` +    field which will be used to determine the following action to take. + +    Attributes +    ---------- +    name : str +        Name to distinguish this WebSocket handle. +    from_localhost : bool +        Set to ``True`` if the access is from the localhost, +        otherwise ``False``. +    """ +    name = "fg21sim" +    from_localhost = None +      def open(self): -        print("WebSocket opened") +        """Invoked when a new WebSocket is opened by the client.""" +        logger.info("WebSocket: %s: opened" % self.name)      def on_message(self, message): -        print("Message received: %s" % message) +        """Handle incoming messages.""" +        logger.info("WebSocket: %s: received: %s" % (self.name, message))          msg_back = message[::-1] -        print("Message sent back: %s" % msg_back) +        logger.info("WebSocket: %s: sent: %s" % (self.name, msg_back))          self.write_message(msg_back)      def on_close(self): -        print("WebSocket closed") +        """Invoked when a new WebSocket is closed by the client.""" +        if hasattr(self, "close_code"): +            code = self.close_code +        if hasattr(self, "close_reason"): +            reason = self.close_reason +        logger.info("WebSocket: {0}: closed by client: {1}, {2}".format( +            self.name, code, reason)) + +    def check_origin(self, origin): +        """Check the origin of the WebSocket access. + +        Attributes +        ---------- +        from_localhost : bool +            Set to ``True`` if the access is from the localhost, +            otherwise ``False``. + +        NOTE +        ---- +        Currently, only allow access from the ``localhost`` +        (i.e., 127.0.0.1) and local LAN. +        """ +        logger.info("WebSocket: {0}: access origin: {1}".format( +            self.name, origin)) +        # NOTE: `urlparse`: `scheme://netloc/path;parameters?query#fragment` +        netloc = urlparse(origin).netloc +        # NOTE: `netloc` example: `user:pass@example.com:8080` +        host = netloc.split("@")[-1].split(":")[0] +        if host in ["localhost", "127.0.0.1"]: +            self.from_localhost = True +            logger.info("WebSocket: %s: access from localhost" % self.name) +            return True +        # XXX/TODO: check whether from the local LAN ?? +        return False  | 
