from parlay.protocols.base_protocol import BaseProtocol
from parlay.server.adapter import Adapter
from autobahn.twisted.websocket import WebSocketClientFactory, WebSocketServerProtocol, WebSocketClientProtocol
from parlay.server.broker import Broker
import json
from twisted.internet import defer
from twisted.internet.protocol import Factory
[docs]class WebSocketServerAdapter(WebSocketServerProtocol, Adapter):
"""
When a client connects over a websocket, this is the protocol that will handle the communication.
The messages are encoded as a JSON string
"""
broker = Broker.get_instance()
def __init__(self, broker=None):
WebSocketServerProtocol.__init__(self)
self._discovery_response_defer = None
self._protocol_response_defer = None
self._open_protocol_response_defer = None
[docs] def onClose(self, wasClean, code, reason):
print "Closing:" + str(self)
# clean up after ourselves
self.broker.adapters.remove(self)
[docs] def send_message_as_JSON(self, msg):
"""
Send a message dictionary as JSON
"""
self.sendMessage(json.dumps(msg))
[docs] def onMessage(self, payload, isBinary):
if not isBinary:
msg = json.loads(payload)
# if we're waiting for discovery and its a discovery response
if self._discovery_response_defer is not None and \
msg['TOPICS'].get('type', None) == 'get_protocol_discovery_response':
# discovery!
# get skeleton
discovery = msg['CONTENTS'].get('discovery', [])
self._discovery_response_defer.callback(discovery)
self._discovery_response_defer = None
# if we're waiting for a protocol list and its a protocol response
elif self._protocol_response_defer is not None and \
msg['TOPICS'].get('type', None) == 'get_protocol_list_response':
protocol_list = msg['CONTENTS'].get('protocol_list', [])
self._protocol_response_defer.callback(protocol_list)
self._protocol_response_defer = None
# else its just a regular message, publish it.
else:
self.broker.publish(msg, self.send_message_as_JSON)
else:
print "Binary messages not supported"
[docs] def onConnect(self, request):
# let the broker know we exist!
self.broker.adapters.append(self)
[docs] def discover(self, force):
# already in the middle of discovery
if self._discovery_response_defer is not None:
return self._discovery_response_defer
self._discovery_response_defer = defer.Deferred()
self.send_message_as_JSON({'TOPICS': {'type': 'get_protocol_discovery'}, 'CONTENTS': {}})
def timeout():
if self._discovery_response_defer is not None:
# call back with nothing if timeout
self._discovery_response_defer.callback({})
self._discovery_response_defer = None
self.broker.reactor.callLater(10, timeout)
return self._discovery_response_defer
[docs] def get_protocols(self):
"""
Return a list of protocols that could potentially be opened.
Return a deferred if this is not ready yet
"""
# already in the middle of discovery
if self._protocol_response_defer is not None:
return self._protocol_response_defer
self._protocol_response_defer = defer.Deferred()
self.send_message_as_JSON({'TOPICS': {'type': 'get_protocol_list'}, 'CONTENTS': {}})
def timeout():
if self._protocol_response_defer is not None:
# call back with nothing if timeout
self._protocol_response_defer.callback({})
self._protocol_response_defer = None
self.broker.reactor.callLater(2, timeout)
return self._protocol_response_defer
[docs] def get_open_protocols(self):
return []
def __str__(self):
return "Websocket at " + str(self.peer)
[docs]class WebsocketClientAdapter(Adapter, WebSocketClientProtocol):
"""
Connect a Python item to the Broker over a Websocket
"""
def __init__(self):
WebSocketClientProtocol.__init__(self)
Adapter.__init__(self)
self._subscribe_q = []
self._listener_list = [] # no way to unsubscribe. Subscriptions last
[docs] def onConnect(self, request):
WebSocketClientProtocol.onConnect(self, request)
self._connected.callback(True)
# flush our subscription requests
for _fn, topics in self._subscribe_q:
self.subscribe(_fn, **topics)
self._subscribe_q = [] # empty the list
[docs] def call_on_every_message(self, listener):
self._listener_list.append(listener)
[docs] def onMessage(self, packet, isBinary):
"""
We got a message. See who wants to process it.
"""
if isBinary:
print "WebsocketBrokerProtocol doesn't understand binary messages"
return
msg = json.loads(packet)
# run it through the listeners for processing
for fn in self._listener_list:
fn(msg)
[docs] def subscribe(self, _fn=None, **topics):
"""
Subscribe to messages the topics in **kwargs
"""
# wait until we're connected to subscribe
if not self.connected:
self._subscribe_q.append((_fn, topics))
return
self.publish({"TOPICS": {'type': 'subscribe'}, "CONTENTS": {'TOPICS': topics}})
if _fn is not None:
def listener(msg):
t = msg["TOPICS"]
if all(k in t and v == t[k] for k, v in t.iteritems()):
_fn(msg)
self._listener_list.append(listener)
[docs] def publish(self, msg, callback=None):
if not self.connected:
raise RuntimeError("Not Connected to Broker yet")
self.sendMessage(json.dumps(msg))
[docs]class WebsocketClientAdapterFactory(WebSocketClientFactory):
def __init__(self, *args, **kwargs):
self.adapter = WebsocketClientAdapter() # this is the adapter singleton
WebSocketClientFactory.__init__(self, *args, **kwargs)
[docs] def buildProtocol(self, addr):
adapter = self.adapter
adapter.factory = self
return adapter