def send(self, message: str) -> None: """ Send message across the websocket. :param message: Message as string :raise WebSocketException: When *self._auto_reconnect* is False: If a message cannot be sent and all retries have been exhausted. :raise WebSocketConnectionClosedException: When the websocket is not available at all. """ retries = 0 retry_delay = 0 while True: retry_delay = self._backoff(retry_delay) try: with self._reader_guard: if self._reader_status in [ReaderStatus.NONE]: raise WebSocketConnectionClosedException('Websocket not started.') elif self._reader_status in [ReaderStatus.DONE, ReaderStatus.FAILED]: raise WebSocketConnectionClosedException('Websocket has exited.') elif self._reader_status not in [ReaderStatus.RUNNING, ReaderStatus.RUNNING_PRELIMINARY]: raise WebSocketException('Websocket not ready.') with self._ws_guard: if not self._ws: raise WebSocketConnectionClosedException('Websocket is gone.') if logger.isEnabledFor(logging.DEBUG): logger.debug("Sending message: %s", message) else: logger.info("Sending message of length %d", len(message)) self._ws.send(message) return except WebSocketConnectionClosedException: raise except Exception as err: if retries >= self.MAX_RETRIES: if self._auto_reconnect: retries = 0 logger.warning('Restarting because of error: %s', str(err)) self.restart() else: raise WebSocketException("Could not send and all retries have been exhausted.") else: logger.warning('Retrying to send message because of error: %s', str(err)) retries += 1
def test_gateway_end_when_websocket_exception(ws_mock): ws_mock.recv_data.side_effect = [WebSocketException()] gateway = Gateway(CONNECTION_URL) for data in gateway: pytest.fail("Should receive no data")
async def test_send_key_websocketexception(hass, remote): """Testing unhandled response exception.""" await setup_samsungtv(hass, MOCK_CONFIG) remote.control = Mock(side_effect=WebSocketException("Boom")) assert await hass.services.async_call(DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON
def create_connection(self, *a, **kw): self._report_call('create_connection', *a, **kw) if self.fail: self.connected = False raise WebSocketException() else: self.connected = True return self
def test_gateway_closes_when_connect_errors(ws_mock): ws_mock.connected = False ws_mock.connect.side_effect = [WebSocketException()] gateway = Gateway(CONNECTION_URL) for data in gateway: pytest.fail("No data should be emitted") ws_mock.connect.assert_called_once() ws_mock.recv_data.assert_not_called() assert gateway.close_reason assert WebSocketException.__name__ in gateway.close_reason.reason
def _check_message(self, ws: WebSocketApp, message: str) -> None: """ Look for error 401. Try to reconnect with a new token when this is encountered. Set status to *ReaderStatus.RESTARTING* when a token is no longer valid on error code 401. Set status to *ReaderStatus.FAILED* when error 401 is received and the token was never valid. :param ws: WebSocketApp :param message: Incoming message as string """ try: with self._reader_guard: error_message = ErrorMessage.parse(message) if error_message: if error_message.code == 401: if self._reader_status == ReaderStatus.RUNNING_PRELIMINARY: raise WebSocketException( "Received error message while token was never valid: " + str(error_message)) else: self._api_handler.refresh_token() # If we get here, the token has been refreshed successfully. self._reconnect_delay = 0 self._reader_status = ReaderStatus.RESTARTING logger.info("Refreshing token because of error: %s", str(error_message)) self._close(reason=f"{self._api_handler.user_agent} token refresh.") return else: if logger.isEnabledFor(logging.DEBUG): logger.debug("Received message: %s", message) else: logger.info("Received message of length %d", len(message)) self.on_message(ws, message) with self._reader_guard: # If we get here, the token is valid if self._reader_status not in [ReaderStatus.RUNNING, ReaderStatus.FAILED]: self._reader_status = ReaderStatus.RUNNING self._reconnect_delay = 0 except Exception as err: self._set_error(err) self._close(status=STATUS_UNEXPECTED_CONDITION, reason="Exception while handling incoming text message.")
def run_forever(self): """ run event loop for WebSocket framework. This loop is infinite loop and is alive during websocket is available. """ if self.sock: raise WebSocketException("socket is already opened") try: self.sock = WebSocket() self.sock.connect(self.url) self._run_with_no_err(self.on_open) while True: data = self.sock.recv() if data is None: break self._run_with_no_err(self.on_message, data) except Exception, e: self._run_with_no_err(self.on_error, e)
def start(self) -> None: """ Create the WebSocketApp :raise WebSocketException: When the creation of the WebSocketApp fails. """ try: with self._ws_guard: self._ws = WebSocketApp(self._url, header={ "Sec-WebSocket-Protocol": f"{self._protocol}, token-{self._api_handler.token}" }, on_open=lambda ws: self._check_open(ws), on_close=lambda ws, code, reason: self._check_close(ws, code, reason), on_message=lambda ws, msg: self._check_message(ws, msg), on_error=lambda ws, _err: self._check_error(ws, _err), on_ping=lambda ws, data: ws.send(data, opcode=ABNF.OPCODE_PONG)) except Exception as err: raise WebSocketException("Cannot create WebSocketApp.") from err
def run_forever(self, sockopt=None, sslopt=None, ping_interval=0, ping_timeout=None, ping_payload="", http_proxy_host=None, http_proxy_port=None, http_no_proxy=None, http_proxy_auth=None, skip_utf8_validation=True, host=None, origin=None, dispatcher=None, suppress_origin=False, proxy_type=None): """ run event loop for WebSocket framework. This loop is infinite loop and is alive during websocket is available. sockopt: values for socket.setsockopt. sockopt must be tuple and each element is argument of sock.setsockopt. sslopt: ssl socket optional dict. ping_interval: automatically send "ping" command every specified period(second) if set to 0, not send automatically. ping_timeout: timeout(second) if the pong message is not received. http_proxy_host: http proxy host name. http_proxy_port: http proxy port. If not set, set to 80. http_no_proxy: host names, which doesn't use proxy. skip_utf8_validation: skip utf8 validation. host: update host header. origin: update origin header. dispatcher: customize reading data from socket. suppress_origin: suppress outputting origin header. Returns ------- False if caught KeyboardInterrupt True if other exception was raised during a loop """ if ping_timeout is not None and ping_timeout <= 0: ping_timeout = None if ping_timeout and ping_interval and ping_interval <= ping_timeout: raise WebSocketException("Ensure ping_interval > ping_timeout") if not sockopt: sockopt = [] if not sslopt: sslopt = {} if self.sock: raise WebSocketException("socket is already opened") thread = None self.keep_running = True self.last_ping_tm = 0 self.last_pong_tm = 0 def teardown(close_frame=None): """ Tears down the connection. If close_frame is set, we will invoke the on_close handler with the statusCode and reason from there. """ if thread and thread.isAlive(): event.set() thread.join() self.keep_running = False if self.sock: self.sock.close() close_args = self._get_close_args( close_frame.data if close_frame else None) self._callback(self.on_close, *close_args) self.sock = None try: self.sock = WebSocket( self.get_mask_key, sockopt=sockopt, sslopt=sslopt, fire_cont_frame=self.on_cont_message is not None, skip_utf8_validation=skip_utf8_validation, enable_multithread=True if ping_interval else False, sock_id=self.sock_id ) self.sock.settimeout(getdefaulttimeout()) self.sock.connect( self.url, header=self.header, cookie=self.cookie, http_proxy_host=http_proxy_host, http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy, http_proxy_auth=http_proxy_auth, subprotocols=self.subprotocols, host=host, origin=origin, suppress_origin=suppress_origin, proxy_type=proxy_type) if not dispatcher: dispatcher = self.create_dispatcher(ping_timeout) self._callback(self.on_open) if ping_interval: event = threading.Event() thread = threading.Thread( target=self._send_ping, args=(ping_interval, event, ping_payload)) thread.setDaemon(True) thread.start() def read(): if not self.keep_running: return teardown() op_code, frame = self.sock.recv_data_frame(True) if op_code == ABNF.OPCODE_CLOSE: return teardown(frame) elif op_code == ABNF.OPCODE_PING: self._callback(self.on_ping, frame.data) elif op_code == ABNF.OPCODE_PONG: self.last_pong_tm = time.time() self._callback(self.on_pong, frame.data) elif op_code == ABNF.OPCODE_CONT and self.on_cont_message: self._callback(self.on_data, frame.data, frame.opcode, frame.fin) self._callback(self.on_cont_message, frame.data, frame.fin) else: data = frame.data if six.PY3 and op_code == ABNF.OPCODE_TEXT: data = data.decode("utf-8") self._callback(self.on_data, data, frame.opcode, True) self._callback(self.on_message, data) return True def check(): if (ping_timeout): has_timeout_expired = time.time() - self.last_ping_tm > ping_timeout has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0 has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout if (self.last_ping_tm and has_timeout_expired and (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)): raise WebSocketTimeoutException("ping/pong timed out") return True dispatcher.read(self.sock.sock, read, check) except (Exception, KeyboardInterrupt, SystemExit) as e: self._callback(self.on_error, e) if isinstance(e, SystemExit): # propagate SystemExit further raise teardown() return not isinstance(e, KeyboardInterrupt)