def __init__(self, factory, config): ## transport configuration self._config = config ## WAMP serializer ## serid = config.get('serializer', 'msgpack') if serid == 'json': ## try JSON WAMP serializer try: from autobahn.wamp.serializer import JsonSerializer serializer = JsonSerializer() except ImportError: raise Exception("could not load WAMP-JSON serializer") elif serid == 'msgpack': ## try MsgPack WAMP serializer try: from autobahn.wamp.serializer import MsgPackSerializer serializer = MsgPackSerializer() serializer._serializer.ENABLE_V5 = False ## FIXME except ImportError: raise Exception("could not load WAMP-MsgPack serializer") else: raise Exception("invalid WAMP serializer '{}'".format(serid)) WampRawSocketClientFactory.__init__(self, factory, serializer)
def __init__(self, factory, config): # transport configuration self._config = config # WAMP serializer # serid = config.get('serializer', 'msgpack') if serid == 'json': # try JSON WAMP serializer try: from autobahn.wamp.serializer import JsonSerializer serializer = JsonSerializer() except ImportError: raise Exception("could not load WAMP-JSON serializer") elif serid == 'msgpack': # try MsgPack WAMP serializer try: from autobahn.wamp.serializer import MsgPackSerializer serializer = MsgPackSerializer() serializer._serializer.ENABLE_V5 = False # FIXME except ImportError: raise Exception("could not load WAMP-MsgPack serializer") else: raise Exception("invalid WAMP serializer '{}'".format(serid)) WampRawSocketClientFactory.__init__(self, factory, serializer)
def test_handshake_succeeds(self): """ A client can connect to a server. """ session_mock = Mock() t = FakeTransport() f = WampRawSocketClientFactory(lambda: session_mock) p = WampRawSocketClientProtocol() p.transport = t p.factory = f server_session_mock = Mock() st = FakeTransport() sf = WampRawSocketServerFactory(lambda: server_session_mock) sp = WampRawSocketServerProtocol() sp.transport = st sp.factory = sf sp.connectionMade() p.connectionMade() # Send the server the client handshake sp.dataReceived(t._written[0:1]) sp.dataReceived(t._written[1:4]) # Send the client the server handshake p.dataReceived(st._written) # The handshake succeeds, a session on each end is created # onOpen is called on the session session_mock.onOpen.assert_called_once_with(p) server_session_mock.onOpen.assert_called_once_with(sp)
def _create_transport_factory(reactor, transport, session_factory): """ Create a WAMP-over-XXX transport factory. """ if transport.type == u'websocket': serializers = create_transport_serializers(transport) factory = WampWebSocketClientFactory(session_factory, url=transport.url, serializers=serializers) elif transport.type == u'rawsocket': serializer = create_transport_serializer(transport.serializers[0]) factory = WampRawSocketClientFactory(session_factory, serializer=serializer) else: assert (False), 'should not arrive here' # set the options one at a time so we can give user better feedback for k, v in transport.options.items(): try: factory.setProtocolOptions(**{k: v}) except (TypeError, KeyError): # this allows us to document options as snake_case # until everything internally is upgraded from # camelCase try: factory.setProtocolOptions( **{_camel_case_from_snake_case(k): v}) except (TypeError, KeyError): raise ValueError("Unknown {} transport option: {}={}".format( transport.type, k, v)) return factory
def _create_transport_factory(reactor, transport_config, session_factory): """ Create a WAMP-over-XXX transport factory. """ if transport_config['type'] == 'websocket': # FIXME: forward WebSocket options serializers = _create_transport_serializers(transport_config) return WampWebSocketClientFactory(session_factory, url=transport_config['url'], serializers=serializers) elif transport_config['type'] == 'rawsocket': # FIXME: forward RawSocket options serializer = _create_transport_serializer(transport_config.get('serializer', u'json')) return WampRawSocketClientFactory(session_factory, serializer=serializer) else: assert(False), 'should not arrive here'
def _create_transport_factory(reactor, transport, session_factory): """ Create a WAMP-over-XXX transport factory. """ if transport.type == 'websocket': # FIXME: forward WebSocket options serializers = _create_transport_serializers(transport) return WampWebSocketClientFactory(session_factory, url=transport.url, serializers=serializers) elif transport.type == 'rawsocket': # FIXME: forward RawSocket options serializer = _create_transport_serializer(transport.serializer) return WampRawSocketClientFactory(session_factory, serializer=serializer) else: assert(False), 'should not arrive here'
def run(self, make, start_reactor=True, auto_reconnect=False, log_level='info'): """ Run the application component. :param make: A factory that produces instances of :class:`autobahn.asyncio.wamp.ApplicationSession` when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`. :type make: callable :param start_reactor: if True (the default) this method starts the Twisted reactor and doesn't return until the reactor stops. If there are any problems starting the reactor or connect()-ing, we stop the reactor and raise the exception back to the caller. :returns: None is returned, unless you specify ``start_reactor=False`` in which case the Deferred that connect() returns is returned; this will callback() with an IProtocol instance, which will actually be an instance of :class:`WampWebSocketClientProtocol` """ if start_reactor: # only select framework, set loop and start logging when we are asked # start the reactor - otherwise we are running in a program that likely # already tool care of all this. from twisted.internet import reactor txaio.use_twisted() txaio.config.loop = reactor txaio.start_logging(level=log_level) if callable(make): # factory for use ApplicationSession def create(): cfg = ComponentConfig(self.realm, self.extra) try: session = make(cfg) except Exception: self.log.failure('ApplicationSession could not be instantiated: {log_failure.value}') if start_reactor and reactor.running: reactor.stop() raise else: return session else: create = make if self.url.startswith(u'rs'): # try to parse RawSocket URL .. isSecure, host, port = parse_rs_url(self.url) # create a WAMP-over-RawSocket transport client factory transport_factory = WampRawSocketClientFactory(create) else: # try to parse WebSocket URL .. isSecure, host, port, resource, path, params = parse_ws_url(self.url) # create a WAMP-over-WebSocket transport client factory transport_factory = WampWebSocketClientFactory(create, url=self.url, serializers=self.serializers, proxy=self.proxy, headers=self.headers) # client WebSocket settings - similar to: # - http://crossbar.io/docs/WebSocket-Compression/#production-settings # - http://crossbar.io/docs/WebSocket-Options/#production-settings # The permessage-deflate extensions offered to the server .. offers = [PerMessageDeflateOffer()] # Function to accept permessage_delate responses from the server .. def accept(response): if isinstance(response, PerMessageDeflateResponse): return PerMessageDeflateResponseAccept(response) # set WebSocket options for all client connections transport_factory.setProtocolOptions(maxFramePayloadSize=1048576, maxMessagePayloadSize=1048576, autoFragmentSize=65536, failByDrop=False, openHandshakeTimeout=2.5, closeHandshakeTimeout=1., tcpNoDelay=True, autoPingInterval=10., autoPingTimeout=5., autoPingSize=4, perMessageCompressionOffers=offers, perMessageCompressionAccept=accept) # supress pointless log noise transport_factory.noisy = False # if user passed ssl= but isn't using isSecure, we'll never # use the ssl argument which makes no sense. context_factory = None if self.ssl is not None: if not isSecure: raise RuntimeError( 'ssl= argument value passed to %s conflicts with the "ws:" ' 'prefix of the url argument. Did you mean to use "wss:"?' % self.__class__.__name__) context_factory = self.ssl elif isSecure: from twisted.internet.ssl import optionsForClientTLS context_factory = optionsForClientTLS(host) from twisted.internet import reactor if self.proxy is not None: from twisted.internet.endpoints import TCP4ClientEndpoint client = TCP4ClientEndpoint(reactor, self.proxy['host'], self.proxy['port']) transport_factory.contextFactory = context_factory elif isSecure: from twisted.internet.endpoints import SSL4ClientEndpoint assert context_factory is not None client = SSL4ClientEndpoint(reactor, host, port, context_factory) else: from twisted.internet.endpoints import TCP4ClientEndpoint client = TCP4ClientEndpoint(reactor, host, port) # as the reactor shuts down, we wish to wait until we've sent # out our "Goodbye" message; leave() returns a Deferred that # fires when the transport gets to STATE_CLOSED def cleanup(proto): if hasattr(proto, '_session') and proto._session is not None: if proto._session.is_attached(): return proto._session.leave() elif proto._session.is_connected(): return proto._session.disconnect() # when our proto was created and connected, make sure it's cleaned # up properly later on when the reactor shuts down for whatever reason def init_proto(proto): reactor.addSystemEventTrigger('before', 'shutdown', cleanup, proto) return proto use_service = False if auto_reconnect: try: # since Twisted 16.1.0 from twisted.application.internet import ClientService use_service = True except ImportError: use_service = False if use_service: self.log.debug('using t.a.i.ClientService') # this is automatically reconnecting service = ClientService(client, transport_factory) service.startService() d = service.whenConnected() else: # this is only connecting once! self.log.debug('using t.i.e.connect()') d = client.connect(transport_factory) # if we connect successfully, the arg is a WampWebSocketClientProtocol d.addCallback(init_proto) # if the user didn't ask us to start the reactor, then they # get to deal with any connect errors themselves. if start_reactor: # if an error happens in the connect(), we save the underlying # exception so that after the event-loop exits we can re-raise # it to the caller. class ErrorCollector(object): exception = None def __call__(self, failure): self.exception = failure.value reactor.stop() connect_error = ErrorCollector() d.addErrback(connect_error) # now enter the Twisted reactor loop reactor.run() # if we exited due to a connection error, raise that to the # caller if connect_error.exception: raise connect_error.exception else: # let the caller handle any errors return d
def test_minimal(self): embedded_components, client_components = [], [ Case2_Backend, Case2_Frontend ] ## create a WAMP router factory ## router_factory = RouterFactory() ## create a WAMP router session factory ## session_factory = RouterSessionFactory(router_factory) ## .. and create and add an WAMP application session to ## run next to the router ## config = types.ComponentConfig(realm=self.realm, extra={'caselog': 'case1.log'}) try: log = io.open('caselog.log', 'w') except Exception as e: print(e) return # log = io.open(config.extra['caselog'], 'w') config.log = log config.dlog = [] config.components = [] config.all_done = [] for C in embedded_components: one_done = Deferred() config.all_done.append(one_done) c = C(config, one_done) config.components.append(c) session_factory.add(c) if self.transport == "websocket": ## create a WAMP-over-WebSocket transport server factory ## transport_factory = WampWebSocketServerFactory( session_factory, debug_wamp=self.debug) transport_factory.setProtocolOptions(failByDrop=False, openHandshakeTimeout=0, closeHandshakeTimeout=0) elif self.transport in ['rawsocket-json', 'rawsocket-msgpack']: ## create a WAMP-over-RawSocket transport server factory ## if self.transport == 'rawsocket-msgpack': serializer = MsgPackSerializer() elif self.transport == 'rawsocket-json': serializer = JsonSerializer() else: raise Exception("should not arrive here") transport_factory = WampRawSocketServerFactory( session_factory, serializer, debug=self.debug) else: raise Exception("should not arrive here") ## start the server from an endpoint ## from twisted.internet import reactor server = serverFromString(reactor, self.server) d = server.listen(transport_factory) def onlisten(port): config.port = port d.addCallback(onlisten) clients = [] clients_d = [] for C in client_components: ## create a WAMP application session factory ## session_factory = ApplicationSessionFactory(config) one_done = Deferred() config.all_done.append(one_done) def make_make(Klass, done): def make(config): c = Klass(config, done) config.components.append(c) return c return make ## .. and set the session class on the factory ## session_factory.session = make_make(C, one_done) if self.transport == "websocket": serializers = [JsonSerializer()] ## create a WAMP-over-WebSocket transport client factory ## transport_factory = WampWebSocketClientFactory( session_factory, serializers=serializers, url=self.url, debug_wamp=self.debug) if True: def maker(Klass): class TestClientProtocol( WampWebSocketClientProtocol): def onOpen(self): self.txcnt = 0 self.rxcnt = 0 WampWebSocketClientProtocol.onOpen(self) def sendMessage(self, bytes, isBinary): self.txcnt += 1 print("> : {:>3} : {:<20} : {}".format( self.txcnt, Klass.__name__, bytes)) WampWebSocketClientProtocol.sendMessage( self, bytes, isBinary) def onMessage(self, bytes, isBinary): self.rxcnt += 1 print("< : {:>3} : {:<20} : {}".format( self.rxcnt, Klass.__name__, bytes)) WampWebSocketClientProtocol.onMessage( self, bytes, isBinary) return TestClientProtocol transport_factory.protocol = maker(C) else: transport_factory.protocol = WampWebSocketClientProtocol transport_factory.setProtocolOptions( failByDrop=False, openHandshakeTimeout=0, closeHandshakeTimeout=0) elif self.transport in ['rawsocket-json', 'rawsocket-msgpack']: ## create a WAMP-over-RawSocket transport client factory ## if self.transport == 'rawsocket-msgpack': serializer = MsgPackSerializer() elif self.transport == 'rawsocket-json': serializer = JsonSerializer() else: raise Exception("should not arrive here") transport_factory = WampRawSocketClientFactory( session_factory, serializer, debug=self.debug) ## start the client from an endpoint ## cl = clientFromString(reactor, self.client) clients_d.append(cl.connect(transport_factory)) clients.append(cl) config.connected_clients = None def client_connected(res): config.connected_clients = [ proto for success, proto in res if success ] DeferredList(clients_d).addCallback(client_connected) d = DeferredList(config.all_done, consumeErrors=True) #d = config.components[1]._done def done(res): log.flush() log.close() if config.port: config.port.stopListening() if config.connected_clients: for proto in config.connected_clients: proto.transport.abortConnection() print("Log length: {}".format(len(config.dlog))) print(config.dlog) #from twisted.internet import reactor #reactor.callLater(1, reactor.stop) def error(err): print(err) d.addCallbacks(done, error) # d2 = Deferred() return d
def run(self, make, start_reactor=True, auto_reconnect=False, log_level='info', endpoint=None, reactor=None): """ Run the application component. :param make: A factory that produces instances of :class:`autobahn.twisted.wamp.ApplicationSession` when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`. :type make: callable :param start_reactor: When ``True`` (the default) this method starts the Twisted reactor and doesn't return until the reactor stops. If there are any problems starting the reactor or connect()-ing, we stop the reactor and raise the exception back to the caller. :returns: None is returned, unless you specify ``start_reactor=False`` in which case the Deferred that connect() returns is returned; this will callback() with an IProtocol instance, which will actually be an instance of :class:`WampWebSocketClientProtocol` """ self.log.debug('{klass}.run()', klass=self.__class__.__name__) if start_reactor: # only select framework, set loop and start logging when we are asked # start the reactor - otherwise we are running in a program that likely # already tool care of all this. from twisted.internet import reactor txaio.use_twisted() txaio.config.loop = reactor txaio.start_logging(level=log_level) if callable(make): # factory for use ApplicationSession def create(): cfg = ComponentConfig(self.realm, self.extra, runner=self) try: session = make(cfg) except Exception: self.log.failure('ApplicationSession could not be instantiated: {log_failure.value}') if start_reactor and reactor.running: reactor.stop() raise else: return session else: create = make if self.url.startswith(u'rs'): # try to parse RawSocket URL .. isSecure, host, port = parse_rs_url(self.url) # use the first configured serializer if any (which means, auto-choose "best") serializer = self.serializers[0] if self.serializers else None # create a WAMP-over-RawSocket transport client factory transport_factory = WampRawSocketClientFactory(create, serializer=serializer) else: # try to parse WebSocket URL .. isSecure, host, port, resource, path, params = parse_ws_url(self.url) # create a WAMP-over-WebSocket transport client factory transport_factory = WampWebSocketClientFactory(create, url=self.url, serializers=self.serializers, proxy=self.proxy, headers=self.headers) # client WebSocket settings - similar to: # - http://crossbar.io/docs/WebSocket-Compression/#production-settings # - http://crossbar.io/docs/WebSocket-Options/#production-settings # The permessage-deflate extensions offered to the server .. offers = [PerMessageDeflateOffer()] # Function to accept permessage_delate responses from the server .. def accept(response): if isinstance(response, PerMessageDeflateResponse): return PerMessageDeflateResponseAccept(response) # set WebSocket options for all client connections transport_factory.setProtocolOptions(maxFramePayloadSize=1048576, maxMessagePayloadSize=1048576, autoFragmentSize=65536, failByDrop=False, openHandshakeTimeout=2.5, closeHandshakeTimeout=1., tcpNoDelay=True, autoPingInterval=10., autoPingTimeout=5., autoPingSize=4, perMessageCompressionOffers=offers, perMessageCompressionAccept=accept) # supress pointless log noise transport_factory.noisy = False if endpoint: client = endpoint else: # if user passed ssl= but isn't using isSecure, we'll never # use the ssl argument which makes no sense. context_factory = None if self.ssl is not None: if not isSecure: raise RuntimeError( 'ssl= argument value passed to %s conflicts with the "ws:" ' 'prefix of the url argument. Did you mean to use "wss:"?' % self.__class__.__name__) context_factory = self.ssl elif isSecure: from twisted.internet.ssl import optionsForClientTLS context_factory = optionsForClientTLS(host) from twisted.internet import reactor if self.proxy is not None: from twisted.internet.endpoints import TCP4ClientEndpoint client = TCP4ClientEndpoint(reactor, self.proxy['host'], self.proxy['port']) transport_factory.contextFactory = context_factory elif isSecure: from twisted.internet.endpoints import SSL4ClientEndpoint assert context_factory is not None client = SSL4ClientEndpoint(reactor, host, port, context_factory) else: from twisted.internet.endpoints import TCP4ClientEndpoint client = TCP4ClientEndpoint(reactor, host, port) # as the reactor shuts down, we wish to wait until we've sent # out our "Goodbye" message; leave() returns a Deferred that # fires when the transport gets to STATE_CLOSED def cleanup(proto): if hasattr(proto, '_session') and proto._session is not None: if proto._session.is_attached(): return proto._session.leave() elif proto._session.is_connected(): return proto._session.disconnect() # when our proto was created and connected, make sure it's cleaned # up properly later on when the reactor shuts down for whatever reason def init_proto(proto): self._connect_successes += 1 reactor.addSystemEventTrigger('before', 'shutdown', cleanup, proto) return proto use_service = False if auto_reconnect: try: # since Twisted 16.1.0 from twisted.application.internet import ClientService from twisted.application.internet import backoffPolicy use_service = True except ImportError: use_service = False if use_service: # this code path is automatically reconnecting .. self.log.debug('using t.a.i.ClientService') if self.max_retries or self.initial_retry_delay or self.max_retry_delay or self.retry_delay_growth or self.retry_delay_jitter: kwargs = {} for key, val in [('initialDelay', self.initial_retry_delay), ('maxDelay', self.max_retry_delay), ('factor', self.retry_delay_growth), ('jitter', lambda: random.random() * self.retry_delay_jitter)]: if val: kwargs[key] = val # retry policy that will only try to reconnect if we connected # successfully at least once before (so it fails on host unreachable etc ..) def retry(failed_attempts): if self._connect_successes > 0 and (self.max_retries == -1 or failed_attempts < self.max_retries): return backoffPolicy(**kwargs)(failed_attempts) else: print('hit stop') self.stop() return 100000000000000 else: retry = backoffPolicy() self._client_service = ClientService(client, transport_factory, retryPolicy=retry) self._client_service.startService() d = self._client_service.whenConnected() else: # this code path is only connecting once! self.log.debug('using t.i.e.connect()') d = client.connect(transport_factory) # if we connect successfully, the arg is a WampWebSocketClientProtocol d.addCallback(init_proto) # if the user didn't ask us to start the reactor, then they # get to deal with any connect errors themselves. if start_reactor: # if an error happens in the connect(), we save the underlying # exception so that after the event-loop exits we can re-raise # it to the caller. class ErrorCollector(object): exception = None def __call__(self, failure): self.exception = failure.value reactor.stop() connect_error = ErrorCollector() d.addErrback(connect_error) # now enter the Twisted reactor loop reactor.run() # if we exited due to a connection error, raise that to the # caller if connect_error.exception: raise connect_error.exception else: # let the caller handle any errors return d
elif args.transport in ['rawsocket-json', 'rawsocket-msgpack']: ## create a WAMP-over-RawSocket transport client factory ## if args.transport == 'rawsocket-msgpack': from autobahn.wamp.serializer import MsgPackSerializer serializer = MsgPackSerializer() elif args.transport == 'rawsocket-json': from autobahn.wamp.serializer import JsonSerializer serializer = JsonSerializer() else: raise Exception("should not arrive here") from autobahn.twisted.rawsocket import WampRawSocketClientFactory transport_factory = WampRawSocketClientFactory(session_factory, serializer, debug=args.debug) ## start the client from an endpoint ## cl = clientFromString(reactor, args.client) clients_d.append(cl.connect(transport_factory)) clients.append(cl) d = DeferredList(config.all_done) #d = config.components[1]._done def done(res):
from autobahn.wamp.router import RouterFactory router_factory = RouterFactory() ## create a WAMP router session factory ## from autobahn.twisted.wamp import RouterSessionFactory session_factory = RouterSessionFactory(router_factory) session_factory.add(TestService()) from autobahn.wamp.serializer import MsgPackSerializer serializer = MsgPackSerializer() from autobahn.twisted.rawsocket import WampRawSocketClientFactory, WampRawSocketClientProtocol transport_factory = WampRawSocketClientFactory(session_factory, serializer, debug = args.debug) class WorkerClientProtocol(WampRawSocketClientProtocol): def connectionMade(self): WampRawSocketClientProtocol.connectionMade(self) def connectionLost(self, reason): print "X"*10 WampRawSocketClientProtocol.connectionLost(self, reason) reactor.stop() transport_factory.protocol = WorkerClientProtocol
def run(self, make, start_reactor: bool = True, auto_reconnect: bool = False, log_level: str = 'info', endpoint: Optional[IStreamClientEndpoint] = None, reactor: Optional[IReactorCore] = None ) -> Union[type(None), Deferred]: """ Run the application component. :param make: A factory that produces instances of :class:`autobahn.twisted.wamp.ApplicationSession` when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`. :param start_reactor: When ``True`` (the default) this method starts the Twisted reactor and doesn't return until the reactor stops. If there are any problems starting the reactor or connect()-ing, we stop the reactor and raise the exception back to the caller. :param auto_reconnect: :param log_level: :param endpoint: :param reactor: :return: None is returned, unless you specify ``start_reactor=False`` in which case the Deferred that connect() returns is returned; this will callback() with an IProtocol instance, which will actually be an instance of :class:`WampWebSocketClientProtocol` """ self.log.debug('{klass}.run()', klass=self.__class__.__name__) if start_reactor: # only select framework, set loop and start logging when we are asked # start the reactor - otherwise we are running in a program that likely # already tool care of all this. from twisted.internet import reactor txaio.use_twisted() txaio.config.loop = reactor txaio.start_logging(level=log_level) if callable(make): # factory for use ApplicationSession def create(): cfg = ComponentConfig(self.realm, self.extra, runner=self) try: session = make(cfg) except Exception: self.log.failure( 'ApplicationSession could not be instantiated: {log_failure.value}' ) if start_reactor and reactor.running: reactor.stop() raise else: return session else: create = make if self.url.startswith('rs'): # try to parse RawSocket URL isSecure, host, port = parse_rs_url(self.url) # use the first configured serializer if any (which means, auto-choose "best") serializer = self.serializers[0] if self.serializers else None # create a WAMP-over-RawSocket transport client factory transport_factory = WampRawSocketClientFactory( create, serializer=serializer) else: # try to parse WebSocket URL isSecure, host, port, resource, path, params = parse_ws_url( self.url) # create a WAMP-over-WebSocket transport client factory transport_factory = WampWebSocketClientFactory( create, url=self.url, serializers=self.serializers, proxy=self.proxy, headers=self.headers) # client WebSocket settings - similar to: # - http://crossbar.io/docs/WebSocket-Compression/#production-settings # - http://crossbar.io/docs/WebSocket-Options/#production-settings # The permessage-deflate extensions offered to the server offers = [PerMessageDeflateOffer()] # Function to accept permessage-deflate responses from the server def accept(response): if isinstance(response, PerMessageDeflateResponse): return PerMessageDeflateResponseAccept(response) # default WebSocket options for all client connections protocol_options = { 'version': WebSocketProtocol.DEFAULT_SPEC_VERSION, 'utf8validateIncoming': True, 'acceptMaskedServerFrames': False, 'maskClientFrames': True, 'applyMask': True, 'maxFramePayloadSize': 1048576, 'maxMessagePayloadSize': 1048576, 'autoFragmentSize': 65536, 'failByDrop': True, 'echoCloseCodeReason': False, 'serverConnectionDropTimeout': 1., 'openHandshakeTimeout': 2.5, 'closeHandshakeTimeout': 1., 'tcpNoDelay': True, 'perMessageCompressionOffers': offers, 'perMessageCompressionAccept': accept, 'autoPingInterval': 10., 'autoPingTimeout': 5., 'autoPingSize': 12, # see: https://github.com/crossbario/autobahn-python/issues/1327 and # _cancelAutoPingTimeoutCall 'autoPingRestartOnAnyTraffic': True, } # let user override above default options if self.websocket_options: protocol_options.update(self.websocket_options) # set websocket protocol options on Autobahn/Twisted protocol factory, from where it will # be applied for every Autobahn/Twisted protocol instance from the factory transport_factory.setProtocolOptions(**protocol_options) # supress pointless log noise transport_factory.noisy = False if endpoint: client = endpoint else: # if user passed ssl= but isn't using isSecure, we'll never # use the ssl argument which makes no sense. context_factory = None if self.ssl is not None: if not isSecure: raise RuntimeError( 'ssl= argument value passed to %s conflicts with the "ws:" ' 'prefix of the url argument. Did you mean to use "wss:"?' % self.__class__.__name__) context_factory = self.ssl elif isSecure: from twisted.internet.ssl import optionsForClientTLS context_factory = optionsForClientTLS(host) from twisted.internet import reactor if self.proxy is not None: from twisted.internet.endpoints import TCP4ClientEndpoint client = TCP4ClientEndpoint(reactor, self.proxy['host'], self.proxy['port']) transport_factory.contextFactory = context_factory elif isSecure: from twisted.internet.endpoints import SSL4ClientEndpoint assert context_factory is not None client = SSL4ClientEndpoint(reactor, host, port, context_factory) else: from twisted.internet.endpoints import TCP4ClientEndpoint client = TCP4ClientEndpoint(reactor, host, port) # as the reactor shuts down, we wish to wait until we've sent # out our "Goodbye" message; leave() returns a Deferred that # fires when the transport gets to STATE_CLOSED def cleanup(proto): if hasattr(proto, '_session') and proto._session is not None: if proto._session.is_attached(): return proto._session.leave() elif proto._session.is_connected(): return proto._session.disconnect() # when our proto was created and connected, make sure it's cleaned # up properly later on when the reactor shuts down for whatever reason def init_proto(proto): self._connect_successes += 1 reactor.addSystemEventTrigger('before', 'shutdown', cleanup, proto) return proto use_service = False if auto_reconnect: try: # since Twisted 16.1.0 from twisted.application.internet import ClientService from twisted.application.internet import backoffPolicy use_service = True except ImportError: use_service = False if use_service: # this code path is automatically reconnecting .. self.log.debug('using t.a.i.ClientService') if (self.max_retries is not None or self.initial_retry_delay is not None or self.max_retry_delay is not None or self.retry_delay_growth is not None or self.retry_delay_jitter is not None): if self.max_retry_delay > 0: kwargs = {} def _jitter(): j = 1 if self.retry_delay_jitter is None else self.retry_delay_jitter return random.random() * j for key, val in [('initialDelay', self.initial_retry_delay), ('maxDelay', self.max_retry_delay), ('factor', self.retry_delay_growth), ('jitter', _jitter)]: if val is not None: kwargs[key] = val # retry policy that will only try to reconnect if we connected # successfully at least once before (so it fails on host unreachable etc ..) def retry(failed_attempts): if self._connect_successes > 0 and ( self.max_retries == -1 or failed_attempts < self.max_retries): return backoffPolicy(**kwargs)(failed_attempts) else: print('hit stop') self.stop() return 100000000000000 else: # immediately reconnect (zero delay) def retry(_): return 0 else: retry = backoffPolicy() # https://twistedmatrix.com/documents/current/api/twisted.application.internet.ClientService.html self._client_service = ClientService(client, transport_factory, retryPolicy=retry) self._client_service.startService() d = self._client_service.whenConnected() else: # this code path is only connecting once! self.log.debug('using t.i.e.connect()') d = client.connect(transport_factory) # if we connect successfully, the arg is a WampWebSocketClientProtocol d.addCallback(init_proto) # if the user didn't ask us to start the reactor, then they # get to deal with any connect errors themselves. if start_reactor: # if an error happens in the connect(), we save the underlying # exception so that after the event-loop exits we can re-raise # it to the caller. class ErrorCollector(object): exception = None def __call__(self, failure): self.exception = failure.value reactor.stop() connect_error = ErrorCollector() d.addErrback(connect_error) # now enter the Twisted reactor loop reactor.run() # if the ApplicationSession sets an "error" key on the self.config.extra dictionary, which # has been set to the self.extra dictionary, extract the Exception from that and re-raise # it as the very last one (see below) exciting back to the caller of self.run() app_error = self.extra.get('error', None) # if we exited due to a connection error, raise that to the caller if connect_error.exception: raise connect_error.exception elif app_error: raise app_error else: # let the caller handle any errors return d