Обратные вызовы от автобана WebSocketClientProtocol к другому объекту

Во-первых, есть класс IO, которому на __init__ передается объект асинхронного цикла (io = IO(loop)), созданный ранее в основном классе. IO затем в какой-то момент инициализирует класс Socket, выполнив self.socket = Socket(self), чтобы объект сокета имел обратный доступ. Позже класс Socket инициализирует класс Websocket, который является подклассом Transport.

class Websocket(Transport):

    name = 'websocket'

    def __init__(self, socket):
        self.socket = socket

    def open(self):
        url = self.prepareUrl()

        factory = WebSocketClientFactory(url, debug = False)
        factory.protocol = Protocol

        websocket = self.socket.loop.create_connection(factory, host=self.socket.io.options.host, port=self.socket.options.port)

        self.socket.io.loop.run_until_complete(websocket)

    def onOpen(self):
        print('print me please!')

Таким образом, объект сокета вызывает self.transport.open() (где self.transport = Websocket(self)), который создает фабрику автобанов, создает асинхронное соединение, выполняя self.socket.loop.create_connection(), а затем добавляет coro future в цикл, выполняя run_until_complete().

Вот тут-то и начинается проблема: для фабрики автобанов требуется класс, который должен наследоваться от autobahn.asyncio.websocket.WebSocketClientProtocol.

Мой класс Protocol(WebSocketClientProtocol) имеет обычный:

class Protocol(WebSocketClientProtocol):

    @asyncio.coroutine
    def onOpen(self):
        print('socket opened!')

Это отлично работает, print('socket opened!') печатает строку, и мой сервер также говорит, что соединение открыто.

Вопрос: из класса Protocol(), когда обратный вызов onOpen() вызывается автобаном, как я могу заставить этот метод вызывать метод transport.onOpen() и выполнять print('print me please!')?


person simon    schedule 08.01.2016    source источник
comment
Единственное решение, которое я нашел до сих пор, — это иметь ссылку на объект сокета внутри объекта цикла, чтобы из сопрограммы Protocol.onOpen() я мог сделать что-то вроде self.factory.loop.socket.transport.onOpen(). Чтобы сделать вещи немного более красивыми, я могу передать объект цикла в качестве базовой линии, которая будет иметь структуру, подобную loop.io.socket.transport. Но опять же, был ли объект цикла действительно предназначен для хранения сторонних ссылок внутри себя? Для меня это выглядит слишком хакерским...   -  person simon    schedule 08.01.2016
comment
В cpp я использовал signal2 для аналогичной цели, и по какой-то причине я пытался искать сигналы термина для python на этот раз... и, конечно, не нашел ничего подходящего... Теперь я смотрю на PyDispatcher и другие. Если никто не опубликует до того, как я найду достаточно хорошее решение, я сам отвечу на вопрос. Я думаю, что сейчас я на правильном пути.   -  person simon    schedule 08.01.2016


Ответы (1)


Хорошо, значит, я исправил это! Это легко сделать с помощью модуля PyDispatch.

Вот мое решение:

import asyncio
from pydispatch import dispatcher
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory

from ..transport import Transport

class Websocket(Transport):

    name = 'websocket'

    def __init__(self, socket):
        self.socket = socket

    def open(self):
        url = self.prepareUrl()

        factory = WebSocketClientFactory(url, debug = False)
        factory.protocol = Protocol

        websocket = self.socket.loop.create_connection(factory, host=self.socket.io.options.host, port=self.socket.options.port)

        dispatcher.connect(self.onOpen, signal='open', sender=dispatcher.Anonymous)

        self.socket.io.loop.run_until_complete(websocket)

    def onOpen(self):
        print('print me please!')


class Protocol(WebSocketClientProtocol):

    @asyncio.coroutine
    def onOpen(self):
        dispatcher.send(signal='open')

ОБНОВЛЕНИЕ

У меня есть другое, ИМО лучшее решение для этого. Этот не использует PyDispatch. Поскольку при завершении задачи asyncio выполняется обратный вызов, который возвращает определенный пользователем объект протокола (который наследуется от WebSocketClientProtocol), мы можем использовать его, чтобы связать два объекта вместе:

import asyncio
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory

from ..transport import Transport

class Protocol(WebSocketClientProtocol):

    def __init__(self):
        self.ws = None
        super().__init__()

    @asyncio.coroutine
    def onConnect(self, response):
        pass # connect handeled when SocketIO 'connect' packet is received

    @asyncio.coroutine
    def onOpen(self):
        self.ws.onOpen()

    @asyncio.coroutine
    def onMessage(self, payload, isBinary):
        self.ws.onMessage(payload=payload, isBinary=isBinary)

    @asyncio.coroutine
    def onClose(self, wasClean, code, reason):
        if not wasClean:
            self.ws.onError(code=code, reason=reason)

        self.ws.onClose()           

class Websocket(Transport):

    name = 'websocket'

    def __init__(self, socket, **kwargs):
        super().__init__(socket)

        loop = kwargs.pop('loop', None)
        self.loop = loop or asyncio.get_event_loop()

        self.transport = None
        self.protocol = None

        self.ready = True

    def open(self):
        url = self.prepareUrl()
        if bool(self.socket.options.query):
            url = '{0}?{1}'.format(url, self.socket.options.query)

        factory = WebSocketClientFactory(url=url, headers=self.socket.options.headers)
        factory.protocol = Protocol

        coro = self.loop.create_connection(factory, host=self.socket.options.host, port=self.socket.options.port, ssl=self.socket.options.secure)

        task = self.loop.create_task(coro)
        task.add_done_callback(self.onWebSocketInit)

    def onWebSocketInit(self, future):
        try:
            self.transport, self.protocol = future.result()
            self.protocol.ws = self
        except Exception:
            self.onClose()

    def send(self, data):
        self.protocol.sendMessage(payload=data.encode('utf-8'), isBinary=False)
        return self

    def close(self):
        if self.isOpen:
            self.protocol.sendClose()
        return self

    def onOpen(self):
        super().onOpen()
        self.socket.setBuffer(False)

    def onMessage(self, payload, isBinary):
        if not isBinary:
            self.onData(payload.decode('utf-8'))
        else:
            self.onError('Message arrived in binary')

    def onClose(self):
        super().onClose()
        self.socket.setBuffer(True)

    def onError(self, code, reason):
        self.socket.onError(reason)
person simon    schedule 08.01.2016