Skip to content

How to get close picows.WSCloseCode(value) #31

Closed as not planned
Closed as not planned

Description

@RiverAi7z

Here is my code, when websocket is closed, I want to get the close code, how can I modify my code to get the code? I read the doc, I can get the close doc by call get_close_code() in WSMsgType.CLOSE, but I never received the close frame from the server. How can i know the reason of each disconnected from server.

class Listener(WSListener):
    def __init__(self, logger, specific_ping_msg=None):
        self._log = logger
        self.msg_queue = asyncio.Queue()
        self._specific_ping_msg = specific_ping_msg

    def send_user_specific_ping(self, transport: WSTransport):
        if self._specific_ping_msg:
            transport.send(WSMsgType.TEXT, self._specific_ping_msg)
            self._log.debug(f"Sent user specific ping {self._specific_ping_msg}")
        else:
            transport.send_ping()
            self._log.debug("Sent default ping.")

    def on_ws_connected(self, transport: WSTransport):
        self._log.debug("Connected to Websocket...")

    def on_ws_disconnected(self, transport: WSTransport):
        self._log.debug("Disconnected from Websocket.")

    def on_ws_frame(self, transport: WSTransport, frame: WSFrame):
        try:
            match frame.msg_type:
                case WSMsgType.PING:
                    # Only send pong if auto_pong is disabled
                    transport.send_pong(frame.get_payload_as_bytes())
                    return
                case WSMsgType.TEXT:
                    # Queue raw bytes for handler to decode
                    self.msg_queue.put_nowait(frame.get_payload_as_bytes())
                    return
                case WSMsgType.CLOSE:
                    self._log.debug(
                        f"Received close frame. {str(frame.get_payload_as_bytes())}"
                    )
                    return
        except Exception as e:
            self._log.error(f"Error processing message: {str(e)}")


class WSClient(ABC):
    def __init__(
        self,
        url: str,
        limiter: Limiter,
        handler: Callable[..., Any],
        specific_ping_msg: bytes = None,
        reconnect_interval: int = 0.2,
        ping_idle_timeout: int = 2,
        ping_reply_timeout: int = 1,
        auto_ping_strategy: Literal[
            "ping_when_idle", "ping_periodically"
        ] = "ping_when_idle",
        enable_auto_ping: bool = True,
        enable_auto_pong: bool = False,
    ):
        self._clock = LiveClock()
        self._url = url
        self._specific_ping_msg = specific_ping_msg
        self._reconnect_interval = reconnect_interval
        self._ping_idle_timeout = ping_idle_timeout
        self._ping_reply_timeout = ping_reply_timeout
        self._enable_auto_pong = enable_auto_pong
        self._enable_auto_ping = enable_auto_ping
        self._listener = None
        self._transport = None
        self._subscriptions = {}
        self._limiter = limiter
        self._callback = handler
        if auto_ping_strategy == "ping_when_idle":
            self._auto_ping_strategy = WSAutoPingStrategy.PING_WHEN_IDLE
        elif auto_ping_strategy == "ping_periodically":
            self._auto_ping_strategy = WSAutoPingStrategy.PING_PERIODICALLY
        self._task_manager = TaskManager()
        self._log = SpdLog.get_logger(type(self).__name__, level="DEBUG", flush=True)

    @property
    def connected(self):
        return self._transport and self._listener

    async def _connect(self):
        WSListenerFactory = lambda: Listener(self._log, self._specific_ping_msg)  # noqa: E731
        self._transport, self._listener = await ws_connect(
            WSListenerFactory,
            self._url,
            enable_auto_ping=self._enable_auto_ping,
            auto_ping_idle_timeout=self._ping_idle_timeout,
            auto_ping_reply_timeout=self._ping_reply_timeout,
            auto_ping_strategy=self._auto_ping_strategy,
            enable_auto_pong=self._enable_auto_pong,
        )

    async def connect(self):
        if not self.connected:
            await self._connect()
            self._task_manager.create_task(self._msg_handler(self._listener.msg_queue))
            self._task_manager.create_task(self._connection_handler())

    async def _connection_handler(self):
        while True:
            try:
                if not self.connected:
                    await self._connect()
                    self._task_manager.create_task(
                        self._msg_handler(self._listener.msg_queue)
                    )
                    await self._resubscribe()
                await self._transport.wait_disconnected()
            except Exception as e:
                self._log.error(f"Connection error: {e}")
            finally:
                self._log.debug("Websocket reconnecting...")
                self._transport, self._listener = None, None
                await asyncio.sleep(self._reconnect_interval)

    async def _send(self, payload: dict):
        await self._limiter.wait()
        self._transport.send(WSMsgType.TEXT, orjson.dumps(payload))

    async def _msg_handler(self, queue: asyncio.Queue):
        while True:
            msg = await queue.get()
            self._callback(msg)
            queue.task_done()

    async def disconnect(self):
        if self.connected:
            self._transport.disconnect()
            await self._task_manager.cancel()

    @abstractmethod
    async def _resubscribe(self):
        pass

Activity

  1. tarasko commented on Dec 17, 2024

    @tarasko
    Owner

    If you didn't receive a "close" frame from the server there is no easy way to know why the server decided to disconnect you. If you still need some code you can assume WSCloseCode.NO_INFO or ABNORMAL_CLOSURE

    picows doesn't try to hold back frames, as soon as it received a complete frame it passes it to the user code immediately.

    What could possibly have happened:

    1. The server doesn't run a code to send a "close" frame before closing socket and terminating TCP connection.
      This typically happens when the server stops abruptly like crash or some kind of forcible stop by admin.
      It is also a very often case that server developers don't care about sending "close" frames even on the normal path.

    2. If the server is running behind cloud connectivity provider (like cloudflare) then such provider may terminate tcp connection.

    3. Network issue. OS TCP stack on the client side terminate TCP connection due to some TCP timeout.

    Afaik there is no reliable way to determine what has happened exactly and distinguish between those cases from the picows perspective, so it doesn't try to guess.

  2. RiverAi7z commented on Dec 17, 2024

    @RiverAi7z
    Author

    If you didn't receive a "close" frame from the server there is no easy way to know why the server decided to disconnect you. If you still need some code you can assume WSCloseCode.NO_INFO or ABNORMAL_CLOSURE

    picows doesn't try to hold back frames, as soon as it received a complete frame it passes it to the user code immediately.

    What could possibly have happened:

    1. The server doesn't run a code to send a "close" frame before closing socket and terminating TCP connection.
      This typically happens when the server stops abruptly like crash or some kind of forcible stop by admin.
      It is also a very often case that server developers don't care about sending "close" frames even on the normal path.
    2. If the server is running behind cloud connectivity provider (like cloudflare) then such provider may terminate tcp connection.
    3. Network issue. OS TCP stack on the client side terminate TCP connection due to some TCP timeout.

    Afaik there is no reliable way to determine what has happened exactly and distinguish between those cases from the picows perspective, so it doesn't try to guess.

    Got it.

  3. Repository owner locked and limited conversation to collaborators on Dec 17, 2024
  4. converted this issue into a discussion #32 on Dec 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions