Search code examples
autobahnpython-3.5crossbar

Crossbar.io/Autobahn server side session storage


I'm trying to set up a WAMP server that can handle session data of individual clients. However this seems more troublesome than initially thought.

Crossbar config:

{
  "workers": [
    {
      "type": "router",
      "realms": [
        {
          "name": "default",
          "roles": [
            {
              "name": "anonymous",
              "permissions": [
                {
                  "uri": "*",
                  "call": true,
                  "register": true
                }
              ]
            }
          ]
        }
      ],
      "transports": [
        {
          "type": "websocket",
          "endpoint": {
            "type": "tcp",
            "port": 8080
          }
        }
      ],
      "components": [
        {
          "type": "class",
          "classname": "server.Server",
          "realm": "default",
          "role": "anonymous"
        }
      ]
    }
  ]
}

server.py:

The server registers two RPCs, one for appending data and one for returning a string of the data. The data is stored as self.data, but this is storing data for all sessions, not on a per client, per session basis. Once the session dies, the server should clean up the session data. Simply cleaning the list is no solution as simultaneous clients can access each others data. The id of the client is available in the append RPC (if the client discloses itself), however this seems fairly useless at this point.

from autobahn.twisted import wamp
from autobahn.wamp import types


class Server(wamp.ApplicationSession):
    def __init__(self, *args, **kwargs):
        wamp.ApplicationSession.__init__(self, *args, **kwargs)
        self.data = []

    def onJoin(self, details):
        def append(data, details):
            client_id = details.caller
            self.data.append(data)

        def get():
            return ''.join(self.data)

        options = types.RegisterOptions(details_arg='details')
        self.register(append, 'append', options=options)
        self.register(get, 'get')

client.py:

The client connects to the server, waits for the connection to open and executes RPCs. The client first appends 'a' and 'b' to the server's data, then the data is get and printed. The result should be ab as data should be stored per client, per session. Once the session dies, the data should be cleaned up.

import asyncio

from autobahn.asyncio import wamp
from autobahn.asyncio import websocket
from autobahn.wamp import types


class Client(wamp.ApplicationSession):
    def onOpen(self, protocol):
        protocol.session = self
        wamp.ApplicationSession.onOpen(self, protocol)


if __name__ == '__main__':
    session_factory = wamp.ApplicationSessionFactory()
    session_factory.session = Client
    transport_factory = websocket.WampWebSocketClientFactory(session_factory)

    loop = asyncio.get_event_loop()
    transport, protocol = loop.run_until_complete(
        asyncio.async(loop.create_connection(
            transport_factory, 'localhost', '8080',)))

    connected = asyncio.Event()

    @asyncio.coroutine
    def poll():
        session = getattr(protocol, 'session', None)
        if not session:
            yield from asyncio.sleep(1)
            asyncio.ensure_future(poll())
        else:
            connected.set()

    # Wait for session to open.
    asyncio.ensure_future(poll())
    loop.run_until_complete(connected.wait())

    # Client is connected, call RPCs.
    options = types.CallOptions(disclose_me=True)
    protocol.session.call('append', 'a', options=options)
    protocol.session.call('append', 'b', options=options)
    f = protocol.session.call('get', options=options)
    # Get stored data and print it.
    print(loop.run_until_complete(f))

Hope somebody can tell me how I can store data per client, per session in the server's memory.


Solution

  • I managed to create a hack for this. It doesn't seem like the perfect solution, but it works for now.

    from autobahn.twisted import wamp
    from autobahn.wamp import types
    from twisted.internet.defer import inlineCallbacks
    
    
    class Server(wamp.ApplicationSession):
        def __init__(self, *args, **kwargs):
            wamp.ApplicationSession.__init__(self, *args, **kwargs)    
            self.sessions = {}
    
        def onJoin(self, details):
            def on_client_join(details):
                client_id = details['session']
                self.sessions[client_id] = {}
    
            def on_client_leave(client_id):
                self.sessions.pop(client_id)
    
            self.subscribe(on_client_join, 'wamp.session.on_join')
            self.subscribe(on_client_leave, 'wamp.session.on_leave')
    
            def get_session(details):
                return self.sessions[details.caller]
    
            @inlineCallbacks
            def append(data, details):
                session = yield self.call('get_session', details)
                d = session.setdefault('data', [])
                d.append(data)
    
            @inlineCallbacks
            def get(details):
                session = yield self.call('get_session', details)
                return ''.join(session['data'])
    
            reg_options = types.RegisterOptions(details_arg='details')
            self.register(get_session, 'get_session')
            self.register(append, 'append', options=reg_options)
            self.register(get, 'get', options=reg_options)
    

    As session gets created when a client connects (on_client_join), and the session gets destroyed when a client disconnects (on_client_leave).

    The server also needs permission to subscribe to the meta events. config.json:

    ...
    "permissions": [
      {
        "uri": "*",
        "call": true,
        "register": true,
        "subscribe": true,
      }
    ]
    ....