def test_call_exception_bare(self):
            handler = ApplicationSession()
            MockTransport(handler)
            exception = Exception()

            def raiser():
                raise exception

            registration0 = yield handler.register(raiser, "com.myapp.myproc_error")
            try:
                yield handler.call('com.myapp.myproc_error')
                self.fail()
            except Exception as e:
                self.assertIsInstance(e, ApplicationError)
            finally:
                yield registration0.unregister()
Ejemplo n.º 2
0
        def test_subscribe(self):
            handler = ApplicationSession()
            MockTransport(handler)

            def on_event(*args, **kwargs):
                print("got event", args, kwargs)

            subscription = yield handler.subscribe(on_event,
                                                   u'com.myapp.topic1')
            self.assertTrue(type(subscription.id) in (int, long))

            subscription = yield handler.subscribe(
                on_event,
                u'com.myapp.topic1',
                options=types.SubscribeOptions(match=u'wildcard'))
            self.assertTrue(type(subscription.id) in (int, long))
Ejemplo n.º 3
0
        def test_register(self):
            handler = ApplicationSession()
            MockTransport(handler)

            def on_call(*args, **kwargs):
                print("got call", args, kwargs)

            registration = yield handler.register(on_call,
                                                  u'com.myapp.procedure1')
            self.assertTrue(type(registration.id) in (int, long))

            registration = yield handler.register(
                on_call,
                u'com.myapp.procedure1',
                options=types.RegisterOptions(match=u'prefix'))
            self.assertTrue(type(registration.id) in (int, long))
        def test_call_exception_runtimeerror(self):
            handler = ApplicationSession()
            MockTransport(handler)
            exception = RuntimeError("a simple error")

            def raiser():
                raise exception

            registration0 = yield handler.register(raiser, "com.myapp.myproc_error")
            try:
                yield handler.call('com.myapp.myproc_error')
                self.fail()
            except Exception as e:
                self.assertIsInstance(e, ApplicationError)
                self.assertEqual(e.error_message(), "wamp.error.runtime_error: a simple error")
            finally:
                yield registration0.unregister()
Ejemplo n.º 5
0
        def test_double_subscribe_errors(self):
            """
            Test various error-conditions when we try to add a second
            subscription-handler (its signature must match any
            existing handlers).
            """
            handler = ApplicationSession()
            MockTransport(handler)

            event0 = Deferred()
            event1 = Deferred()

            def second(*args, **kw):
                # our EventDetails should have been passed as the
                # "boom" kwarg; see "details_arg=" below
                self.assertTrue('boom' in kw)
                self.assertTrue(isinstance(kw['boom'], types.EventDetails))
                event1.callback(args)

            subscription0 = yield handler.subscribe(
                lambda arg: event0.callback(arg), u'com.myapp.topic1')
            subscription1 = yield handler.subscribe(
                second, u'com.myapp.topic1',
                types.SubscribeOptions(details_arg='boom'),
            )
            # same topic, same ID
            self.assertTrue(subscription0.id == subscription1.id)

            # MockTransport gives us the ack reply and then we do our
            # own event message.
            publish = yield handler.publish(
                u'com.myapp.topic1',
                options=types.PublishOptions(acknowledge=True, exclude_me=False),
            )
            # note that the protocol serializer converts all sequences
            # to lists, so we pass "args" as a list, not a tuple on
            # purpose.
            handler.onMessage(
                message.Event(subscription0.id, publish.id, args=['arg0']))

            # each callback should have gotten called, each with its
            # own args (we check the correct kwarg in second() above)
            self.assertTrue(event0.called)
            self.assertTrue(event1.called)
            self.assertEqual(event0.result, 'arg0')
            self.assertEqual(event1.result, ('arg0',))
Ejemplo n.º 6
0
        def test_publish(self):
            handler = ApplicationSession()
            MockTransport(handler)

            publication = yield handler.publish(u'com.myapp.topic1')
            self.assertEqual(publication, None)

            publication = yield handler.publish(u'com.myapp.topic1', 1, 2, 3)
            self.assertEqual(publication, None)

            publication = yield handler.publish(u'com.myapp.topic1',
                                                1,
                                                2,
                                                3,
                                                foo=23,
                                                bar='hello')
            self.assertEqual(publication, None)

            publication = yield handler.publish(
                u'com.myapp.topic1',
                options=types.PublishOptions(exclude_me=False))
            self.assertEqual(publication, None)

            publication = yield handler.publish(u'com.myapp.topic1',
                                                1,
                                                2,
                                                3,
                                                foo=23,
                                                bar='hello',
                                                options=types.PublishOptions(
                                                    exclude_me=False,
                                                    exclude=[100, 200, 300]))
            self.assertEqual(publication, None)

            publication = yield handler.publish(u'com.myapp.topic1',
                                                1,
                                                2,
                                                3,
                                                foo=23,
                                                bar='hello',
                                                options=types.PublishOptions(
                                                    exclude_me=False,
                                                    exclude=[100, 200, 300],
                                                    retain=True))
            self.assertEqual(publication, None)
Ejemplo n.º 7
0
        def test_publish_acknowledged(self):
            handler = ApplicationSession()
            MockTransport(handler)

            publication = yield handler.publish(u'com.myapp.topic1', options=types.PublishOptions(acknowledge=True))
            self.assertTrue(type(publication.id) in (int, long))

            publication = yield handler.publish(u'com.myapp.topic1', 1, 2, 3, options=types.PublishOptions(acknowledge=True))
            self.assertTrue(type(publication.id) in (int, long))

            publication = yield handler.publish(u'com.myapp.topic1', 1, 2, 3, foo=23, bar='hello', options=types.PublishOptions(acknowledge=True))
            self.assertTrue(type(publication.id) in (int, long))

            publication = yield handler.publish(u'com.myapp.topic1', options=types.PublishOptions(exclude_me=False, acknowledge=True))
            self.assertTrue(type(publication.id) in (int, long))

            publication = yield handler.publish(u'com.myapp.topic1', 1, 2, 3, foo=23, bar='hello', options=types.PublishOptions(exclude_me=False, exclude=[100, 200, 300], acknowledge=True))
            self.assertTrue(type(publication.id) in (int, long))
Ejemplo n.º 8
0
        def test_call(self):
            handler = ApplicationSession()
            MockTransport(handler)

            res = yield handler.call(u'com.myapp.procedure1')
            self.assertEqual(res, 100)

            res = yield handler.call(u'com.myapp.procedure1', 1, 2, 3)
            self.assertEqual(res, 100)

            res = yield handler.call(u'com.myapp.procedure1', 1, 2, 3, foo=23, bar='hello')
            self.assertEqual(res, 100)

            res = yield handler.call(u'com.myapp.procedure1', options=types.CallOptions(timeout=10000))
            self.assertEqual(res, 100)

            res = yield handler.call(u'com.myapp.procedure1', 1, 2, 3, foo=23, bar='hello', options=types.CallOptions(timeout=10000))
            self.assertEqual(res, 100)
Ejemplo n.º 9
0
        def __init__(self, handler):
            self._log = False
            self._handler = handler
            self._serializer = serializer.JsonSerializer()
            self._registrations = {}
            self._invocations = {}
            #: str -> ID
            self._subscription_topics = {}

            self._handler.onOpen(self)

            self._my_session_id = util.id()

            roles = {u'broker': role.RoleBrokerFeatures(), u'dealer': role.RoleDealerFeatures()}

            msg = message.Welcome(self._my_session_id, roles)
            self._handler.onMessage(msg)
            self._fake_router_session = ApplicationSession()
Ejemplo n.º 10
0
        def test_invoke_progressive_result_error(self):
            handler = ApplicationSession()
            MockTransport(handler)

            @inlineCallbacks
            def bing(arg, details=None, key=None):
                self.assertTrue(details is not None)
                self.assertTrue(details.progress is not None)
                self.assertEqual(key, 'word')
                self.assertEqual('arg', arg)
                details.progress('life', something='nothing')
                yield succeed('meaning of')
                returnValue(42)

            got_progress = Deferred()
            progress_error = NameError('foo')

            def progress(arg, something=None):
                self.assertEqual('nothing', something)
                got_progress.callback(arg)
                raise progress_error

            # see MockTransport, must start with "com.myapp.myproc"
            yield handler.register(
                bing,
                u'com.myapp.myproc2',
                types.RegisterOptions(details_arg='details'),
            )

            res = yield handler.call(
                u'com.myapp.myproc2',
                'arg',
                options=types.CallOptions(on_progress=progress),
                key='word',
            )
            self.assertEqual(42, res)
            # our progress handler raised an error, but not before
            # recording success.
            self.assertTrue(got_progress.called)
            self.assertEqual('life', got_progress.result)
            # make sure our progress-handler error was logged
            errs = self.flushLoggedErrors()
            self.assertEqual(1, len(errs))
            self.assertEqual(progress_error, errs[0].value)
Ejemplo n.º 11
0
    def create(transport, path, config):
        personality = transport.worker.personality
        personality.WEB_SERVICE_CHECKERS['publisher'](personality, config)

        # create a vanilla session: the publisher will use this to inject events
        #
        publisher_session_config = ComponentConfig(realm=config['realm'], extra=None)
        publisher_session = ApplicationSession(publisher_session_config)

        # add the publisher session to the router
        #
        transport._worker._router_session_factory.add(publisher_session,
                                                      authrole=config.get('role', 'anonymous'))

        # now create the publisher Twisted Web resource
        #
        resource = PublisherResource(config.get('options', {}), publisher_session)

        return RouterWebServiceRestPublisher(transport, path, config, resource)
Ejemplo n.º 12
0
        def test_publish_callback_exception(self):
            """
            Ensure we handle an exception from the user code.
            """
            handler = ApplicationSession()
            MockTransport(handler)

            error_instance = RuntimeError("we have a problem")
            got_err_d = Deferred()

            def observer(kw):
                if kw['isError'] and 'failure' in kw:
                    fail = kw['failure']
                    fail.trap(RuntimeError)
                    if error_instance == fail.value:
                        got_err_d.callback(True)

            log.addObserver(observer)

            def boom():
                raise error_instance

            try:
                sub = yield handler.subscribe(boom, u'com.myapp.topic1')

                # MockTransport gives us the ack reply and then we do our
                # own event message
                publish = yield handler.publish(
                    u'com.myapp.topic1',
                    options=types.PublishOptions(acknowledge=True,
                                                 exclude_me=False),
                )
                msg = message.Event(sub.id, publish.id)
                handler.onMessage(msg)

                # we know it worked if our observer worked and did
                # .callback on our Deferred above.
                self.assertTrue(got_err_d.called)
                # ...otherwise trial will fail the test anyway
                self.flushLoggedErrors()

            finally:
                log.removeObserver(observer)
Ejemplo n.º 13
0
    def test_failure(self):
        """
        A failed call returns the error to the client.
        """
        session = TestSession(types.ComponentConfig(u'realm1'))
        self.session_factory.add(session, authrole=u"test_role")

        session2 = ApplicationSession(types.ComponentConfig(u'realm1'))
        self.session_factory.add(session2, authrole=u"test_role")
        resource = CallerResource({}, session2)

        tests = [
            (u"com.myapp.sqrt", (0,),
             {u"error": u"wamp.error.runtime_error", u"args": [u"don't ask foolish questions ;)"], u"kwargs": {}}),
            (u"com.myapp.checkname", ("foo",),
             {u"error": u"com.myapp.error.reserved", u"args": [], u"kwargs": {}}),
            (u"com.myapp.checkname", ("*",),
             {u"error": u"com.myapp.error.invalid_length", u"args": [], u"kwargs": {"min": 3, "max": 10}}),
            (u"com.myapp.checkname", ("hello",),
             {u"error": u"com.myapp.error.mixed_case", u"args": ["hello", "HELLO"], u"kwargs": {}}),
            (u"com.myapp.compare", (1, 10),
             {u"error": u"com.myapp.error1", u"args": [9], u"kwargs": {}}),
        ]

        for procedure, args, err in tests:
            with LogCapturer() as l:
                request = yield renderResource(
                    resource, b"/",
                    method=b"POST",
                    headers={b"Content-Type": [b"application/json"]},
                    body=dump_json({"procedure": procedure, "args": args}).encode('utf8'))

            self.assertEqual(request.code, 200)
            self.assertEqual(json.loads(native_string(request.get_written_data())),
                             err)

            logs = l.get_category("AR458")
            self.assertEqual(len(logs), 1)
            self.assertEqual(logs[0]["code"], 200)

        # We manually logged the errors; we can flush them from the log
        self.flushLoggedErrors()
Ejemplo n.º 14
0
        def test_double_subscribe_single_unsubscribe(self):
            '''
            Make sure we correctly deal with unsubscribing one of our handlers
            from the same topic.
            '''
            handler = ApplicationSession()
            MockTransport(handler)

            # monkey-patch ApplicationSession to ensure we DO NOT get
            # an Unsubscribed message -- since we only unsubscribe
            # from ONE of our handlers for com.myapp.topic1
            def onMessage(msg):
                assert not isinstance(msg, message.Unsubscribed)
                return ApplicationSession.onMessage(handler, msg)

            handler.onMessage = onMessage

            event0 = Deferred()
            event1 = Deferred()

            subscription0 = yield handler.subscribe(
                lambda: event0.callback(42), u'com.myapp.topic1')
            subscription1 = yield handler.subscribe(
                lambda: event1.callback('foo'), u'com.myapp.topic1')

            self.assertTrue(subscription0.id == subscription1.id)
            yield subscription1.unsubscribe()

            # do a publish (MockTransport fakes the acknowledgement
            # message) and then do an actual publish event. Note the
            # IDs are the same, so there's only one Event.
            publish = yield handler.publish(
                u'com.myapp.topic1',
                options=types.PublishOptions(acknowledge=True,
                                             exclude_me=False),
            )
            handler.onMessage(message.Event(subscription0.id, publish.id))

            # since we unsubscribed the second event handler, we
            # should NOT have called its callback
            self.assertTrue(event0.called, "Missing callback")
            self.assertTrue(not event1.called, "Second callback fired.")
Ejemplo n.º 15
0
        def test_publish_outstanding_errors_async_errback(self):
            handler = ApplicationSession()
            MockTransport(handler)
            error_d = Deferred()

            # this publish will "hang" because 'noreply.' URI is
            # handled specially in MockTransport; so this request will
            # be outstanding
            publication_d = handler.publish(
                u'noreply.foo',
                options=types.PublishOptions(acknowledge=True),
            )
            # further, we add an errback that does some arbitrary async work
            got_errors = []

            def errback(fail):
                got_errors.append(fail)
                return error_d

            publication_d.addErrback(errback)
            # "leave" the session, which should trigger errbacks on
            # all outstanding requests.
            details = types.CloseDetails(reason=u'testing',
                                         message=u'how are you?')
            handler.onLeave(details)

            # since our errback is async, onLeave should not have
            # completed yet but we should have already failed the
            # publication
            self.assertEqual(1, len(got_errors))
            # ...now let the async errback continue by completing the
            # Deferred we returned in our errback (could be fail or
            # success, shoudln't matter)
            error_d.callback(None)

            # ensure we (now) get our errback
            try:
                yield publication_d
            except ApplicationError as e:
                self.assertEqual(u'testing', e.error)
                self.assertEqual(u'how are you?', e.message)
        def test_publish_outstanding_errors(self):
            handler = ApplicationSession()
            MockTransport(handler)

            # this publish will "hang" because 'noreply.' URI is
            # handled specially in MockTransport; so this request will
            # be outstanding
            publication = handler.publish(
                'noreply.foo',
                options=types.PublishOptions(acknowledge=True),
            )
            # "leave" the session, which should trigger errbacks on
            # all outstanding requests.
            details = types.CloseDetails(reason='testing', message='how are you?')
            yield handler.onLeave(details)

            # ensure we got our errback
            try:
                yield publication
            except ApplicationError as e:
                self.assertEqual('testing', e.error)
                self.assertEqual('how are you?', e.message)
Ejemplo n.º 17
0
    def create(transport, path, config):
        personality = transport.worker.personality
        personality.WEB_SERVICE_CHECKERS['caller'](personality, config)

        # create a vanilla session: the caller will use this to inject calls
        #
        caller_session_config = ComponentConfig(realm=config['realm'],
                                                extra=None)
        caller_session = ApplicationSession(caller_session_config)

        # add the calling session to the router
        #
        router = transport._worker._router_session_factory._routerFactory._routers[
            config['realm']]
        transport._worker._router_session_factory.add(caller_session,
                                                      router,
                                                      authrole=config.get(
                                                          'role', 'anonymous'))

        # now create the caller Twisted Web resource
        #
        resource = CallerResource(config.get('options', {}), caller_session)

        return RouterWebServiceRestCaller(transport, path, config, resource)
Ejemplo n.º 18
0
    def create(transport, path, config):
        personality = transport.worker.personality
        personality.WEB_SERVICE_CHECKERS['webhook'](personality, config)

        # create a vanilla session: the webhook will use this to inject events
        #
        webhook_session_config = ComponentConfig(realm=config['realm'],
                                                 extra=None)
        webhook_session = ApplicationSession(webhook_session_config)

        # add the webhook session to the router
        #
        router = transport._worker._router_session_factory._routerFactory._routers[
            config['realm']]
        transport._worker._router_session_factory.add(webhook_session,
                                                      router,
                                                      authrole=config.get(
                                                          'role', 'anonymous'))

        # now create the webhook Twisted Web resource
        #
        resource = WebhookResource(config.get('options', {}), webhook_session)

        return RouterWebServiceWebhook(transport, path, config, resource)
Ejemplo n.º 19
0
    def test_router_session_goodbye_custom_message(self):
        """
        Reason should be propagated properly from Goodbye message
        """

        from crossbar.router.session import RouterApplicationSession
        session = ApplicationSession()
        session.onLeave = mock.Mock()
        session._realm = 'realm'
        router = Router(factory=mock.Mock(),
                        realm=RouterRealm(
                            controller=None,
                            id='realm',
                            config=dict(name='realm'),
                        ))
        rap = RouterApplicationSession(session, router)

        rap.send(message.Goodbye('wamp.reason.logout', 'some custom message'))

        leaves = session.onLeave.mock_calls
        self.assertEqual(1, len(leaves))
        details = leaves[0][1][0]
        self.assertEqual('wamp.reason.logout', details.reason)
        self.assertEqual('some custom message', details.message)
Ejemplo n.º 20
0
 def component(config):
     session = ApplicationSession(config)
     for name, fn in callbacks.items():
         session.on(name, fn)
     return session
Ejemplo n.º 21
0
    def start_router_transport(self, id, config, details=None):
        """
      Start a transport on this router.

      :param id: The ID of the transport to start.
      :type id: str
      :param config: The transport configuration.
      :type config: dict
      """
        if self.debug:
            log.msg(
                "{}.start_router_transport".format(self.__class__.__name__),
                id, config)

        ## prohibit starting a transport twice
        ##
        if id in self.transports:
            emsg = "ERROR: could not start transport - a transport with ID '{}'' is already running (or starting)".format(
                id)
            log.msg(emsg)
            raise ApplicationError('crossbar.error.already_running', emsg)

        ## check configuration
        ##
        try:
            checkconfig.check_router_transport(config)
        except Exception as e:
            emsg = "ERROR: invalid router transport configuration ({})".format(
                e)
            log.msg(emsg)
            raise ApplicationError("crossbar.error.invalid_configuration",
                                   emsg)
        else:
            if self.debug:
                log.msg("Starting {}-transport on router.".format(
                    config['type']))

        ## standalone WAMP-RawSocket transport
        ##
        if config['type'] == 'rawsocket':

            transport_factory = CrossbarWampRawSocketServerFactory(
                self.session_factory, config)
            transport_factory.noisy = False

        ## standalone WAMP-WebSocket transport
        ##
        elif config['type'] == 'websocket':

            transport_factory = CrossbarWampWebSocketServerFactory(
                self.session_factory, self.config.extra.cbdir, config,
                self._templates)
            transport_factory.noisy = False

        ## Flash-policy file server pseudo transport
        ##
        elif config['type'] == 'flashpolicy':

            transport_factory = FlashPolicyFactory(
                config.get('allowed_domain', None),
                config.get('allowed_ports', None))

        ## WebSocket testee pseudo transport
        ##
        elif config['type'] == 'websocket.testee':

            transport_factory = WebSocketTesteeServerFactory(
                config, self._templates)

        ## Stream testee pseudo transport
        ##
        elif config['type'] == 'stream.testee':

            transport_factory = StreamTesteeServerFactory()

        ## Twisted Web based transport
        ##
        elif config['type'] == 'web':

            options = config.get('options', {})

            ## create Twisted Web root resource
            ##
            root_config = config['paths']['/']

            root_type = root_config['type']
            root_options = root_config.get('options', {})

            ## Static file hierarchy root resource
            ##
            if root_type == 'static':

                if 'directory' in root_config:

                    root_dir = os.path.abspath(
                        os.path.join(self.config.extra.cbdir,
                                     root_config['directory']))

                elif 'package' in root_config:

                    if not 'resource' in root_config:
                        raise ApplicationError(
                            "crossbar.error.invalid_configuration",
                            "missing resource")

                    try:
                        mod = importlib.import_module(root_config['package'])
                    except ImportError as e:
                        emsg = "ERROR: could not import resource '{}' from package '{}' - {}".format(
                            root_config['resource'], root_config['package'], e)
                        log.msg(emsg)
                        raise ApplicationError(
                            "crossbar.error.invalid_configuration", emsg)
                    else:
                        try:
                            root_dir = os.path.abspath(
                                pkg_resources.resource_filename(
                                    root_config['package'],
                                    root_config['resource']))
                        except Exception as e:
                            emsg = "ERROR: could not import resource '{}' from package '{}' - {}".format(
                                root_config['resource'],
                                root_config['package'], e)
                            log.msg(emsg)
                            raise ApplicationError(
                                "crossbar.error.invalid_configuration", emsg)
                        else:
                            mod_version = getattr(mod, '__version__', '?.?.?')
                            log.msg(
                                "Loaded static Web resource '{}' from package '{} {}' (filesystem path {})"
                                .format(root_config['resource'],
                                        root_config['package'], mod_version,
                                        root_dir))

                else:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "missing web spec")

                root_dir = root_dir.encode(
                    'ascii',
                    'ignore')  # http://stackoverflow.com/a/20433918/884770
                if self.debug:
                    log.msg("Starting Web service at root directory {}".format(
                        root_dir))

                ## create resource for file system hierarchy
                ##
                if root_options.get('enable_directory_listing', False):
                    root = File(root_dir)
                else:
                    root = FileNoListing(root_dir)

                ## set extra MIME types
                ##
                root.contentTypes.update(EXTRA_MIME_TYPES)
                if 'mime_types' in root_options:
                    root.contentTypes.update(root_options['mime_types'])
                patchFileContentTypes(root)

                ## render 404 page on any concrete path not found
                ##
                root.childNotFound = Resource404(self._templates, root_dir)

            ## WSGI root resource
            ##
            elif root_type == 'wsgi':

                if not _HAS_WSGI:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "WSGI unsupported")

                wsgi_options = root_config.get('options', {})

                if not 'module' in root_config:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "missing module")

                if not 'object' in root_config:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "missing object")

                try:
                    mod = importlib.import_module(root_config['module'])
                except ImportError:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "module import failed")
                else:
                    if not root_config['object'] in mod.__dict__:
                        raise ApplicationError(
                            "crossbar.error.invalid_configuration",
                            "object not in module")
                    else:
                        app = getattr(mod, root_config['object'])

                ## create a Twisted Web WSGI resource from the user's WSGI application object
                try:
                    wsgi_resource = WSGIResource(reactor,
                                                 reactor.getThreadPool(), app)
                except Exception as e:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "could not instantiate WSGI resource: {}".format(e))
                else:
                    ## create a root resource serving everything via WSGI
                    root = WSGIRootResource(wsgi_resource, {})

            ## Redirecting root resource
            ##
            elif root_type == 'redirect':

                redirect_url = root_config['url'].encode('ascii', 'ignore')
                root = RedirectResource(redirect_url)

            ## Pusher resource
            ##
            elif root_type == 'pusher':

                ## create a vanilla session: the pusher will use this to inject events
                ##
                pusher_session_config = ComponentConfig(
                    realm=root_config['realm'], extra=None)
                pusher_session = ApplicationSession(pusher_session_config)

                ## add the pushing session to the router
                ##
                self.session_factory.add(pusher_session,
                                         authrole=root_config.get(
                                             'role', 'anonymous'))

                ## now create the pusher Twisted Web resource and add it to resource tree
                ##
                root = PusherResource(root_config.get('options', {}),
                                      pusher_session)

            ## Invalid root resource
            ##
            else:
                raise ApplicationError(
                    "crossbar.error.invalid_configuration",
                    "invalid Web root path type '{}'".format(root_type))

            ## create Twisted Web resources on all non-root paths configured
            ##
            self.add_paths(root, config.get('paths', {}))

            ## create the actual transport factory
            ##
            transport_factory = Site(root)
            transport_factory.noisy = False

            ## Web access logging
            ##
            if not options.get('access_log', False):
                transport_factory.log = lambda _: None

            ## Traceback rendering
            ##
            transport_factory.displayTracebacks = options.get(
                'display_tracebacks', False)

            ## HSTS
            ##
            if options.get('hsts', False):
                if 'tls' in config['endpoint']:
                    hsts_max_age = int(options.get('hsts_max_age', 31536000))
                    transport_factory.requestFactory = createHSTSRequestFactory(
                        transport_factory.requestFactory, hsts_max_age)
                else:
                    log.msg(
                        "Warning: HSTS requested, but running on non-TLS - skipping HSTS"
                    )

            ## enable Hixie-76 on Twisted Web
            ##
            if options.get('hixie76_aware', False):
                transport_factory.protocol = HTTPChannelHixie76Aware  # needed if Hixie76 is to be supported

        ## Unknown transport type
        ##
        else:
            ## should not arrive here, since we did check_transport() in the beginning
            raise Exception("logic error")

        ## create transport endpoint / listening port from transport factory
        ##
        d = create_listening_port_from_config(config['endpoint'],
                                              transport_factory,
                                              self.config.extra.cbdir, reactor)

        def ok(port):
            self.transports[id] = RouterTransport(id, config,
                                                  transport_factory, port)
            if self.debug:
                log.msg(
                    "Router transport '{}'' started and listening".format(id))
            return

        def fail(err):
            emsg = "ERROR: cannot listen on transport endpoint ({})".format(
                err.value)
            log.msg(emsg)
            raise ApplicationError("crossbar.error.cannot_listen", emsg)

        d.addCallbacks(ok, fail)
        return d
Ejemplo n.º 22
0
    def create_resource(self, path_config):
        """
      Creates child resource to be added to the parent.

      :param path_config: Configuration for the new child resource.
      :type path_config: dict

      :returns: Resource -- the new child resource
      """
        ## WAMP-WebSocket resource
        ##
        if path_config['type'] == 'websocket':

            ws_factory = CrossbarWampWebSocketServerFactory(
                self.session_factory, self.config.extra.cbdir, path_config,
                self._templates)

            ## FIXME: Site.start/stopFactory should start/stop factories wrapped as Resources
            ws_factory.startFactory()

            return WebSocketResource(ws_factory)

        ## Static file hierarchy resource
        ##
        elif path_config['type'] == 'static':

            static_options = path_config.get('options', {})

            if 'directory' in path_config:

                static_dir = os.path.abspath(
                    os.path.join(self.config.extra.cbdir,
                                 path_config['directory']))

            elif 'package' in path_config:

                if not 'resource' in path_config:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "missing resource")

                try:
                    mod = importlib.import_module(path_config['package'])
                except ImportError as e:
                    emsg = "ERROR: could not import resource '{}' from package '{}' - {}".format(
                        path_config['resource'], path_config['package'], e)
                    log.msg(emsg)
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration", emsg)
                else:
                    try:
                        static_dir = os.path.abspath(
                            pkg_resources.resource_filename(
                                path_config['package'],
                                path_config['resource']))
                    except Exception as e:
                        emsg = "ERROR: could not import resource '{}' from package '{}' - {}".format(
                            path_config['resource'], path_config['package'], e)
                        log.msg(emsg)
                        raise ApplicationError(
                            "crossbar.error.invalid_configuration", emsg)

            else:

                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "missing web spec")

            static_dir = static_dir.encode(
                'ascii',
                'ignore')  # http://stackoverflow.com/a/20433918/884770

            ## create resource for file system hierarchy
            ##
            if static_options.get('enable_directory_listing', False):
                static_resource = File(static_dir)
            else:
                static_resource = FileNoListing(static_dir)

            ## set extra MIME types
            ##
            static_resource.contentTypes.update(EXTRA_MIME_TYPES)
            if 'mime_types' in static_options:
                static_resource.contentTypes.update(
                    static_options['mime_types'])
            patchFileContentTypes(static_resource)

            ## render 404 page on any concrete path not found
            ##
            static_resource.childNotFound = Resource404(
                self._templates, static_dir)

            return static_resource

        ## WSGI resource
        ##
        elif path_config['type'] == 'wsgi':

            if not _HAS_WSGI:
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "WSGI unsupported")

            wsgi_options = path_config.get('options', {})

            if not 'module' in path_config:
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "missing module")

            if not 'object' in path_config:
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "missing object")

            try:
                mod = importlib.import_module(path_config['module'])
            except ImportError as e:
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "module import failed - {}".format(e))
            else:
                if not path_config['object'] in mod.__dict__:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "object not in module")
                else:
                    app = getattr(mod, path_config['object'])

            ## create a Twisted Web WSGI resource from the user's WSGI application object
            try:
                wsgi_resource = WSGIResource(reactor, reactor.getThreadPool(),
                                             app)
            except Exception as e:
                raise ApplicationError(
                    "crossbar.error.invalid_configuration",
                    "could not instantiate WSGI resource: {}".format(e))
            else:
                return wsgi_resource

        ## Redirecting resource
        ##
        elif path_config['type'] == 'redirect':
            redirect_url = path_config['url'].encode('ascii', 'ignore')
            return RedirectResource(redirect_url)

        ## JSON value resource
        ##
        elif path_config['type'] == 'json':
            value = path_config['value']

            return JsonResource(value)

        ## CGI script resource
        ##
        elif path_config['type'] == 'cgi':

            cgi_processor = path_config['processor']
            cgi_directory = os.path.abspath(
                os.path.join(self.config.extra.cbdir,
                             path_config['directory']))
            cgi_directory = cgi_directory.encode(
                'ascii',
                'ignore')  # http://stackoverflow.com/a/20433918/884770

            return CgiDirectory(cgi_directory, cgi_processor,
                                Resource404(self._templates, cgi_directory))

        ## WAMP-Longpoll transport resource
        ##
        elif path_config['type'] == 'longpoll':

            path_options = path_config.get('options', {})

            lp_resource = WampLongPollResource(
                self.session_factory,
                timeout=path_options.get('request_timeout', 10),
                killAfter=path_options.get('session_timeout', 30),
                queueLimitBytes=path_options.get('queue_limit_bytes',
                                                 128 * 1024),
                queueLimitMessages=path_options.get('queue_limit_messages',
                                                    100),
                debug=path_options.get('debug', False),
                debug_transport_id=path_options.get('debug_transport_id',
                                                    None))
            lp_resource._templates = self._templates

            return lp_resource

        ## Pusher resource
        ##
        elif path_config['type'] == 'pusher':

            ## create a vanilla session: the pusher will use this to inject events
            ##
            pusher_session_config = ComponentConfig(realm=path_config['realm'],
                                                    extra=None)
            pusher_session = ApplicationSession(pusher_session_config)

            ## add the pushing session to the router
            ##
            self.session_factory.add(pusher_session,
                                     authrole=path_config.get(
                                         'role', 'anonymous'))

            ## now create the pusher Twisted Web resource
            ##
            return PusherResource(path_config.get('options', {}),
                                  pusher_session)

        ## Schema Docs resource
        ##
        elif path_config['type'] == 'schemadoc':

            realm = path_config['realm']

            if not realm in self.realm_to_id:
                raise ApplicationError(
                    "crossbar.error.no_such_object",
                    "No realm with URI '{}' configured".format(realm))

            realm_id = self.realm_to_id[realm]

            realm_schemas = self.realms[realm_id].session._schemas

            return SchemaDocResource(self._templates, realm, realm_schemas)

        ## Nested subpath resource
        ##
        elif path_config['type'] == 'path':

            nested_paths = path_config.get('paths', {})

            if '/' in nested_paths:
                nested_resource = self.create_resource(nested_paths['/'])
            else:
                nested_resource = Resource()

            ## nest subpaths under the current entry
            ##
            self.add_paths(nested_resource, nested_paths)

            return nested_resource

        else:
            raise ApplicationError(
                "crossbar.error.invalid_configuration",
                "invalid Web path type '{}'".format(path_config['type']))
Ejemplo n.º 23
0
    def render(self, request):
        """
        Initiate the rendering of a HTTP/GET request by calling a WAMP procedure, the
        resulting ``dict`` is rendered together with the specified Jinja2 template
        for this URL.

        :param request: The HTTP request.
        :returns: server.NOT_DONE_YET (special)
        """
        # https://twistedmatrix.com/documents/current/api/twisted.web.resource.Resource.html#render
        # The encoded path of the request URI (_not_ (!) including query arguments),
        full_path = request.path.decode('utf-8')

        # HTTP request method (GET, POST, ..)
        http_method = request.method.decode()
        if http_method not in ['GET', 'POST']:
            request.setResponseCode(511)
            return self._render_error(
                'Method not allowed on path "{full_path}" [werkzeug.routing.MapAdapter.match]'
                .format(full_path=full_path), request)

        # in case of HTTP/POST, read request body as one binary string
        if http_method == 'POST' and request.content:
            content_type = request.getAllHeaders().get(
                b'content-type', b'application/octet-stream').decode()

            # https://stackoverflow.com/a/11549600/884770
            # http://marianoiglesias.com.ar/python/file-uploading-with-multi-part-encoding-using-twisted/
            body_data = request.content.read()
            self.log.info('POST data len = {newdata_len}',
                          newdata_len=len(body_data))
        else:
            content_type = None
            body_data = None

        # parse and decode any query parameters
        query_args = {}
        if request.args:
            for key, values in request.args.items():
                key = key.decode()
                # we only process the first header value per key (!)
                value = values[0].decode()
                query_args[key] = value
            self.log.info('Parsed query parameters: {query_args}',
                          query_args=query_args)

        # parse client announced accept header
        client_accept = request.getAllHeaders().get(b'accept', None)
        if client_accept:
            client_accept = client_accept.decode()

        # flag indicating the client wants to get plain JSON results (not rendered HTML)
        client_return_json = client_accept == 'application/json'

        # client cookie processing
        cookie = request.received_cookies.get(b'session_cookie')
        self.log.debug('Session Cookie is ({})'.format(cookie))
        if cookie:
            session = self._session_cache.get(cookie)
            if not session:
                # FIXME: lookup role for current session
                self.log.debug(
                    'Creating a new session for cookie ({})'.format(cookie))
                authrole = 'anonymous'
                session = ApplicationSession(
                    ComponentConfig(realm=self._realm_name, extra=None))
                self._worker._router_session_factory.add(session,
                                                         authrole=authrole)
                self._session_cache[cookie] = session
            else:
                self.log.debug(
                    'Using a cached session for ({})'.format(cookie))
        else:
            self.log.debug(
                'No session cookie, falling back on default session')
            session = self._default_session

        try:
            # werkzeug.routing.MapAdapter
            # http://werkzeug.pocoo.org/docs/dev/routing/#werkzeug.routing.MapAdapter.match
            (procedure, request.template), kwargs = self._map_adapter.match(
                full_path, method=http_method, query_args=query_args)
            if kwargs and query_args:
                kwargs.update(query_args)
            else:
                kwargs = query_args
            self.log.info(
                'WapResource on path "{full_path}" mapped to procedure "{procedure}"',
                full_path=full_path,
                procedure=procedure)

            # FIXME: how do we allow calling WAMP procedures with positional args?
            if procedure:
                self.log.info(
                    'calling procedure "{procedure}" with kwargs={kwargs} and body_data_len={body_data_len}',
                    procedure=procedure,
                    kwargs=kwargs,
                    body_data_len=len(body_data) if body_data else 0)

                # we need a session to call
                if not session:
                    self.log.error('could not call procedure - no session')
                    return self._render_error(
                        'could not call procedure - no session', request)

                if body_data:
                    if kwargs:
                        d = session.call(procedure,
                                         **kwargs,
                                         data=body_data,
                                         data_type=content_type)
                    else:
                        d = session.call(procedure,
                                         data=body_data,
                                         data_type=content_type)
                else:
                    if kwargs:
                        d = session.call(procedure, **kwargs)
                    else:
                        d = session.call(procedure)
            else:
                d = succeed({})

            d.addCallbacks(self._after_call_success,
                           self._after_call_error,
                           callbackArgs=[request, client_return_json],
                           errbackArgs=[request, client_return_json])

            return server.NOT_DONE_YET

        except NotFound:
            self.log.info('URL "{url}" not found (method={method})',
                          url=full_path,
                          method=http_method)
            request.setResponseCode(404)
            return self._render_error(
                'Path "{full_path}" not found [werkzeug.routing.MapAdapter.match]'
                .format(full_path=full_path), request)

        except MethodNotAllowed:
            self.log.info('method={method} not allowed on URL "{url}"',
                          url=full_path,
                          method=http_method)
            request.setResponseCode(511)
            return self._render_error(
                'Method not allowed on path "{full_path}" [werkzeug.routing.MapAdapter.match]'
                .format(full_path=full_path), request)

        except Exception as e:
            self.log.info(
                'error while processing method={method} on URL "{url}": {e}',
                url=full_path,
                method=http_method,
                e=e)
            request.setResponseCode(500)
            request.write(
                self._render_error(
                    'Unknown error with path "{full_path}" [werkzeug.routing.MapAdapter.match]'
                    .format(full_path=full_path), request))
            raise
Ejemplo n.º 24
0
    def __init__(self, worker, config, path):
        """
        :param worker: The router worker controller within this Web service is started.
        :type worker: crossbar.worker.router.RouterController

        :param config: The Web service configuration item.
        :type config: dict
        """
        resource.Resource.__init__(self)
        self._worker = worker
        self._config = config
        self._session_cache = {}

        self._realm_name = config.get('wamp', {}).get('realm', None)
        self._authrole = config.get('wamp', {}).get('authrole', 'anonymous')
        self._service_agent = worker.realm_by_name(self._realm_name).session

        #   TODO:
        #       We need to lookup the credentials for the current user based on the pre-established
        #       HTTP session cookie, this will establish the 'authrole' the user is running as.
        #       This 'authrole' can then be used to authorize the back-end topic call.
        #   QUESTION:
        #       Does the topic need the authid, if so, how do we pass it?
        #
        #   This is our default (anonymous) session for unauthenticated users
        #
        router = worker._router_factory.get(self._realm_name)
        self._default_session = ApplicationSession(
            ComponentConfig(realm=self._realm_name, extra=None))
        worker._router_session_factory.add(self._default_session,
                                           router,
                                           authrole=self._authrole)

        # Setup Jinja2 to point to our templates folder or a package resource
        #
        templates_config = config.get("templates")

        if type(templates_config) == str:
            # resolve specified template directory path relative to node directory
            templates_dir = os.path.abspath(
                os.path.join(self._worker.config.extra.cbdir,
                             templates_config))
            templates_source = 'directory'

        elif type(templates_config) == dict:

            # in case we got a dict, that must contain "package" and "resource" attributes
            if 'package' not in templates_config:
                raise ApplicationError(
                    'crossbar.error.invalid_configuration',
                    'missing attribute "resource" in WAP web service configuration'
                )

            if 'resource' not in templates_config:
                raise ApplicationError(
                    'crossbar.error.invalid_configuration',
                    'missing attribute "resource" in WAP web service configuration'
                )

            try:
                importlib.import_module(templates_config['package'])
            except ImportError as e:
                emsg = 'Could not import resource {} from package {}: {}'.format(
                    templates_config['resource'], templates_config['package'],
                    e)
                raise ApplicationError('crossbar.error.invalid_configuration',
                                       emsg)
            else:
                try:
                    # resolve template directory from package resource
                    templates_dir = os.path.abspath(
                        pkg_resources.resource_filename(
                            templates_config['package'],
                            templates_config['resource']))
                except Exception as e:
                    emsg = 'Could not import resource {} from package {}: {}'.format(
                        templates_config['resource'],
                        templates_config['package'], e)
                    raise ApplicationError(
                        'crossbar.error.invalid_configuration', emsg)

            templates_source = 'package'
        else:
            raise ApplicationError(
                'crossbar.error.invalid_configuration',
                'invalid type "{}" for attribute "templates" in WAP web service configuration'
                .format(type(templates_config)))

        if config.get('sandbox', True):
            # The sandboxed environment. It works like the regular environment but tells the compiler to
            # generate sandboxed code.
            # https://jinja.palletsprojects.com/en/2.11.x/sandbox/#jinja2.sandbox.SandboxedEnvironment
            env = SandboxedEnvironment(loader=FileSystemLoader(templates_dir),
                                       autoescape=True)
        else:
            env = Environment(loader=FileSystemLoader(templates_dir),
                              autoescape=True)

        self.log.info(
            'WapResource created (realm="{realm}", authrole="{authrole}", templates_dir="{templates_dir}", templates_source="{templates_source}", jinja2_env={jinja2_env})',
            realm=hlid(self._realm_name),
            authrole=hlid(self._authrole),
            templates_dir=hlid(templates_dir),
            templates_source=hlid(templates_source),
            jinja2_env=hltype(env.__class__))

        # http://werkzeug.pocoo.org/docs/dev/routing/#werkzeug.routing.Map
        map = Map()

        # Add all our routes into 'map', note each route endpoint is a tuple of the
        # topic to call, and the template to use when rendering the results.
        for route in config.get('routes', {}):

            # compute full absolute URL of route to be added - ending in Werkzeug/Routes URL pattern
            rpath = route['path']
            _rp = []
            if path != '/':
                _rp.append(path)
            if rpath != '/':
                _rp.append(rpath)
            route_url = '/' + '/'.join(_rp)
            route_methods = [route.get('method')]

            # note the WAMP procedure to call and the Jinja2 template to render as HTTP response
            route_endpoint = (route['call'], env.get_template(route['render']))
            map.add(
                Rule(route_url, methods=route_methods,
                     endpoint=route_endpoint))
            self.log.info(
                'WapResource route added (url={route_url}, methods={route_methods}, endpoint={route_endpoint})',
                route_url=hlid(route_url),
                route_methods=hlid(route_methods),
                route_endpoint=route_endpoint)

        # http://werkzeug.pocoo.org/docs/dev/routing/#werkzeug.routing.MapAdapter
        # http://werkzeug.pocoo.org/docs/dev/routing/#werkzeug.routing.MapAdapter.match
        self._map_adapter = map.bind('/')
Ejemplo n.º 25
0
    def render_GET(self, request):
        """
        Initiate the rendering of a HTTP/GET request by calling a WAMP procedure, the
        resulting ``dict`` is rendered together with the specified Jinja2 template
        for this URL.

        :param request: The HTTP request.
        :returns: server.NOT_DONE_YET (special)
        """
        cookie = request.received_cookies.get(b'session_cookie')
        self.log.debug('Session Cookie is ({})'.format(cookie))
        if cookie:
            session = self._session_cache.get(cookie)
            if not session:
                # FIXME: lookup role for current session
                self.log.debug('Creating a new session for cookie ({})'.format(cookie))
                authrole = 'anonymous'
                session = ApplicationSession(ComponentConfig(realm=self._realm_name, extra=None))
                self._worker._router_session_factory.add(session, authrole=authrole)
                self._session_cache[cookie] = session
            else:
                self.log.debug('Using a cached session for ({})'.format(cookie))
        else:
            self.log.debug('No session cookie, falling back on default session')
            session = self._default_session

        if not session:
            self.log.error('could not call procedure - no session')
            return self._render_error('could not call procedure - no session', request)

        full_path = request.uri.decode('utf-8')
        try:
            # werkzeug.routing.MapAdapter
            # http://werkzeug.pocoo.org/docs/dev/routing/#werkzeug.routing.MapAdapter.match
            (procedure, request.template), kwargs = self._map_adapter.match(full_path)

            self.log.debug(
                'WapResource HTTP/GET "{full_path}" mapped to procedure "{procedure}"',
                full_path=full_path,
                procedure=procedure)

            # FIXME: how do we allow calling WAMP procedures with positional args?
            if kwargs:
                d = session.call(procedure, **kwargs)
            else:
                d = session.call(procedure)

            # d.addCallback(self._after_call_success, request)
            # d.addErrback(self._after_call_error, request)
            d.addCallbacks(
                self._after_call_success,
                self._after_call_error,
                callbackArgs=[request],
                errbackArgs=[request])

            return server.NOT_DONE_YET

        except NotFound:
            request.setResponseCode(404)
            return self._render_error('path not found [werkzeug.routing.MapAdapter.match]', request)

        except MethodNotAllowed:
            request.setResponseCode(511)
            return self._render_error('method not allowed [werkzeug.routing.MapAdapter.match]', request)

        except Exception:
            request.setResponseCode(500)
            request.write(self._render_error('unknown error [werkzeug.routing.MapAdapter.match]', request))
            raise
Ejemplo n.º 26
0
    def create_resource(self, path_config):
        """
        Creates child resource to be added to the parent.

        :param path_config: Configuration for the new child resource.
        :type path_config: dict

        :returns: Resource -- the new child resource
        """
        # WAMP-WebSocket resource
        #
        if path_config['type'] == 'websocket':

            ws_factory = WampWebSocketServerFactory(self._router_session_factory, self.config.extra.cbdir, path_config, self._templates)

            # FIXME: Site.start/stopFactory should start/stop factories wrapped as Resources
            ws_factory.startFactory()

            return WebSocketResource(ws_factory)

        # Static file hierarchy resource
        #
        elif path_config['type'] == 'static':

            static_options = path_config.get('options', {})

            if 'directory' in path_config:

                static_dir = os.path.abspath(os.path.join(self.config.extra.cbdir, path_config['directory']))

            elif 'package' in path_config:

                if 'resource' not in path_config:
                    raise ApplicationError(u"crossbar.error.invalid_configuration", "missing resource")

                try:
                    mod = importlib.import_module(path_config['package'])
                except ImportError as e:
                    emsg = "Could not import resource {} from package {}: {}".format(path_config['resource'], path_config['package'], e)
                    self.log.error(emsg)
                    raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
                else:
                    try:
                        static_dir = os.path.abspath(pkg_resources.resource_filename(path_config['package'], path_config['resource']))
                    except Exception as e:
                        emsg = "Could not import resource {} from package {}: {}".format(path_config['resource'], path_config['package'], e)
                        self.log.error(emsg)
                        raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)

            else:

                raise ApplicationError(u"crossbar.error.invalid_configuration", "missing web spec")

            static_dir = static_dir.encode('ascii', 'ignore')  # http://stackoverflow.com/a/20433918/884770

            # create resource for file system hierarchy
            #
            if static_options.get('enable_directory_listing', False):
                static_resource_class = StaticResource
            else:
                static_resource_class = StaticResourceNoListing

            cache_timeout = static_options.get('cache_timeout', DEFAULT_CACHE_TIMEOUT)

            static_resource = static_resource_class(static_dir, cache_timeout=cache_timeout)

            # set extra MIME types
            #
            static_resource.contentTypes.update(EXTRA_MIME_TYPES)
            if 'mime_types' in static_options:
                static_resource.contentTypes.update(static_options['mime_types'])
            patchFileContentTypes(static_resource)

            # render 404 page on any concrete path not found
            #
            static_resource.childNotFound = Resource404(self._templates, static_dir)

            return static_resource

        # WSGI resource
        #
        elif path_config['type'] == 'wsgi':

            if not _HAS_WSGI:
                raise ApplicationError(u"crossbar.error.invalid_configuration", "WSGI unsupported")

            # wsgi_options = path_config.get('options', {})

            if 'module' not in path_config:
                raise ApplicationError(u"crossbar.error.invalid_configuration", "missing WSGI app module")

            if 'object' not in path_config:
                raise ApplicationError(u"crossbar.error.invalid_configuration", "missing WSGI app object")

            # import WSGI app module and object
            mod_name = path_config['module']
            try:
                mod = importlib.import_module(mod_name)
            except ImportError as e:
                raise ApplicationError(u"crossbar.error.invalid_configuration", "WSGI app module '{}' import failed: {} - Python search path was {}".format(mod_name, e, sys.path))
            else:
                obj_name = path_config['object']
                if obj_name not in mod.__dict__:
                    raise ApplicationError(u"crossbar.error.invalid_configuration", "WSGI app object '{}' not in module '{}'".format(obj_name, mod_name))
                else:
                    app = getattr(mod, obj_name)

            # create a Twisted Web WSGI resource from the user's WSGI application object
            try:
                wsgi_resource = WSGIResource(self._reactor, self._reactor.getThreadPool(), app)
            except Exception as e:
                raise ApplicationError(u"crossbar.error.invalid_configuration", "could not instantiate WSGI resource: {}".format(e))
            else:
                return wsgi_resource

        # Redirecting resource
        #
        elif path_config['type'] == 'redirect':
            redirect_url = path_config['url'].encode('ascii', 'ignore')
            return RedirectResource(redirect_url)

        # JSON value resource
        #
        elif path_config['type'] == 'json':
            value = path_config['value']

            return JsonResource(value)

        # CGI script resource
        #
        elif path_config['type'] == 'cgi':

            cgi_processor = path_config['processor']
            cgi_directory = os.path.abspath(os.path.join(self.config.extra.cbdir, path_config['directory']))
            cgi_directory = cgi_directory.encode('ascii', 'ignore')  # http://stackoverflow.com/a/20433918/884770

            return CgiDirectory(cgi_directory, cgi_processor, Resource404(self._templates, cgi_directory))

        # WAMP-Longpoll transport resource
        #
        elif path_config['type'] == 'longpoll':

            path_options = path_config.get('options', {})

            lp_resource = WampLongPollResource(self._router_session_factory,
                                               timeout=path_options.get('request_timeout', 10),
                                               killAfter=path_options.get('session_timeout', 30),
                                               queueLimitBytes=path_options.get('queue_limit_bytes', 128 * 1024),
                                               queueLimitMessages=path_options.get('queue_limit_messages', 100),
                                               debug=path_options.get('debug', False),
                                               debug_transport_id=path_options.get('debug_transport_id', None)
                                               )
            lp_resource._templates = self._templates

            return lp_resource

        # Publisher resource (part of REST-bridge)
        #
        elif path_config['type'] == 'publisher':

            # create a vanilla session: the publisher will use this to inject events
            #
            publisher_session_config = ComponentConfig(realm=path_config['realm'], extra=None)
            publisher_session = ApplicationSession(publisher_session_config)

            # add the publisher session to the router
            #
            self._router_session_factory.add(publisher_session, authrole=path_config.get('role', 'anonymous'))

            # now create the publisher Twisted Web resource
            #
            return PublisherResource(path_config.get('options', {}), publisher_session)

        # Webhook resource (part of REST-bridge)
        #
        elif path_config['type'] == 'webhook':

            # create a vanilla session: the webhook will use this to inject events
            #
            webhook_session_config = ComponentConfig(realm=path_config['realm'], extra=None)
            webhook_session = ApplicationSession(webhook_session_config)

            # add the webhook session to the router
            #
            self._router_session_factory.add(webhook_session, authrole=path_config.get('role', 'anonymous'))

            # now create the webhook Twisted Web resource
            #
            return WebhookResource(path_config.get('options', {}), webhook_session)

        # Caller resource (part of REST-bridge)
        #
        elif path_config['type'] == 'caller':

            # create a vanilla session: the caller will use this to inject calls
            #
            caller_session_config = ComponentConfig(realm=path_config['realm'], extra=None)
            caller_session = ApplicationSession(caller_session_config)

            # add the calling session to the router
            #
            self._router_session_factory.add(caller_session, authrole=path_config.get('role', 'anonymous'))

            # now create the caller Twisted Web resource
            #
            return CallerResource(path_config.get('options', {}), caller_session)

        # File Upload resource
        #
        elif path_config['type'] == 'upload':

            upload_directory = os.path.abspath(os.path.join(self.config.extra.cbdir, path_config['directory']))
            upload_directory = upload_directory.encode('ascii', 'ignore')  # http://stackoverflow.com/a/20433918/884770
            if not os.path.isdir(upload_directory):
                emsg = "configured upload directory '{}' in file upload resource isn't a directory".format(upload_directory)
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)

            if 'temp_directory' in path_config:
                temp_directory = os.path.abspath(os.path.join(self.config.extra.cbdir, path_config['temp_directory']))
                temp_directory = temp_directory.encode('ascii', 'ignore')  # http://stackoverflow.com/a/20433918/884770
            else:
                temp_directory = os.path.abspath(tempfile.gettempdir())
                temp_directory = os.path.join(temp_directory, 'crossbar-uploads')
                if not os.path.exists(temp_directory):
                    os.makedirs(temp_directory)

            if not os.path.isdir(temp_directory):
                emsg = "configured temp directory '{}' in file upload resource isn't a directory".format(temp_directory)
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)

            # file upload progress and finish events are published via this session
            #
            upload_session_config = ComponentConfig(realm=path_config['realm'], extra=None)
            upload_session = ApplicationSession(upload_session_config)

            self._router_session_factory.add(upload_session, authrole=path_config.get('role', 'anonymous'))

            self.log.info("File upload resource started. Uploads to {upl} using temp folder {tmp}.", upl=upload_directory, tmp=temp_directory)

            return FileUploadResource(upload_directory, temp_directory, path_config['form_fields'], upload_session, path_config.get('options', {}))

        # Generic Twisted Web resource
        #
        elif path_config['type'] == 'resource':

            try:
                klassname = path_config['classname']

                self.log.debug("Starting class '{}'".format(klassname))

                c = klassname.split('.')
                module_name, klass_name = '.'.join(c[:-1]), c[-1]
                module = importlib.import_module(module_name)
                make = getattr(module, klass_name)

                return make(path_config.get('extra', {}))

            except Exception as e:
                emsg = "Failed to import class '{}' - {}".format(klassname, e)
                self.log.error(emsg)
                self.log.error("PYTHONPATH: {pythonpath}", pythonpath=sys.path)
                raise ApplicationError(u"crossbar.error.class_import_failed", emsg)

        # Schema Docs resource
        #
        elif path_config['type'] == 'schemadoc':

            realm = path_config['realm']

            if realm not in self.realm_to_id:
                raise ApplicationError(u"crossbar.error.no_such_object", "No realm with URI '{}' configured".format(realm))

            realm_id = self.realm_to_id[realm]

            realm_schemas = self.realms[realm_id].session._schemas

            return SchemaDocResource(self._templates, realm, realm_schemas)

        # Nested subpath resource
        #
        elif path_config['type'] == 'path':

            nested_paths = path_config.get('paths', {})

            if '/' in nested_paths:
                nested_resource = self.create_resource(nested_paths['/'])
            else:
                nested_resource = Resource()

            # nest subpaths under the current entry
            #
            self.add_paths(nested_resource, nested_paths)

            return nested_resource

        else:
            raise ApplicationError(u"crossbar.error.invalid_configuration", "invalid Web path type '{}'".format(path_config['type']))
Ejemplo n.º 27
0
    def start_router_transport(self, id, config, details=None):
        """
        Start a transport on this router and return when the transport has started.

        **Usage:**

        This procedure is registered under

        * ``crossbar.node.<node_id>.worker.<worker_id>.start_router_transport``

        The procedure takes a WAMP transport configuration with a listening endpoint, e.g.

        .. code-block:: javascript

            {
                "type": "websocket",
                "endpoint": {
                    "type": "tcp",
                    "port": 8080
                }
            }

        **Errors:**

        The procedure may raise the following errors:

        * ``crossbar.error.invalid_configuration`` - the provided transport configuration is invalid
        * ``crossbar.error.already_running`` - a transport with the given ID is already running (or starting)
        * ``crossbar.error.cannot_listen`` - could not listen on the configured listening endpoint of the transport
        * ``crossbar.error.class_import_failed`` - a side-by-side component could not be instantiated

        **Events:**

        The procedure will publish an event when the transport **is starting** to

        * ``crossbar.node.<node_id>.worker.<worker_id>.on_router_transport_starting``

        and publish an event when the transport **has started** to

        * ``crossbar.node.<node_id>.worker.<worker_id>.on_router_transport_started``

        :param id: The ID of the transport to start.
        :type id: unicode
        :param config: The transport configuration.
        :type config: dict
        """
        self.log.debug("{}.start_router_transport".format(self.__class__.__name__),
                       id=id, config=config)

        # prohibit starting a transport twice
        #
        if id in self.transports:
            emsg = "Could not start transport: a transport with ID '{}' is already running (or starting)".format(id)
            self.log.error(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check configuration
        #
        try:
            checkconfig.check_router_transport(config)
        except Exception as e:
            emsg = "Invalid router transport configuration: {}".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
        else:
            self.log.debug("Starting {}-transport on router.".format(config['type']))

        # standalone WAMP-RawSocket transport
        #
        if config['type'] == 'rawsocket':

            transport_factory = WampRawSocketServerFactory(self._router_session_factory, config)
            transport_factory.noisy = False

        # standalone WAMP-WebSocket transport
        #
        elif config['type'] == 'websocket':

            transport_factory = WampWebSocketServerFactory(self._router_session_factory, self.config.extra.cbdir, config, self._templates)
            transport_factory.noisy = False

        # Flash-policy file server pseudo transport
        #
        elif config['type'] == 'flashpolicy':

            transport_factory = FlashPolicyFactory(config.get('allowed_domain', None), config.get('allowed_ports', None))

        # WebSocket testee pseudo transport
        #
        elif config['type'] == 'websocket.testee':

            transport_factory = WebSocketTesteeServerFactory(config, self._templates)

        # Stream testee pseudo transport
        #
        elif config['type'] == 'stream.testee':

            transport_factory = StreamTesteeServerFactory()

        # Twisted Web based transport
        #
        elif config['type'] == 'web':

            options = config.get('options', {})

            # create Twisted Web root resource
            #
            root_config = config['paths']['/']

            root_type = root_config['type']
            root_options = root_config.get('options', {})

            # Static file hierarchy root resource
            #
            if root_type == 'static':

                if 'directory' in root_config:

                    root_dir = os.path.abspath(os.path.join(self.config.extra.cbdir, root_config['directory']))

                elif 'package' in root_config:

                    if 'resource' not in root_config:
                        raise ApplicationError(u"crossbar.error.invalid_configuration", "missing resource")

                    try:
                        mod = importlib.import_module(root_config['package'])
                    except ImportError as e:
                        emsg = "Could not import resource {} from package {}: {}".format(root_config['resource'], root_config['package'], e)
                        self.log.error(emsg)
                        raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
                    else:
                        try:
                            root_dir = os.path.abspath(pkg_resources.resource_filename(root_config['package'], root_config['resource']))
                        except Exception as e:
                            emsg = "Could not import resource {} from package {}: {}".format(root_config['resource'], root_config['package'], e)
                            self.log.error(emsg)
                            raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
                        else:
                            mod_version = getattr(mod, '__version__', '?.?.?')
                            self.log.info("Loaded static Web resource '{}' from package '{} {}' (filesystem path {})".format(root_config['resource'], root_config['package'], mod_version, root_dir))

                else:
                    raise ApplicationError(u"crossbar.error.invalid_configuration", "missing web spec")

                root_dir = root_dir.encode('ascii', 'ignore')  # http://stackoverflow.com/a/20433918/884770
                self.log.debug("Starting Web service at root directory {}".format(root_dir))

                # create resource for file system hierarchy
                #
                if root_options.get('enable_directory_listing', False):
                    static_resource_class = StaticResource
                else:
                    static_resource_class = StaticResourceNoListing

                cache_timeout = root_options.get('cache_timeout', DEFAULT_CACHE_TIMEOUT)

                root = static_resource_class(root_dir, cache_timeout=cache_timeout)

                # set extra MIME types
                #
                root.contentTypes.update(EXTRA_MIME_TYPES)
                if 'mime_types' in root_options:
                    root.contentTypes.update(root_options['mime_types'])
                patchFileContentTypes(root)

                # render 404 page on any concrete path not found
                #
                root.childNotFound = Resource404(self._templates, root_dir)

            # WSGI root resource
            #
            elif root_type == 'wsgi':

                if not _HAS_WSGI:
                    raise ApplicationError(u"crossbar.error.invalid_configuration", "WSGI unsupported")

                # wsgi_options = root_config.get('options', {})

                if 'module' not in root_config:
                    raise ApplicationError(u"crossbar.error.invalid_configuration", "missing WSGI app module")

                if 'object' not in root_config:
                    raise ApplicationError(u"crossbar.error.invalid_configuration", "missing WSGI app object")

                # import WSGI app module and object
                mod_name = root_config['module']
                try:
                    mod = importlib.import_module(mod_name)
                except ImportError as e:
                    raise ApplicationError(u"crossbar.error.invalid_configuration", "WSGI app module '{}' import failed: {} - Python search path was {}".format(mod_name, e, sys.path))
                else:
                    obj_name = root_config['object']
                    if obj_name not in mod.__dict__:
                        raise ApplicationError(u"crossbar.error.invalid_configuration", "WSGI app object '{}' not in module '{}'".format(obj_name, mod_name))
                    else:
                        app = getattr(mod, obj_name)

                # create a Twisted Web WSGI resource from the user's WSGI application object
                try:
                    wsgi_resource = WSGIResource(self._reactor, self._reactor.getThreadPool(), app)
                except Exception as e:
                    raise ApplicationError(u"crossbar.error.invalid_configuration", "could not instantiate WSGI resource: {}".format(e))
                else:
                    # create a root resource serving everything via WSGI
                    root = WSGIRootResource(wsgi_resource, {})

            # Redirecting root resource
            #
            elif root_type == 'redirect':

                redirect_url = root_config['url'].encode('ascii', 'ignore')
                root = RedirectResource(redirect_url)

            # Publisher resource (part of REST-bridge)
            #
            elif root_type == 'publisher':

                # create a vanilla session: the publisher will use this to inject events
                #
                publisher_session_config = ComponentConfig(realm=root_config['realm'], extra=None)
                publisher_session = ApplicationSession(publisher_session_config)

                # add the publishing session to the router
                #
                self._router_session_factory.add(publisher_session, authrole=root_config.get('role', 'anonymous'))

                # now create the publisher Twisted Web resource and add it to resource tree
                #
                root = PublisherResource(root_config.get('options', {}), publisher_session)

            # Webhook resource (part of REST-bridge)
            #
            elif root_type == 'webhook':

                # create a vanilla session: the webhook will use this to inject events
                #
                webhook_session_config = ComponentConfig(realm=root_config['realm'], extra=None)
                webhook_session = ApplicationSession(webhook_session_config)

                # add the publishing session to the router
                #
                self._router_session_factory.add(webhook_session, authrole=root_config.get('role', 'anonymous'))

                # now create the webhook Twisted Web resource and add it to resource tree
                #
                root = WebhookResource(root_config.get('options', {}), webhook_session)

            # Caller resource (part of REST-bridge)
            #
            elif root_type == 'caller':

                # create a vanilla session: the caller will use this to inject calls
                #
                caller_session_config = ComponentConfig(realm=root_config['realm'], extra=None)
                caller_session = ApplicationSession(caller_session_config)

                # add the calling session to the router
                #
                self._router_session_factory.add(caller_session, authrole=root_config.get('role', 'anonymous'))

                # now create the caller Twisted Web resource and add it to resource tree
                #
                root = CallerResource(root_config.get('options', {}), caller_session)

            # Generic Twisted Web resource
            #
            elif root_type == 'resource':

                try:
                    klassname = root_config['classname']

                    self.log.debug("Starting class '{}'".format(klassname))

                    c = klassname.split('.')
                    module_name, klass_name = '.'.join(c[:-1]), c[-1]
                    module = importlib.import_module(module_name)
                    make = getattr(module, klass_name)
                    root = make(root_config.get('extra', {}))

                except Exception as e:
                    emsg = "Failed to import class '{}' - {}".format(klassname, e)
                    self.log.error(emsg)
                    self.log.error("PYTHONPATH: {pythonpath}",
                                   pythonpath=sys.path)
                    raise ApplicationError(u"crossbar.error.class_import_failed", emsg)

            # Invalid root resource
            #
            else:
                raise ApplicationError(u"crossbar.error.invalid_configuration", "invalid Web root path type '{}'".format(root_type))

            # create Twisted Web resources on all non-root paths configured
            #
            self.add_paths(root, config.get('paths', {}))

            # create the actual transport factory
            #
            transport_factory = Site(root)
            transport_factory.noisy = False

            # Web access logging
            #
            if not options.get('access_log', False):
                transport_factory.log = lambda _: None

            # Traceback rendering
            #
            transport_factory.displayTracebacks = options.get('display_tracebacks', False)

            # HSTS
            #
            if options.get('hsts', False):
                if 'tls' in config['endpoint']:
                    hsts_max_age = int(options.get('hsts_max_age', 31536000))
                    transport_factory.requestFactory = createHSTSRequestFactory(transport_factory.requestFactory, hsts_max_age)
                else:
                    self.log.warn("Warning: HSTS requested, but running on non-TLS - skipping HSTS")

        # Unknown transport type
        #
        else:
            # should not arrive here, since we did check_transport() in the beginning
            raise Exception("logic error")

        # create transport endpoint / listening port from transport factory
        #
        d = create_listening_port_from_config(config['endpoint'], transport_factory, self.config.extra.cbdir, self._reactor)

        def ok(port):
            self.transports[id] = RouterTransport(id, config, transport_factory, port)
            self.log.debug("Router transport '{}'' started and listening".format(id))
            return

        def fail(err):
            emsg = "Cannot listen on transport endpoint: {}".format(err.value)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.cannot_listen", emsg)

        d.addCallbacks(ok, fail)
        return d