Esempio n. 1
0
    def start(self):
        # create a Session repr between ourselves and the Router.
        # pass in out ``MessageHandler`` which will process messages
        # before they are passed back to the client.
        self._session = Session(
            client=self,
            router=self.router,
            transport=self.transport,
            message_handler=self.message_handler,
        )

        # establish the session
        message_obj = self.session.begin()

        # raise if Router aborts handshake or we cannot respond to a
        # Challenge.
        if message_obj.WAMP_CODE == Abort.WAMP_CODE:
            raise WampyError(message_obj.message)

        if message_obj.WAMP_CODE == Challenge.WAMP_CODE:
            if 'WAMPYSECRET' not in os.environ:
                raise WampyError("Wampy requires a client's secret to be "
                                 "in the environment as ``WAMPYSECRET``")

            raise WampyError("Failed to handle CHALLENGE")

        logger.debug('client %s has established a session with id "%s"',
                     self.name, self.session.id)
Esempio n. 2
0
        def wrapper(*args, **kwargs):
            message = Call(procedure=name, args=args, kwargs=kwargs)
            response = self.client.make_rpc(message)

            wamp_code = response.WAMP_CODE
            if wamp_code == Error.WAMP_CODE:
                _, _, request_id, _, endpoint, exc_args, exc_kwargs = (
                    response.message)

                if endpoint == NOT_AUTHORISED:
                    raise WampyError(
                        "NOT_AUTHORISED: {} - {}".format(
                            self.client.name, exc_args[0]
                        )
                    )

                raise WampyError(
                    'oops! wampy has failed, sorry: {}'.format(
                        response.message
                    )
                )

            if wamp_code != Result.WAMP_CODE:
                raise WampProtocolError(
                    'unexpected message code: "%s (%s) %s"',
                    wamp_code, MESSAGE_TYPE_MAP[wamp_code],
                    response[5]
                )

            result = response.value
            logger.debug("RpcProxy got result: %s", result)
            return result
Esempio n. 3
0
    def start(self):
        # establish the underlying connection. this will raise on error.
        connection = self.transport.connect()

        # create a Session repr between ourselves and the Router.
        # pass in the live connection over a transport that the Session
        # doesn't need to care about - it only cares how to receive
        # messages over this.
        # pass in out ``MessageHandler`` which will process messages
        # before they are passed back to the client.
        self._session = Session(
            client=self,
            router=self.router,
            connection=connection,
            message_handler=self.message_handler,
        )

        # establish the session
        message_obj = self.session.begin()

        # raise if Router aborts handshake or we cannot respond to a
        # Challenge.
        if message_obj.WAMP_CODE == Abort.WAMP_CODE:
            raise WelcomeAbortedError(message_obj.message)

        if message_obj.WAMP_CODE == Challenge.WAMP_CODE:
            if 'WAMPYSECRET' not in os.environ:
                raise WampyError("Wampy requires a client's secret to be "
                                 "in the environment as ``WAMPYSECRET``")

            raise WampyError("Failed to handle CHALLENGE")

        logger.debug('client %s has established a session with id "%s"',
                     self.name, self.session.id)
Esempio n. 4
0
def wait_for_subscriptions(container, number_of_subscriptions):
    if not container.started:
        raise WampyError(
            "Cannot look for registrations unless the service is running")

    for ext in container.extensions:
        if type(ext) == WampTopicProxy:
            break
    else:
        raise WampyError("no clients found subscribing to topics")

    session = ext.client.session

    success = False
    with eventlet.Timeout(TIMEOUT, False):
        while (len(session.subscription_map.keys()) < number_of_subscriptions):
            eventlet.sleep()
        success = True

    if not success:
        logger.error("%s has not subscribed to %s topics", ext.client.name,
                     number_of_subscriptions)
        raise WampyError("Subscriptions Not Found")

    logger.info("found subscriptions: %s", session.subscription_map.keys())
Esempio n. 5
0
def wait_for_registrations(container, number_of_registrations):
    if not container.started:
        raise WampyError(
            "Cannot look for registrations unless the service is running")

    for ext in container.extensions:
        if type(ext) == WampCalleeProxy:
            break
    else:
        raise WampyError("no clients found registering callees")

    session = ext.client.session

    success = False
    with eventlet.Timeout(TIMEOUT, False):
        while (len(session.registration_map.keys()) < number_of_registrations):
            eventlet.sleep()
        success = True

    if not success:
        logger.error("%s has not registered %s callees", ext.client.name,
                     number_of_registrations)
        raise WampyError("Registrations Not Found: {}".format(
            session.registration_map.keys()))

    logger.info("found registrations: %s", session.registration_map.keys())
Esempio n. 6
0
    def start(self):
        """ Start Crossbar.io in a subprocess.
        """
        if self.started is True:
            raise WampyError("Router already started")

        # will attempt to connect or start up the CrossBar
        crossbar_config_path = self.config_path
        cbdir = self.crossbar_directory

        # starts the process from the root of the test namespace
        cmd = [
            'crossbar', 'start',
            '--cbdir', cbdir,
            '--config', crossbar_config_path,
        ]

        self.proc = subprocess.Popen(cmd, preexec_fn=os.setsid)

        self._wait_until_ready()
        logger.info(
            "Crosbar.io is ready for connections on %s (IPV%s)",
            self.url, self.ipv
        )

        self.started = True
Esempio n. 7
0
 def callee_callback(self, procedure_name, *args, **kwargs):
     for provider in self.providers:
         if provider.method_name == procedure_name:
             provider.handle_message(*args, **kwargs)
             break
     else:
         raise WampyError('no providers matching procedure_name')
Esempio n. 8
0
    def _connect(self):
        if self.ipv == 4:
            _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

            try:
                _socket.connect((self.host.encode(), self.port))
            except socket_error as exc:
                if exc.errno == 61:
                    logger.error('unable to connect to %s:%s (IPV%s)',
                                 self.host, self.port, self.ipv)

                raise

        elif self.ipv == 6:
            _socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

            try:
                _socket.connect(("::", self.port))
            except socket_error as exc:
                if exc.errno == 61:
                    logger.error('unable to connect to %s:%s (IPV%s)',
                                 self.host, self.port, self.ipv)

                raise

        else:
            raise WampyError("unknown IPV: {}".format(self.ipv))

        self.socket = _socket
        logger.debug("socket connected")
Esempio n. 9
0
    def __init__(
        self,
        config_path="./crossbar/config.json",
        crossbar_directory=None,
    ):

        with open(config_path) as data_file:
            config_data = json.load(data_file)

        self.config = config_data
        self.config_path = config_path
        config = self.config['workers'][0]

        self.realm = config['realms'][0]
        self.roles = self.realm['roles']

        if len(config['transports']) > 1:
            raise WampyError(
                "Only a single websocket transport is supported by Wampy, "
                "sorry")

        self.transport = config['transports'][0]
        self.url = self.transport.get("url")
        if self.url is None:
            raise WampyError("The ``url`` value is required by Wampy. "
                             "Please add to your configuration file. Thanks.")

        self.ipv = self.transport['endpoint'].get("version", None)
        if self.ipv is None:
            logger.warning(
                "defaulting to IPV 4 because neither was specified.")
            self.ipv = 4

        self.parse_url()

        self.websocket_location = self.resource

        self.crossbar_directory = crossbar_directory

        try:
            self.certificate = self.transport['endpoint']['tls']['certificate']
        except KeyError:
            self.certificate = None

        self.proc = None
        self.started = False
Esempio n. 10
0
    def __call__(self, *unsupported_args, **kwargs):
        if len(unsupported_args) != 0:
            raise WampyError(
                "wampy only supports publishing keyword arguments "
                "to a Topic.")

        topic = kwargs.pop("topic")
        if not kwargs:
            raise WampyError(
                "wampy requires at least one message to publish to a topic")

        if "options" not in kwargs:
            kwargs["options"] = {}
        message = Publish(topic=topic, **kwargs)
        logger.info('publishing message: "%s"', message)

        self.client.send_message(message)
Esempio n. 11
0
    def __init__(self, server_url, certificate_path, ipv=4):
        super(SecureWebSocket, self).__init__(server_url=server_url, ipv=ipv)

        # PROTOCOL_TLSv1_1 and PROTOCOL_TLSv1_2 are only available if Python is
        # linked with OpenSSL 1.0.1 or later.
        try:
            self.ssl_version = ssl.PROTOCOL_TLSv1_2
        except AttributeError:
            raise WampyError("Your Python Environment does not support TLS")

        self.certificate = certificate_path
Esempio n. 12
0
    def __init__(
        self,
        url="ws://localhost:8080",
        config_path="./crossbar/config.json",
        crossbar_directory=None,
    ):
        """ A wrapper around a Crossbar Server. Wampy uses this when
        executing its test suite.

        Typically used in test cases, local dev and scripts rather than
        with production applications. For Production, just deploy and
        connect to as you would any other server.

        """
        with open(config_path) as data_file:
            config_data = json.load(data_file)

        self.config = config_data
        self.config_path = config_path
        config = self.config['workers'][0]

        self.realm = config['realms'][0]
        self.roles = self.realm['roles']

        if len(config['transports']) > 1:
            raise WampyError(
                "Only a single websocket transport is supported by Wampy, "
                "sorry"
            )

        self.transport = config['transports'][0]
        self.url = url

        self.ipv = self.transport['endpoint'].get("version", None)
        if self.ipv is None:
            logger.warning(
                "defaulting to IPV 4 because neither was specified."
            )
            self.ipv = 4

        self.parse_url()

        self.websocket_location = self.resource

        self.crossbar_directory = crossbar_directory

        try:
            self.certificate = self.transport['endpoint']['tls']['certificate']
        except KeyError:
            self.certificate = None

        self.proc = None
        self.started = False
Esempio n. 13
0
    def _say_hello(self):
        details = self.roles
        for role, features in details['roles'].items():
            features.setdefault('features', {})
            features['features'].setdefault('call_timeout', True)

        message = Hello(realm=self.realm, details=details)
        self.send_message(message)
        message_obj = self.recv_message()

        # raise if Router aborts handshake or we cannot respond to a
        # Challenge.
        if message_obj.WAMP_CODE == Abort.WAMP_CODE:
            raise WampyError(message_obj.message)

        if message_obj.WAMP_CODE == Challenge.WAMP_CODE:
            if 'WAMPYSECRET' not in os.environ:
                raise WampyError("Wampy requires a client's secret to be "
                                 "in the environment as ``WAMPYSECRET``")

            raise WampyError("Failed to handle CHALLENGE")
Esempio n. 14
0
    def register_router(self, router):
        super(SecureWebSocket, self).register_router(router)

        self.ipv = router.ipv

        # PROTOCOL_TLSv1_1 and PROTOCOL_TLSv1_2 are only available if Python is
        # linked with OpenSSL 1.0.1 or later.
        try:
            self.ssl_version = ssl.PROTOCOL_TLSv1_2
        except AttributeError:
            raise WampyError("Your Python Environment does not support TLS")

        self.certificate = router.certificate
Esempio n. 15
0
    def _upgrade(self):
        handshake_headers = self._get_handshake_headers()
        handshake = '\r\n'.join(handshake_headers) + "\r\n\r\n"

        self.socket.send(handshake.encode())

        try:
            with eventlet.Timeout(5):
                self.status, self.headers = self._read_handshake_response()
        except eventlet.Timeout:
            raise WampyError(
                'No response after handshake "{}"'.format(handshake))

        logger.debug("connection upgraded")
Esempio n. 16
0
def get_async_adapter():
    if async_name == GEVENT:
        from . gevent_ import Gevent
        _adapter = Gevent()
        return _adapter

    if async_name == EVENTLET:
        from . eventlet_ import Eventlet
        _adapter = Eventlet()
        return _adapter

    raise WampyError(
        'only gevent and eventlet are supported, sorry. help out??'
    )
Esempio n. 17
0
        def wrapper(*args, **kwargs):
            # timeout is currently handled by wampy whilst
            # https://github.com/crossbario/crossbar/issues/299
            # is addressed, but we pass in the value regardless, waiting
            # for the feature on CrossBar.
            # WAMP Call Message requires milliseconds...
            options = {
                'timeout': int(self.client.call_timeout * 1000),
            }
            message = Call(
                procedure=name,
                options=options,
                args=args,
                kwargs=kwargs,
            )
            response = self.client._make_rpc(message)

            wamp_code = response.WAMP_CODE
            if wamp_code == Error.WAMP_CODE:
                _, _, request_id, _, endpoint, exc_args, exc_kwargs = (
                    response.message)

                if endpoint == NOT_AUTHORISED:
                    raise WampyError("NOT_AUTHORISED: {} - {}".format(
                        self.client.name, exc_args[0]))

                raise WampyError('oops! wampy has failed, sorry: {}'.format(
                    response.message))

            if wamp_code != Result.WAMP_CODE:
                raise WampProtocolError(
                    'unexpected message code: "%s (%s) %s"', wamp_code,
                    MESSAGE_TYPE_MAP[wamp_code], response[5])

            result = response.value
            logger.debug("RpcProxy got result: %s", result)
            return result
Esempio n. 18
0
    def __init__(self,
                 request_type,
                 request_id,
                 details=None,
                 error="",
                 args_list=None,
                 kwargs_dict=None):
        """ Error reply sent by a Peer as an error response to
        different kinds of requests.

        :Parameters:

            :request_type:
                The WAMP message type code for the original request.
            :type request_type: int

            :request_id:
                The WAMP request ID of the original request
                (`Call`, `Subscribe`, ...) this error occurred for.
            :type request: int

            :args_list:
                Args to pass into an Application defined Exception

            :kwargs_list:
                Kwargs to pass into an Application defined Exception

            [ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict,
                Error|uri, Arguments|list, ArgumentsKw|dict]

        """
        super(Error, self).__init__()

        self.request_type = request_type
        self.request_id = request_id
        self.error = error
        self.args_list = args_list or []
        self.kwargs_dict = kwargs_dict or {}

        # wampy is not implementing ``details`` which appears to be an
        # alternative to args and kwargs
        if details:
            raise WampyError("Not Implemented: must use ``args_list`` and '"
                             "``kwargs_dict, not ``details``")
        self.details = {}
Esempio n. 19
0
    def __init__(self, bytes):
        super(ServerFrame, self).__init__(bytes)

        if not bytes:
            return

        try:
            self.payload_length_indicator = bytes[1] & 0b1111111
        except Exception:
            raise IncompleteFrameError(required_bytes=1)

        # if this doesn't raise, all the above will receive a value
        self.ensure_complete_frame(bytes)

        # server must not mask the payload
        mask = bytes[1] >> 7
        assert mask == 0

        self.buffered_bytes = bytes

        self.len = 0
        # Parse the first two bytes of header.
        self.fin = bytes[0] >> 7

        if self.fin == 0:
            logger.exception("Multiple Frames Returned: %s", bytes)
            raise WampyError(
                'Multiple framed responses not yet supported: {}'.format(
                    bytes))

        self.opcode = bytes[0] & 0b1111

        if self.opcode != 9:
            # Wamp data frames contain a json-encoded payload.
            # The other kind of frame we handle (opcode 0x9) is a ping and it
            # has a non-json payload
            try:
                # decode required before loading JSON for python 2 only
                self.payload = json.loads(self.body.decode('utf-8'))
            except Exception:
                raise WebsocktProtocolError(
                    'Failed to load JSON object from: "%s"', self.body)
        else:
            self.payload = self.body
Esempio n. 20
0
    def try_connection(self):
        if self.ipv == 4:
            _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

            try:
                _socket.connect((self.host, self.port))
            except socket_error:
                raise ConnectionError("Could not connect")

        elif self.ipv == 6:
            _socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

            try:
                _socket.connect(("::", self.port))
            except socket_error:
                raise ConnectionError("Could not connect")

        else:
            raise WampyError("unknown IPV: {}".format(self.ipv))

        _socket.shutdown(socket.SHUT_RDWR)
        _socket.close()
Esempio n. 21
0
def get_async_adapter():

    class Gevent(Async):

        def __init__(self):
            self.message_queue = gevent.queue.Queue()

        def Timeout(self, timeout):
            return gevent.Timeout(timeout)

        def receive_message(self, timeout):
            try:
                message = self.message_queue.get(timeout=timeout)
            except gevent.queue.Empty:
                raise WampProtocolError(
                    "no message returned (timed-out in {})".format(timeout)
                )
            return message

        def spawn(self, *args, **kwargs):
            gthread = gevent.spawn(*args, **kwargs)
            return gthread

        def sleep(self, time):
            return gevent.sleep(time)

    class Eventlet(Async):

        def __init__(self):
            self.message_queue = eventlet.Queue()

        def Timeout(self, timeout):
            return eventlet.Timeout(timeout)

        def receive_message(self, timeout):
            try:
                message = self._wait_for_message(timeout)
            except eventlet.Timeout:
                raise WampProtocolError(
                    "no message returned (timed-out in {})".format(timeout)
                )
            return message

        def spawn(self, *args, **kwargs):
            gthread = eventlet.spawn(*args, **kwargs)
            return gthread

        def sleep(self, time):
            return eventlet.sleep(time)

        def _wait_for_message(self, timeout):
            q = self.message_queue

            with eventlet.Timeout(timeout):
                while q.qsize() == 0:
                    eventlet.sleep()

            message = q.get()
            return message

    from wampy.config.defaults import async_name
    if async_name == GEVENT:
        _adapter = Gevent()
        return _adapter

    if async_name == EVENTLET:
        _adapter = Eventlet()
        return _adapter

    raise WampyError(
        'only gevent and eventlet are supported, sorry. help out??'
    )
Esempio n. 22
0
    def __init__(self, **kwargs):
        if "topic" not in kwargs:
            raise WampyError("subscriber missing ``topic`` keyword argument")

        self.topic = kwargs['topic']
Esempio n. 23
0
    def __init__(
        self,
        router_url,
        message_handler,
        ipv,
        cert_path,
        call_timeout,
        realm,
        roles,
    ):
        """ A Session between a Client and a Router.

        The WAMP layer of the internal architecture.

        :Parameters:
            router_url : string
                The URL of the Router Peer.
            message_handler : instance
                An instance of ``wampy.message_handler.MessageHandler``,
                or a subclass of it. Handles incoming WAMP Messages.
            ipv : int
                The Internet Protocol version for the Transport to use

        """
        self.url = router_url
        # decomposes the url, adding new Session instance variables for
        # them, so that the Session can decide on the Transport it needs
        # to use to connect to the Router
        self.parse_url()

        self.message_handler = message_handler
        self.ipv = ipv
        self.cert_path = cert_path
        self.call_timeout = call_timeout
        self.realm = realm
        self.roles = roles

        if self.scheme == "ws":
            self.transport = WebSocket(
                server_url=self.url,
                ipv=self.ipv,
            )
        elif self.scheme == "wss":
            self.transport = SecureWebSocket(
                server_url=self.url,
                ipv=self.ipv,
                certificate_path=self.cert_path,
            )
        else:
            raise WampyError(
                'wampy only suppoers network protocol "ws" or "wss"')

        self.connection = self.transport.connect(upgrade=True)

        self.request_ids = {}
        self.subscription_map = {}
        self.registration_map = {}

        self.session_id = None
        # spawn a green thread to listen for incoming messages over
        # a connection and put them on a queue to be processed
        self._managed_thread = None
        # the MessageHandler is responsible for putting messages on
        # to this queue which are then returned to the Client. The
        # queue is shared between the green threads.
        self._message_queue = async_adapter.message_queue
        self._listen(self.connection)
Esempio n. 24
0
    def __init__(
        self,
        url=None,
        cert_path=None,
        realm=DEFAULT_REALM,
        roles=DEFAULT_ROLES,
        message_handler=None,
        name=None,
        router=None,
    ):
        """ A WAMP Client "Peer".

        WAMP is designed for application code to run within Clients,
        i.e. Peers.

        Peers have the roles Callee, Caller, Publisher, and Subscriber.

        Subclass this base class to implemente the Roles for your application.

        :Parameters:
            url : string
                The URL of the Router Peer.
                This must include protocol, host and port and an optional path,
                e.g. "ws://example.com:8080" or "wss://example.com:8080/ws".
                Note though that "ws" protocol defaults to port 8080, an "wss"
                to 443.
            cert_path : str
                If using ``wss`` protocol, a certificate might be required by
                the Router. If so, provide here.
            realm : str
                The routing namespace to construct the ``Session`` over.
                Defaults to ``realm1``.
            roles : dictionary
                Description of the Roles implemented by the ``Client``.
                Defaults to ``wampy.constants.DEFAULT_ROLES``.
            message_handler : instance
                An instance of ``wampy.message_handler.MessageHandler``, or
                a subclass of. This controls the conversation between the
                two Peers.
            name : string
                Optional name for your ``Client``. Useful for when testing
                your app or for logging.
            router : instance
                An alternative way to connect to a Router rather than ``url``.
                An instance of a Router Peer, e.g.
                ``wampy.peers.routers.Crossbar``
                This is more configurable and powerful, but requires a copy
                of the Router's config file, making this only really useful
                in single host setups or testing.

        """
        if url and router:
            raise WampyError(
                'Both ``url`` and ``router`` decide how your client connects '
                'to the Router, and so only one can be defined on '
                'instantiation. Please choose one or the other.')

        # the endpoint of a WAMP Router
        self.url = url or CROSSBAR_DEFAULT

        # the ``realm`` is the administrive domain to route messages over.
        self.realm = realm
        # the ``roles`` define what Roles (features) the Client can act,
        # but also configure behaviour such as auth
        self.roles = roles
        # a Session is a transient conversation between two Peers - a Client
        # and a Router. Here we model the Peer we are going to connect to.
        self.router = router or Router(url=self.url, cert_path=cert_path)
        # wampy uses a decoupled "messge handler" to process incoming messages.
        # wampy also provides a very adequate default.
        self.message_handler = message_handler or MessageHandler()

        # this conversation is over a transport. WAMP messages are transmitted
        # as WebSocket messages by default (well, actually... that's because no
        # other transports are supported!)
        if self.router.scheme == "ws":
            self.transport = WebSocket()
        elif self.router.scheme == "wss":
            self.transport = SecureWebSocket()
        else:
            raise WampyError('Network protocl must be "ws" or "wss"')

        # the transport is responsible for the connection.
        self.transport.register_router(self.router)

        # generally ``name`` is used for debuggubg and logging only
        self.name = name or self.__class__.__name__

        self._session = None
Esempio n. 25
0
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import logging
import os

from wampy.constants import GEVENT, EVENT_LOOP_BACKENDS
from wampy.errors import WampyError

logger = logging.getLogger(__name__)

async_name = os.environ.get('WAMPY_ASYNC_NAME', GEVENT)
logger.info('asycn name is "%s"', async_name)
if async_name not in EVENT_LOOP_BACKENDS:
    logger.error('unsupported event loop for wampy! "%s"', async_name)
    raise WampyError(
        'export your WAMPY_ASYNC_NAME os environ value to be one of "{}" '
        'or just remove and use the default gevent'.format(
            EVENT_LOOP_BACKENDS), )