Table of Contents
undergr0und is a wrapper around ws4py that is needed for routing and exchanging routes automatically. Some light modification (~3 lines) were made to ws4py itself to allow for server-side handler kwargs.
There is also a JavaScript implementation that was required for dashb0ard: undergr0und.js
Name
Highway, undergr0und, ... what is going on here?
Highway was renamed to undergr0und during development to be more in line with the other fl0w components.
Why
fl0w requires a very specialised networking solution in order to remain as modular as possible while still being reasonably high level. That's why every single component of fl0w can implement its own server/client network-subprotocol which is then "routed through a pipe" to the same component on the other side. undergr0und also utilises this mechanic to manage itself. There is no explicit receive call, the network protocol is completely asynchronous.
Example
Client
from undergr0und import Client as BaseClient
from undergr0und import Route
class Info(Route):
def run(self, data, handler):
print(data)
class Client(BaseClient):
def ready(self):
self.send(None, "info")
try:
ws = Client('ws://127.0.0.1:3077')
# setup has to be called before the connection is established
ws.setup({"info" : Info()}, debug=True)
ws.connect()
ws.run_forever()
except KeyboardInterrupt:
ws.close()
Server
from wsgiref.simple_server import make_server
from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler
from ws4py.server.wsgiutils import WebSocketWSGIApplication
from undergr0und import Server as BaseServer
from undergr0und import Route
from time import time
class Info(Route):
def run(self, data, handler):
handler.send(time(), handler.reverse_routes[self])
class Server(BaseServer):
pass
server = make_server("127.0.0.1", 3077,
server_class=WSGIServer, handler_class=WebSocketWSGIRequestHandler,
app=WebSocketWSGIApplication(handler_cls=Server,
handler_args={"routes" : {"info" : Info()}, "debug" : True}))
server.initialize_websockets_manager()
try:
server.serve_forever()
except KeyboardInterrupt:
server.server_close()
Tidbits
class Route(Route):
def run(self, data, handler):
# Called when a message for that particular route is received.
def start(self, handler):
# Called once the connection is ready.
class Handler(undergr0und.Server | undergr0und.Client):
def ready(self):
# Called once the handler has been initialised.
Protocol
The first 4 bytes of every message are reserved for the data type and the route id. All following bytes contain the actual data. (struct: Bh)
Data type | Route ID | Data |
---|---|---|
1 | 2 | * |
Data Types
Data type | ID |
---|---|
String | 0 |
Dictionary | 1 |
List | 1 |
Bytes | 2 |
Integer | 3 |
Float | 4 |
Indexed dictionary | 5 |
None | 6 |
Error Codes
Error | Code |
---|---|
Invalid Route | 1 |
Invalid Metadata Layout | 2 |
Invalid Data Type | 3 |