def connect_error(fail): if isinstance(fail.value, asyncio.CancelledError): reconnect[0] = False txaio.reject(done_f, fail) return self.log.debug(u'component failed: {error}', error=txaio.failure_message(fail)) self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail)) # If this is a "fatal error" that will never work, # we bail out now if isinstance(fail.value, ApplicationError): if fail.value.error in [ u'wamp.error.no_such_realm' ]: reconnect[0] = False self.log.error( u"Fatal error, not reconnecting") txaio.reject(done_f, fail) return self.log.error(u"{msg}", msg=fail.value.error_message()) return one_reconnect_loop(None) elif isinstance(fail.value, OSError): # failed to connect entirely, like nobody # listening etc. self.log.info(u"Connection failed: {msg}", msg=txaio.failure_message(fail)) return one_reconnect_loop(None) elif _is_ssl_error(fail.value): # Quoting pyOpenSSL docs: "Whenever # [SSL.Error] is raised directly, it has a # list of error messages from the OpenSSL # error queue, where each item is a tuple # (lib, function, reason). Here lib, function # and reason are all strings, describing where # and what the problem is. See err(3) for more # information." self.log.error(u"TLS failure: {reason}", reason=fail.value.args[1]) self.log.error(u"Marking this transport as failed") transport.failed() else: self.log.error( u'Connection failed: {error}', error=txaio.failure_message(fail), ) # some types of errors should probably have # stacktraces logged immediately at error # level, e.g. SyntaxError? self.log.debug( u'{tb}', tb=txaio.failure_format_traceback(fail)) return one_reconnect_loop(None)
def connect_error(fail): if isinstance(fail.value, asyncio.CancelledError): reconnect[0] = False txaio.reject(done_f, fail) return self.log.debug(u'component failed: {error}', error=txaio.failure_message(fail)) self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail)) # If this is a "fatal error" that will never work, # we bail out now if isinstance(fail.value, ApplicationError): if fail.value.error in [u'wamp.error.no_such_realm']: reconnect[0] = False self.log.error(u"Fatal error, not reconnecting") txaio.reject(done_f, fail) return self.log.error(u"{msg}", msg=fail.value.error_message()) return one_reconnect_loop(None) elif isinstance(fail.value, OSError): # failed to connect entirely, like nobody # listening etc. self.log.info(u"Connection failed: {msg}", msg=txaio.failure_message(fail)) return one_reconnect_loop(None) elif _is_ssl_error(fail.value): # Quoting pyOpenSSL docs: "Whenever # [SSL.Error] is raised directly, it has a # list of error messages from the OpenSSL # error queue, where each item is a tuple # (lib, function, reason). Here lib, function # and reason are all strings, describing where # and what the problem is. See err(3) for more # information." self.log.error(u"TLS failure: {reason}", reason=fail.value.args[1]) self.log.error(u"Marking this transport as failed") transport.failed() else: self.log.error( u'Connection failed: {error}', error=txaio.failure_message(fail), ) # some types of errors should probably have # stacktraces logged immediately at error # level, e.g. SyntaxError? self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail)) return one_reconnect_loop(None)
def error(err): # errmsg = 'Failure while invoking procedure {0} registered under "{1}: {2}".'.format(endpoint.fn, registration.procedure, err) errmsg = u"{0}".format(err.value.args[0]) try: self.onUserError(err, errmsg) except: pass formatted_tb = None if self.traceback_app: formatted_tb = txaio.failure_format_traceback(err) del self._invocations[msg.request] if hasattr(err, 'value'): exc = err.value else: exc = err reply = self._message_from_exception(message.Invocation.MESSAGE_TYPE, msg.request, exc, formatted_tb) try: self._transport.send(reply) except SerializationError as e: # the application-level payload returned from the invoked procedure can't be serialized reply = message.Error(message.Invocation.MESSAGE_TYPE, msg.request, ApplicationError.INVALID_PAYLOAD, args=[u'error return value from invoked procedure "{0}" could not be serialized: {1}'.format(registration.procedure, e)]) self._transport.send(reply) # we have handled the error, so we eat it return None
def test_errback(framework): f = txaio.create_future() exception = RuntimeError("it failed") errors = [] def err(f): errors.append(f) txaio.add_callbacks(f, None, err) try: raise exception except: fail = txaio.create_failure() txaio.reject(f, fail) run_once() assert len(errors) == 1 assert isinstance(errors[0], txaio.IFailedFuture) assert exception == errors[0].value assert txaio.failure_traceback(errors[0]) is not None tb = txaio.failure_format_traceback(errors[0]) assert 'RuntimeError' in tb assert 'it failed' in tb assert txaio.failure_message(errors[0]) == 'RuntimeError: it failed' assert 'it failed' in str(errors[0])
def test_errback_without_except(framework): ''' Create a failure without an except block ''' f = txaio.create_future() exception = RuntimeError("it failed") errors = [] def err(f): errors.append(f) txaio.add_callbacks(f, None, err) fail = txaio.create_failure(exception) txaio.reject(f, fail) run_once() assert len(errors) == 1 assert isinstance(errors[0], txaio.IFailedFuture) tb = txaio.failure_format_traceback(errors[0]) assert 'RuntimeError' in tb assert 'it failed' in tb assert txaio.failure_message(errors[0]) == 'RuntimeError: it failed' assert 'it failed' in str(errors[0])
def test_errback_cancel_exception(framework): ''' reject a future with a CancelledError ''' f = txaio.create_future() errors = [] def err(f): errors.append(f) txaio.add_callbacks(f, None, err) txaio.cancel(f, msg="future cancelled") run_once() assert len(errors) == 1 assert isinstance(errors[0], txaio.IFailedFuture) tb = txaio.failure_format_traceback(errors[0]) assert 'CancelledError' in tb if txaio.using_asyncio and sys.version_info >= (3, 9): assert txaio.failure_message( errors[0]) == 'CancelledError: future cancelled' assert 'future cancelled' in str(errors[0]) else: assert txaio.failure_message(errors[0]) == 'CancelledError: '
def component_failure(comp, f): log.error("Component '{c}' error: {msg}", c=comp, msg=txaio.failure_message(f)) log.debug("Component error: {tb}", tb=txaio.failure_format_traceback(f)) # double-check: is a component-failure still fatal to the # startup process (because we passed consume_exception=False # to gather() below?) return None
def on_startup_error(err): term_print('CROSSBAR:NODE_STARTUP_FAILED') exit_info['was_clean'] = False log.error("Could not start node: {tb}", tb=failure_format_traceback(err)) if reactor.running: reactor.stop()
def send(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransport.send` """ if self.isOpen(): try: self.log.debug( "WampLongPoll: TX {octets}", octets=_LazyHexFormatter(msg), ) payload, isBinary = self._serializer.serialize(msg) except Exception as e: # all exceptions raised from above should be serialization errors .. f = create_failure() self.log.error( "Unable to serialize WAMP application payload: {msg}", msg=failure_message(f), ) self.log.debug("{tb}", tb=failure_format_traceback(f)) raise SerializationError( "unable to serialize WAMP application payload ({0})". format(e)) else: self._receive.queue(payload) else: raise TransportLost()
def error(err): errmsg = txaio.failure_message(err) try: self.onUserError(err, errmsg) except: pass formatted_tb = None if self.traceback_app: formatted_tb = txaio.failure_format_traceback( err) del self._invocations[msg.request] reply = self._message_from_exception( message.Invocation.MESSAGE_TYPE, msg.request, err.value, formatted_tb, ) try: self._transport.send(reply) except SerializationError as e: # the application-level payload returned from the invoked procedure can't be serialized reply = message.Error( message.Invocation.MESSAGE_TYPE, msg.request, ApplicationError.INVALID_PAYLOAD, args=[ u'error return value from invoked procedure "{0}" could not be serialized: {1}' .format(registration.procedure, e) ]) self._transport.send(reply) # we have handled the error, so we eat it return None
def error(err): errmsg = txaio.failure_message(err) try: self.onUserError(err, errmsg) except: pass formatted_tb = None if self.traceback_app: formatted_tb = txaio.failure_format_traceback(err) del self._invocations[msg.request] reply = self._message_from_exception( message.Invocation.MESSAGE_TYPE, msg.request, err.value, formatted_tb, ) try: self._transport.send(reply) except SerializationError as e: # the application-level payload returned from the invoked procedure can't be serialized reply = message.Error(message.Invocation.MESSAGE_TYPE, msg.request, ApplicationError.INVALID_PAYLOAD, args=[u'error return value from invoked procedure "{0}" could not be serialized: {1}'.format(registration.procedure, e)]) self._transport.send(reply) # we have handled the error, so we eat it return None
def test_errback_reject_no_args(): """ txaio.reject() with no args """ f = txaio.create_future() exception = RuntimeError("it failed") errors = [] def err(f): errors.append(f) txaio.add_callbacks(f, None, err) try: raise exception except: txaio.reject(f) run_once() assert len(errors) == 1 assert isinstance(errors[0], txaio.IFailedFuture) assert exception == errors[0].value tb = txaio.failure_format_traceback(errors[0]) assert 'RuntimeError' in tb assert 'it failed' in tb assert txaio.failure_message(errors[0]) == 'RuntimeError: it failed' assert 'it failed' in str(errors[0])
def render_POST(self, request): """ A client sends a message via WAMP-over-Longpoll by HTTP/POSTing to this Web resource. The body of the POST should contain a batch of WAMP messages which are serialized according to the selected serializer, and delimited by a single ``\0`` byte in between two WAMP messages in the batch. """ payload = request.content.read() self.log.debug( "WampLongPoll: receiving data for transport '{tid}'\n{octets}", tid=self._parent._transport_id, octets=_LazyHexFormatter(payload), ) try: # process (batch of) WAMP message(s) self._parent.onMessage(payload, None) except Exception: f = create_failure() self.log.error( "Could not unserialize WAMP message: {msg}", msg=failure_message(f), ) self.log.debug("{tb}", tb=failure_format_traceback(f)) return self._parent._parent._fail_request( request, b"could not unserialize WAMP message.") else: request.setResponseCode(http.NO_CONTENT) self._parent._parent._set_standard_headers(request) self._parent._isalive = True return b""
def handle_connect_error(fail): unrecoverable_error = False # FIXME - make txaio friendly # Can connect_f ever be in a cancelled state? # if txaio.using_asyncio and isinstance(fail.value, asyncio.CancelledError): # unrecoverable_error = True self.log.debug(u'component failed: {error}', error=txaio.failure_message(fail)) self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail)) # If this is a "fatal error" that will never work, # we bail out now if isinstance(fail.value, ApplicationError): if fail.value.error in [ u'wamp.error.no_such_realm', u'wamp.error.no_auth_method' ]: unrecoverable_error = True self.log.error(u"Fatal error, not reconnecting") self.log.error(u"{msg}", msg=fail.value.error_message()) elif isinstance(fail.value, OSError): # failed to connect entirely, like nobody # listening etc. self.log.info(u"Connection failed: {msg}", msg=txaio.failure_message(fail)) elif self._is_ssl_error(fail.value): # Quoting pyOpenSSL docs: "Whenever # [SSL.Error] is raised directly, it has a # list of error messages from the OpenSSL # error queue, where each item is a tuple # (lib, function, reason). Here lib, function # and reason are all strings, describing where # and what the problem is. See err(3) for more # information." self.log.error(u"TLS failure: {reason}", reason=fail.value.args[1]) self.log.error(u"Marking this transport as failed") transport_candidate[0].failed() else: # This is some unknown failure, e.g. could # be SyntaxError etc so we're aborting the # whole mission self.log.error( u'Connection failed: {error}', error=txaio.failure_message(fail), ) unrecoverable_error = True if unrecoverable_error: txaio.reject(done_f, fail) return txaio.call_later(0, transport_check, None) return
def error(fail): self._delay_f = None if self._stopping: # might be better to add framework-specific checks in # subclasses to see if this is CancelledError (for # Twisted) and whatever asyncio does .. but tracking # if we're in the shutdown path is fine too txaio.resolve(self._done_f, None) else: self.log.info("Internal error {msg}", msg=txaio.failure_message(fail)) self.log.debug("{tb}", tb=txaio.failure_format_traceback(fail)) txaio.reject(self._done_f, fail)
def handle_connect_error(fail): # FIXME - make txaio friendly # Can connect_f ever be in a cancelled state? # if txaio.using_asyncio and isinstance(fail.value, asyncio.CancelledError): # unrecoverable_error = True self.log.debug(u'component failed: {error}', error=txaio.failure_message(fail)) self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail)) # If this is a "fatal error" that will never work, # we bail out now if isinstance(fail.value, ApplicationError): self.log.error(u"{msg}", msg=fail.value.error_message()) elif isinstance(fail.value, OSError): # failed to connect entirely, like nobody # listening etc. self.log.info(u"Connection failed: {msg}", msg=txaio.failure_message(fail)) elif self._is_ssl_error(fail.value): # Quoting pyOpenSSL docs: "Whenever # [SSL.Error] is raised directly, it has a # list of error messages from the OpenSSL # error queue, where each item is a tuple # (lib, function, reason). Here lib, function # and reason are all strings, describing where # and what the problem is. See err(3) for more # information." # (and 'args' is a 1-tuple containing the above # 3-tuple...) ssl_lib, ssl_func, ssl_reason = fail.value.args[0][0] self.log.error(u"TLS failure: {reason}", reason=ssl_reason) else: self.log.error( u'Connection failed: {error}', error=txaio.failure_message(fail), ) if self._is_fatal is None: is_fatal = False else: is_fatal = self._is_fatal(fail.value) if is_fatal: self.log.info("Error was fatal; failing transport") transport_candidate[0].failed() txaio.call_later(0, transport_check, None) return
def onUserError(self, fail, msg): """ Implements :func:`autobahn.wamp.interfaces.ISession.onUserError` """ if isinstance(fail.value, ApplicationError): self.log.debug('{klass}.onUserError(): "{msg}"', klass=self.__class__.__name__, msg=fail.value.error_message()) else: self.log.error( '{klass}.onUserError(): "{msg}"\n{traceback}', klass=self.__class__.__name__, msg=msg, traceback=txaio.failure_format_traceback(fail), )
def onUserError(self, fail, msg): """ This is called when we try to fire a callback, but get an exception from user code -- for example, a registered publish callback or a registered method. By default, this prints the current stack-trace and then error-message to stdout. ApplicationSession-derived objects may override this to provide logging if they prefer. The Twisted implemention does this. (See :class:`autobahn.twisted.wamp.ApplicationSession`) :param fail: instance implementing txaio.IFailedFuture :param msg: an informative message from the library. It is suggested you log this immediately after the exception. """ if isinstance(fail.value, exception.ApplicationError): self.log.error(fail.value.error_message()) else: self.log.error(u"{msg}: {traceback}", msg=msg, traceback=txaio.failure_format_traceback(fail))
def test_errback_plain_exception(framework): ''' reject a future with just an Exception ''' f = txaio.create_future() exception = RuntimeError("it failed") errors = [] def err(f): errors.append(f) txaio.add_callbacks(f, None, err) txaio.reject(f, exception) run_once() assert len(errors) == 1 assert isinstance(errors[0], txaio.IFailedFuture) tb = txaio.failure_format_traceback(errors[0]) assert 'RuntimeError' in tb assert 'it failed' in tb assert txaio.failure_message(errors[0]) == 'RuntimeError: it failed' assert 'it failed' in str(errors[0])
def test_errback_without_except(): ''' Create a failure without an except block ''' f = txaio.create_future() exception = RuntimeError("it failed") errors = [] def err(f): errors.append(f) txaio.add_callbacks(f, None, err) fail = txaio.create_failure(exception) txaio.reject(f, fail) run_once() assert len(errors) == 1 assert isinstance(errors[0], txaio.IFailedFuture) tb = txaio.failure_format_traceback(errors[0]) assert 'RuntimeError' in tb assert 'it failed' in tb assert txaio.failure_message(errors[0]) == 'RuntimeError: it failed' assert 'it failed' in str(errors[0])
def render_POST(self, request): """ A client sends a message via WAMP-over-Longpoll by HTTP/POSTing to this Web resource. The body of the POST should contain a batch of WAMP messages which are serialized according to the selected serializer, and delimited by a single ``\0`` byte in between two WAMP messages in the batch. """ payload = request.content.read() self.log.debug( "WampLongPoll: receiving data for transport '{tid}'\n{octets}", tid=self._parent._transport_id, octets=_LazyHexFormatter(payload), ) try: # process (batch of) WAMP message(s) self._parent.onMessage(payload, None) except Exception: f = create_failure() self.log.error( "Could not unserialize WAMP message: {msg}", msg=failure_message(f), ) self.log.debug("{tb}", tb=failure_format_traceback(f)) return self._parent._parent._fail_request( request, b"could not unserialize WAMP message." ) else: request.setResponseCode(http.NO_CONTENT) self._parent._parent._set_standard_headers(request) self._parent._isalive = True return b""
def onUserError(self, fail, msg): """ This is called when we try to fire a callback, but get an exception from user code -- for example, a registered publish callback or a registered method. By default, this prints the current stack-trace and then error-message to stdout. ApplicationSession-derived objects may override this to provide logging if they prefer. The Twisted implemention does this. (See :class:`autobahn.twisted.wamp.ApplicationSession`) :param fail: instance implementing txaio.IFailedFuture :param msg: an informative message from the library. It is suggested you log this immediately after the exception. """ if isinstance(fail.value, exception.ApplicationError): self.log.error(fail.value.error_message()) else: self.log.error( u'{msg}: {traceback}', msg=msg, traceback=txaio.failure_format_traceback(fail), )
def send(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransport.send` """ if self.isOpen(): try: self.log.debug( "WampLongPoll: TX {octets}", octets=_LazyHexFormatter(msg), ) payload, isBinary = self._serializer.serialize(msg) except Exception as e: # all exceptions raised from above should be serialization errors .. f = create_failure() self.log.error( "Unable to serialize WAMP application payload: {msg}", msg=failure_message(f), ) self.log.debug("{tb}", tb=failure_format_traceback(f)) raise SerializationError("unable to serialize WAMP application payload ({0})".format(e)) else: self._receive.queue(payload) else: raise TransportLost()
def render_POST(self, request): """ Request to create a new WAMP session. """ self.log.debug("WampLongPoll: creating new session ..") payload = request.content.read().decode('utf8') try: options = json.loads(payload) except Exception: f = create_failure() self.log.error( "Couldn't parse WAMP request body: {msg}", msg=failure_message(f), ) self.log.debug("{msg}", msg=failure_format_traceback(f)) return self._parent._fail_request(request, b"could not parse WAMP session open request body") if type(options) != dict: return self._parent._fail_request(request, b"invalid type for WAMP session open request") if u'protocols' not in options: return self._parent._fail_request(request, "missing attribute 'protocols' in WAMP session open request") # determine the protocol to speak # protocol = None serializer = None for p in options[u'protocols']: version, serializerId = parseSubprotocolIdentifier(p) if version == 2 and serializerId in self._parent._serializers.keys(): serializer = self._parent._serializers[serializerId] protocol = p break if protocol is None: return self._fail_request( request, b"no common protocol to speak (I speak: {0})".format( ["wamp.2.{0}".format(s) for s in self._parent._serializers.keys()]) ) # make up new transport ID # if self._parent._debug_transport_id: # use fixed transport ID for debugging purposes transport = self._parent._debug_transport_id else: transport = generate_token(1, 12) # this doesn't contain all the info (when a header key appears multiple times) # http_headers_received = request.getAllHeaders() http_headers_received = {} for key, values in request.requestHeaders.getAllRawHeaders(): if key not in http_headers_received: http_headers_received[key] = [] http_headers_received[key].extend(values) transport_details = { u'transport': transport, u'serializer': serializer, u'protocol': protocol, u'peer': request.getClientIP(), u'http_headers_received': http_headers_received, u'http_headers_sent': None } # create instance of WampLongPollResourceSession or subclass thereof .. # self._parent._transports[transport] = self._parent.protocol(self._parent, transport_details) # create response # self._parent._set_standard_headers(request) request.setHeader(b'content-type', b'application/json; charset=utf-8') result = { u'transport': transport, u'protocol': protocol } self.log.debug( "WampLongPoll: new session created on transport" " '{transport}'".format( transport=transport, ) ) payload = json.dumps(result) return payload.encode()
def start(self, reactor=None): """ This starts the Component, which means it will start connecting (and re-connecting) to its configured transports. A Component runs until it is "done", which means one of: - There was a "main" function defined, and it completed successfully; - Something called ``.leave()`` on our session, and we left successfully; - ``.stop()`` was called, and completed successfully; - none of our transports were able to connect successfully (failure); :returns: a Deferred that fires (with ``None``) when we are "done" or with a Failure if something went wrong. """ if reactor is None: self.log.warn("Using default reactor") from twisted.internet import reactor yield self.fire('start', reactor, self) # transports to try again and again .. transport_gen = itertools.cycle(self._transports) reconnect = True self.log.debug('Entering re-connect loop') while reconnect: # cycle through all transports forever .. transport = next(transport_gen) # only actually try to connect using the transport, # if the transport hasn't reached max. connect count if transport.can_reconnect(): delay = transport.next_delay() self.log.debug( 'trying transport {transport_idx} using connect delay {transport_delay}', transport_idx=transport.idx, transport_delay=delay, ) yield sleep(delay) try: transport.connect_attempts += 1 yield self._connect_once(reactor, transport) transport.connect_sucesses += 1 except Exception as e: transport.connect_failures += 1 f = txaio.create_failure() self.log.error(u'component failed: {error}', error=txaio.failure_message(f)) self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(f)) # If this is a "fatal error" that will never work, # we bail out now if isinstance(e, ApplicationError): if e.error in [u'wamp.error.no_such_realm']: reconnect = False self.log.error(u"Fatal error, not reconnecting") # The thinking here is that we really do # want to 'raise' (and thereby fail the # entire "start" / reconnect loop) because # if the realm isn't valid, we're "never" # going to succeed... raise self.log.error(u"{msg}", msg=e.error_message()) elif _is_ssl_error(e): # Quoting pyOpenSSL docs: "Whenever # [SSL.Error] is raised directly, it has a # list of error messages from the OpenSSL # error queue, where each item is a tuple # (lib, function, reason). Here lib, function # and reason are all strings, describing where # and what the problem is. See err(3) for more # information." for (lib, fn, reason) in e.args[0]: self.log.error(u"TLS failure: {reason}", reason=reason) self.log.error(u"Marking this transport as failed") transport.failed() else: f = txaio.create_failure() self.log.error( u'Connection failed: {error}', error=txaio.failure_message(f), ) # some types of errors should probably have # stacktraces logged immediately at error # level, e.g. SyntaxError? self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(f)) else: self.log.debug(u"Not reconnecting") reconnect = False else: # check if there is any transport left we can use # to connect if not self._can_reconnect(): self.log.info("No remaining transports to try") reconnect = False
def component_failure(f): log.error("Component error: {msg}", msg=txaio.failure_message(f)) log.debug("Component error: {tb}", tb=txaio.failure_format_traceback(f)) return None
def error(fail): self.log.info("Internal error {msg}", msg=txaio.failure_message(fail)) self.log.debug("{tb}", tb=txaio.failure_format_traceback(fail)) txaio.reject(done_f, fail)
def on_shutdown_error(err): exit_info['was_clean'] = False log.error("on_shutdown_error: {tb}", tb=failure_format_traceback(err))
def test_bad_failures(handler, framework): # just ensuring this doesn't explode txaio.failure_format_traceback("not a failure") txaio.failure_message("not a failure")
def start(self, reactor=None): """ This starts the Component, which means it will start connecting (and re-connecting) to its configured transports. A Component runs until it is "done", which means one of: - There was a "main" function defined, and it completed successfully; - Something called ``.leave()`` on our session, and we left successfully; - ``.stop()`` was called, and completed successfully; - none of our transports were able to connect successfully (failure); :returns: a Deferred that fires (with ``None``) when we are "done" or with a Failure if something went wrong. """ if reactor is None: self.log.warn("Using default reactor") from twisted.internet import reactor yield self.fire('start', reactor, self) # transports to try again and again .. transport_gen = itertools.cycle(self._transports) reconnect = True last_failure = None self.log.debug('Entering re-connect loop') while reconnect: # cycle through all transports forever .. transport = next(transport_gen) # only actually try to connect using the transport, # if the transport hasn't reached max. connect count if transport.can_reconnect(): delay = transport.next_delay() self.log.debug( 'trying transport {transport_idx} using connect delay {transport_delay}', transport_idx=transport.idx, transport_delay=delay, ) yield sleep(delay) try: yield self._connect_once(reactor, transport) except Exception as e: f = txaio.create_failure() last_failure = f self.log.error(u'component failed: {error}', error=txaio.failure_message(f)) self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(f)) # If this is a "fatal error" that will never work, # we bail out now if isinstance(e, ApplicationError): if e.error in [u'wamp.error.no_such_realm', u'wamp.error.no_auth_method']: reconnect = False self.log.error(u"Fatal error, not reconnecting") # The thinking here is that we really do # want to 'raise' (and thereby fail the # entire "start" / reconnect loop) because # if the realm isn't valid, we're "never" # going to succeed... raise self.log.error(u"{msg}", msg=e.error_message()) elif _is_ssl_error(e): # Quoting pyOpenSSL docs: "Whenever # [SSL.Error] is raised directly, it has a # list of error messages from the OpenSSL # error queue, where each item is a tuple # (lib, function, reason). Here lib, function # and reason are all strings, describing where # and what the problem is. See err(3) for more # information." for (lib, fn, reason) in e.args[0]: self.log.error(u"TLS failure: {reason}", reason=reason) self.log.error(u"Marking this transport as failed") transport.failed() else: f = txaio.create_failure() self.log.error( u'Connection failed: {error}', error=txaio.failure_message(f), ) # some types of errors should probably have # stacktraces logged immediately at error # level, e.g. SyntaxError? self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(f)) else: self.log.debug(u"Not reconnecting") reconnect = False else: # check if there is any transport left we can use # to connect if not self._can_reconnect(): self.log.info("No remaining transports to try") reconnect = False if last_failure is not None: last_failure.raiseException()
def render_POST(self, request): """ Request to create a new WAMP session. """ self.log.debug("WampLongPoll: creating new session ..") payload = request.content.read().decode('utf8') try: options = json.loads(payload) except Exception: f = create_failure() self.log.error( "Couldn't parse WAMP request body: {msg}", msg=failure_message(f), ) self.log.debug("{msg}", msg=failure_format_traceback(f)) return self._parent._fail_request( request, b"could not parse WAMP session open request body") if not isinstance(options, dict): return self._parent._fail_request( request, b"invalid type for WAMP session open request") if 'protocols' not in options: return self._parent._fail_request( request, "missing attribute 'protocols' in WAMP session open request") # determine the protocol to speak # protocol = None serializer = None for p in options['protocols']: version, serializerId = parseSubprotocolIdentifier(p) if version == 2 and serializerId in self._parent._serializers.keys( ): serializer = self._parent._serializers[serializerId] protocol = p break if protocol is None: return self._fail_request( request, b"no common protocol to speak (I speak: {0})".format([ "wamp.2.{0}".format(s) for s in self._parent._serializers.keys() ])) # make up new transport ID # if self._parent._debug_transport_id: # use fixed transport ID for debugging purposes transport = self._parent._debug_transport_id else: transport = generate_token(1, 12) # this doesn't contain all the info (when a header key appears multiple times) # http_headers_received = request.getAllHeaders() http_headers_received = {} for key, values in request.requestHeaders.getAllRawHeaders(): if key not in http_headers_received: http_headers_received[key] = [] http_headers_received[key].extend(values) transport_details = { 'transport': transport, 'serializer': serializer, 'protocol': protocol, 'peer': request.getClientIP(), 'http_headers_received': http_headers_received, 'http_headers_sent': None } # create instance of WampLongPollResourceSession or subclass thereof .. # self._parent._transports[transport] = self._parent.protocol( self._parent, transport_details) # create response # self._parent._set_standard_headers(request) request.setHeader(b'content-type', b'application/json; charset=utf-8') result = {'transport': transport, 'protocol': protocol} self.log.debug("WampLongPoll: new session created on transport" " '{transport}'".format(transport=transport, )) payload = json.dumps(result) return payload.encode()