aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--fg21sim/webui/app.py13
-rw-r--r--fg21sim/webui/websocket.py87
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