Aiohttp: Зависание / блокировка клиента websocket?

Созданный на 26 сент. 2018  ·  12Комментарии  ·  Источник: aio-libs/aiohttp

Короче говоря

ws.receive() похоже, зависает, несмотря на поток данных из веб-сокета.

Ожидаемое поведение

ws.receive должен что-то вывести и сообщить.

Фактическое поведение

он просто зависает / блокируется на веб-сокете.

Действия по воспроизведению

Код ниже - это код вызова.

    async def _ws_subscribe(self,
                            session: ClientSession,
                            url: str
                            ) -> Dict:
        """ Fetch data from web socket asynchronously.

        This helper method fetches data from a web socket asynchronously. It is used to
        fetch subscribe to websockets of the SkyQ box.

        Args:
            session (aiohttp.ClientSession): Session to use when fetching the data.
            url (str): WebSocket URL to fetch.

        Returns:
            dict: The body of data returned.

        """
        LOGGER.debug(f"Inside _ws_subscribe()...")

        async with session.ws_connect(url, autoclose=False) as ws:
            while True:
                LOGGER.debug(f"Inside _ws_subscribe() infinite loop pre ws.receive.")
                payload = await ws.receive()
                LOGGER.debug(f"READ SOMETHING! _ws_subscribe() infinite loop post ws.receive")
                LOGGER.debug(f'type = {payload.type}')
                LOGGER.debug(f'payload data = {payload.data}')
                LOGGER.debug(f'payload exception = {ws.exception()}')
                if payload.type == aiohttp.WSMsgType.TEXT:
                    LOGGER.debug('Web-socket data received.')
                    asyncio.create_task(self._handle(payload))
                    # asyncio.ensure_future(self._handle(payload))
                elif payload.type == aiohttp.WSMsgType.BINARY:
                    LOGGER.debug('Web-socket binary data received.')
                elif payload.type == aiohttp.WSMsgType.PING:
                    LOGGER.debug('Web-socket ping received')
                    ws.pong()
                elif payload.type == aiohttp.WSMsgType.PONG:
                    LOGGER.debug('Web-socket pong received')
                else:
                    if payload.type == aiohttp.WSMsgType.CLOSE:
                        await ws.close()
                    elif payload.type == aiohttp.WSMsgType.ERROR:
                        LOGGER.info(f'Error during receive {ws.exception()}')
                    elif payload.type == aiohttp.WSMsgType.CLOSED:
                        pass

                    break

Результирующий журнал:

(pyskyq-4vSEKDfZ) ✔ [brad<strong i="6">@bradmac</strong>:~/Code/pyskyq] [16-create-status-end-point-property|✚ 5…2] $ pyskyq -vv green
[2018-09-26 09:12:14] DEBUG:pyskyq.cli:Starting SkyQ...
[2018-09-26 09:12:14] DEBUG:pyskyq.skyremote:Initialised SkyRemote object with host=skyq, port=49160
[2018-09-26 09:12:14] DEBUG:pyskyq.status:Initialised Status object object with host=skyq, port=9006
[2018-09-26 09:12:14] DEBUG:asyncio:Using selector: KqueueSelector
[2018-09-26 09:12:14] DEBUG:pyskyq.status:Asyncio event loop thread running...
[2018-09-26 09:12:14] DEBUG:pyskyq.skyq:Initialised SkyQ object with host=skyq.
[2018-09-26 09:12:14] INFO:pyskyq.cli:Script ends here
[2018-09-26 09:12:14] DEBUG:pyskyq.status:Setting up web socket listener on ws://skyq:9006/as/system/status.
[2018-09-26 09:12:14] DEBUG:pyskyq.status:Inside _ws_subscribe()...
[2018-09-26 09:12:14] DEBUG:asyncio:Get address info skyq:9006, type=<SocketKind.SOCK_STREAM: 1>
[2018-09-26 09:12:14] DEBUG:asyncio:Getting address info skyq:9006, type=<SocketKind.SOCK_STREAM: 1> took 15.352ms: [(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('10.0.1.6', 9006))]
[2018-09-26 09:12:14] DEBUG:asyncio:poll 60478.847 ms took 14.299 ms: 1 events
[2018-09-26 09:12:14] DEBUG:asyncio:connect <socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('0.0.0.0', 0)> to ('10.0.1.6', 9006)
[2018-09-26 09:12:14] DEBUG:asyncio:poll 60461.657 ms took 21.045 ms: 1 events
[2018-09-26 09:12:14] DEBUG:asyncio:<socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('10.0.1.30', 50141), raddr=('10.0.1.6', 9006)> connected to 10.0.1.6:9006: (<_SelectorSocketTransport fd=8 read=polling write=<idle, bufsize=0>>, <aiohttp.client_proto.ResponseHandler object at 0x1033c8ac8>)
[2018-09-26 09:12:14] DEBUG:asyncio:poll 60429.970 ms took 7.192 ms: 1 events
[2018-09-26 09:12:14] DEBUG:pyskyq.status:Inside _ws_subscribe() infinite loop pre ws.receive.

Однако сокет открыт и обслуживает данные, что может видеть netcat.

(pyskyq-4vSEKDfZ) ✔ [brad<strong i="6">@bradmac</strong>:~/Code/pyskyq] [16-create-status-end-point-property|✚ 5…2] $ nc skyq 9006
GET /as/system/status HTTP/1.1
Host: skyq:9006
Accept: */*
Connection: Upgrade
Upgrade: websocket

�~�{
   "camessage" : {
      "reason" : "no message",
      "state" : "unavailable"
   },
   "drmstatus" : {
      "state" : "available"
   },
   "entitlements" : [
      "ANALYTICS",
      "BIGBASIC",
      "ETHAN_APP_1",
      "HD",
      "PDL",
      "SKY_DRM_CE",
      "SKY_DRM_MR",
      "SKY_IPPV",
      "ULTRA+",
      "SKY+",
      "GATEWAYENABLER",
      "SIDELOAD"
   ],
   "epginfobits" : {
      "epginfobits" : "0xFDE5FFC0",
      "mask" : "0x5FFA003F",
      "state" : "available"
   },
   "gatewayservices" : {
      "state" : "available"
   },
   "hdmi" : {
      "2160p10bitCapable" : false,
      "authenticatedHDCP" : "1.x",
      "sinkHDCP" : "1.x",
      "sinkHLG" : false,
      "sinkUHD" : false,
      "state" : "available",
      "uhdConfigured" : false
   },
   "network" : {
      "state" : "available"
   },
   "nssplayback" : {
      "state" : "available"
   },
   "pvr" : {
      "state" : "available"
   },
   "schedule" : {
      "lastdate" : "20181003",
      "state" : "available"
   },
   "servicelist" : {
      "state" : "available"
   },
   "smartcard" : {
      "active" : true,
      "bouquet" : "4101",
      "countryCode" : "GBR",
      "currency" : "GBP",
      "cwe" : true,
      "householdid" : "10947783",
      "paired" : true,
      "state" : "available",
      "subbouquet" : "1",
      "transactionlimit" : 65535,
      "viewingCardNumber" : "725 325 260"
   },
   "swupdate" : {
      "reason" : "IDLE",
      "state" : "unavailable"
   },
   "systemupdates" : {
      "entitlements" : 2,
      "install" : 1,
      "servicegenres" : 1,
      "smartcard" : 1
   },
   "updatetask" : {
      "reason" : "no update",
      "state" : "unavailable"
   }
}
^C(pyskyq-4vSEKDfZ) ✔ [brad<strong i="7">@bradmac</strong>:~/Code/pyskyq] [16-create-status-end-point-property|✚ 5…2] $

Ваше окружение

(pyskyq-4vSEKDfZ) ✔ [brad<strong i="6">@bradmac</strong>:~/Code/pyskyq] [16-create-status-end-point-property|✚ 5…2] $ uname -a
Darwin bradmac 17.7.0 Darwin Kernel Version 17.7.0: Thu Jun 21 22:53:14 PDT 2018; root:xnu-4570.71.2~1/RELEASE_X86_64 x86_64
(pyskyq-4vSEKDfZ) ✔ [brad<strong i="7">@bradmac</strong>:~/Code/pyskyq] [16-create-status-end-point-property|✚ 5…2] $ pip freeze
aiohttp==3.4.4
appnope==0.1.0
arrow==0.12.1
async-timeout==3.0.0
async-upnp-client==0.12.4
attrs==18.2.0
backcall==0.1.0
binaryornot==0.4.4
bleach==2.1.3
certifi==2018.4.16
chardet==3.0.4
click==6.7
cookiecutter==1.6.0
cycler==0.10.0
decorator==4.3.0
Django==2.1.1
entrypoints==0.2.3
ez-setup==0.9
future==0.16.0
html5lib==1.0.1
idna==2.7
idna-ssl==1.1.0
ipykernel==4.8.2
ipython==6.4.0
ipython-genutils==0.2.0
ipywidgets==7.2.1
jedi==0.12.0
Jinja2==2.10
jinja2-time==0.2.0
jsonschema==2.6.0
jupyter==1.0.0
jupyter-client==5.2.3
jupyter-console==5.2.0
jupyter-core==4.4.0
kaggle==1.4.2
kiwisolver==1.0.1
MarkupSafe==1.0
matplotlib==2.2.2
mistune==0.8.3
multidict==4.4.2
nbconvert==5.3.1
nbformat==4.4.0
notebook==5.5.0
numpy==1.14.4
pandocfilters==1.4.2
parso==0.2.1
pexpect==4.6.0
pickleshare==0.7.4
pipenv==2018.7.1
poyo==0.4.1
prompt-toolkit==1.0.15
ptyprocess==0.6.0
Pygments==2.2.0
pyparsing==2.2.0
PyScaffold==3.0.3
python-dateutil==2.7.3
python-didl-lite==1.1.0
pytz==2018.4
pyzmq==17.0.0
qtconsole==4.3.1
requests==2.19.1
Send2Trash==1.5.0
simplegeneric==0.8.1
six==1.11.0
terminado==0.8.1
testpath==0.3.1
tornado==5.0.2
tqdm==4.24.0
traitlets==4.3.2
urllib3==1.22
virtualenv==16.0.0
virtualenv-clone==0.3.0
voluptuous==0.11.5
wcwidth==0.1.7
webencodings==0.5.1
whichcraft==0.4.1
widgetsnbextension==3.2.1
yarl==1.2.6
(pyskyq-4vSEKDfZ) ✔ [brad<strong i="8">@bradmac</strong>:~/Code/pyskyq] [16-create-status-end-point-property|✚ 5…2] $

Обновить...

Версия Python - 3.7.0

Все 12 Комментарий

GitMate.io считает, что, возможно, связанные проблемы: https://github.com/aio-libs/aiohttp/issues/2200 (фоновые клиентские веб-сайты зависают на SIGINT), https://github.com/aio-libs/aiohttp/issues/ 1002 (ответ Websocket .close () может зависать бесконечно), https://github.com/aio-libs/aiohttp/issues/376 (зависает ProactorEventLoop), https://github.com/aio-libs/aiohttp/issues / 3027 (зависание ws_connect) и https://github.com/aio-libs/aiohttp/issues/265 (реализация веб-сокета на стороне клиента).

похоже, client_ws.py # L204 не возвращает ... какие-нибудь идеи?

Сервер представляет собой телеприставку SkyQ, поэтому реализация WS может не полностью соответствовать RFC. Например, он не соблюдает ping / poings ... И в полезной нагрузке есть странный �~� ... Не уверен, что это код операции или какая-то другая причуда ...

Websocket - это двоичный протокол. �~� кодирует тип и длину сообщения.
Извините, я не могу вам помочь без вашего участия в его расшифровке и сравнении с фактическим размером полезной нагрузки.

�~� - это 001111110111111000111111 в двоичном формате, то есть 3F7E3F в шестнадцатеричном формате.

эти байты выглядят как сквозные ...
00111111
01111110
00111111

Я не эксперт по RFC, но что там говорится о типе и длине?

Как вы думаете, проблема в этом? Т.е. это не согласие с реальной длиной полезной нагрузки, а то, что ожидает, например, лишнего кадра?

Интересно, что этот клиент cli веб-сокета , написанный на go, отлично обрабатывает вывод, включая двоичный материал вверху:

``
WS WS: // SkyQ: 9006 / как / система / статус
<{
"camessage": {
"причина": "нет сообщения",
"состояние": "недоступно"
},
"drmstatus": {
"состояние": "доступно"
},
"права": [
«АНАЛИТИКА»,
"BIGBASIC",
"ETHAN_APP_1",
"HD",
«ПДЛ»,
"SKY_DRM_CE",
"SKY_DRM_MR",
"SKY_IPPV",
"УЛЬТРА +",
"НЕБО +",
"ШЛЮЗИНАБЛЕР",
"SIDELOAD"
],
"epginfobits": {
"epginfobits": "0xFDE5FFC0",
"маска": "0x5FFA003F",
"состояние": "доступно"
},
"gatewayservices": {
"состояние": "доступно"
},
"hdmi": {
"2160p10bitCapable": false,
"AuthenticatedHDCP": "НЕТ",
"причина": "Выходной порт HDMI отключен",
"inkHDCP ":" НЕТ ",
"inkHLG ": ложь,
"inkUHD ": ложь,
"состояние": "недоступно",
"uhdConfigured": ложь
},
"сеть" : {
"состояние": "доступно"
},
"nssplayback": {
"состояние": "доступно"
},
"pvr": {
"состояние": "доступно"
},
"расписание" : {
"lastdate": "20181003",
"состояние": "доступно"
},
"перечень услуг" : {
"состояние": "доступно"
},
"интеллектуальная карточка" : {
"активный": правда,
«букет»: «4101»,
"countryCode": "GBR",
«валюта»: «фунты стерлингов»,
"cwe": правда,
"homeid": "10947783",
"парный": правда,
"состояние": "доступно",
"суббукет": "1",
«transactionlimit»: 65535,
"viewCardNumber": "725 325 260"
},
"swupdate": {
"причина": "ПРОСТОЙ",
"состояние": "недоступно"
},
"системные обновления" : {
«права»: 2,
«установить»: 1,
"servicegenres": 1,
«смарт-карта»: 1
},
"updatetask": {
"причина": "нет обновлений",
"состояние": "недоступно"
}
}

^ C
Прерывать
✔ [ brad @ bradmac : ~] $ `` `

Протокол гласит: первый байт 3F в WS-сообщении запрещен (зарезервирован для использования в будущем).
Вы уверены, что что-то не пропустили?

Я предполагаю, что вы имеете в виду 2-й, 3-й и 4-й бит в базовом протоколе кадрирования. В таком случае да, возможно, я ошибся. Это может быть проблема с Unicode / ASCII. Я посмотрю еще раз.

Я пробовал несколько других онлайн-конвертеров ... Я получаю вот что:
шестнадцатеричный = ef bf bd 7e ef bf bd
двоичный = 11101111 10111111 10111101 01111110 11101111 10111111 10111101

Если в соответствии с RFC 6455

5.2.  Base Framing Protocol

   This wire format for the data transfer part is described by the ABNF
   [RFC5234] given in detail in this section.  (Note that, unlike in
   other sections of this document, the ABNF in this section is
   operating on groups of bits.  The length of each group of bits is
   indicated in a comment.  When encoded on the wire, the most
   significant bit is the leftmost in the ABNF).  A high-level overview
   of the framing is given in the following figure.  In a case of
   conflict between the figure below and the ABNF specified later in
   this section, the figure is authoritative.

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+

   FIN:  1 bit

      Indicates that this is the final fragment in a message.  The first
      fragment MAY also be the final fragment.

   RSV1, RSV2, RSV3:  1 bit each

      MUST be 0 unless an extension is negotiated that defines meanings
      for non-zero values.  If a nonzero value is received and none of
      the negotiated extensions defines the meaning of such a nonzero
      value, the receiving endpoint MUST _Fail the WebSocket
      Connection_.

Тогда похоже, что RSV1 и RSV2 устанавливаются в 1, как вы предлагаете, если не ведутся переговоры о каком-то etxension? Я понятия не имею.

Количество символов в документе JSON: 1703

RFC говорит о длине полезной нагрузки:

Payload length:  7 bits, 7+16 bits, or 7+64 bits

      The length of the "Payload data", in bytes: if 0-125, that is the
      payload length.  If 126, the following 2 bytes interpreted as a
      16-bit unsigned integer are the payload length.  If 127, the
      following 8 bytes interpreted as a 64-bit unsigned integer (the
      most significant bit MUST be 0) are the payload length.  Multibyte
      length quantities are expressed in network byte order.  Note that
      in all cases, the minimal number of bytes MUST be used to encode
      the length, for example, the length of a 124-byte-long string
      can't be encoded as the sequence 126, 0, 124.  The payload length
      is the length of the "Extension data" + the length of the
      "Application data".  The length of the "Extension data" may be
      zero, in which case the payload length is the length of the
      "Application data".

Итак, учитывая все это, куда мы пойдем дальше? Если сервер «мертвый мозг» и не соответствует RFC, то мне нужно обойти это, поскольку другие клиенты явно могут это сделать ... Можете ли вы предложить какие-либо предложения?

По-прежнему выглядит подозрительно.
Единственное известное расширение - это сжатие WebSocket, использующее RSV1. Я не знаю, какое расширение использует RSV2.
Более того, код операции FF тоже зарезервирован и не должен использоваться.

Если вы не знаете, что отправляет сервер, невозможно «исправить» клиента.

@asvetlov Я попробовал библиотеку websockets и она работает ... Так что я собираюсь пойти с ней, так как я не могу тратить больше времени на отладку этой библиотеки с вами ...

aoihttp - не работает

import asyncio
import logging
import sys

import aiohttp

LOGGER = logging.getLogger(__name__)
logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout,
                    format=logformat, datefmt="%Y-%m-%d %H:%M:%S")


async def main():
    async with aiohttp.ClientSession() as session:
        async with session.ws_connect('http://skyq:9006/as/system/status') as ws:
            payload = await ws.receive()
            print(payload.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

websockets - работает

import asyncio
import websockets
import logging
import sys

LOGGER = logging.getLogger(__name__)
logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout,
                    format=logformat, datefmt="%Y-%m-%d %H:%M:%S")


async def hello():
    async with websockets.connect(
            'ws://skyq:9006/as/system/status') as websocket:

        payload = await websocket.recv()
        print(f"{payload}")

asyncio.get_event_loop().run_until_complete(hello())

Вы можете бесплатно закрыть этот билет, если хотите, но вы можете оставить его открытым, поскольку он действительно выглядит ошибкой ... Или, по крайней мере, ситуацией, которая не подчиняется закону Постела.

Я ценю вашу помощь в попытке решить эту проблему.

По-прежнему выглядит подозрительно.
Единственное известное расширение - это сжатие WebSocket, использующее RSV1. Я не знаю, какое расширение использует RSV2.
Более того, код операции FF тоже зарезервирован и не должен использоваться.

Привет @asvetlov

Если вы не знаете, что отправляет сервер, невозможно «исправить» клиента.

Я думаю, вы имели в виду «если вы не знаете, что отправляет сервер, вы не можете исправить клиента».

В таком случае я полностью не согласен с этим.

Вы знакомы с принципом устойчивости ?

«Будьте консервативными в том, что вы посылаете, и либеральными в том, что получаете».

Независимо от ДОЛЖЕН, ДОЛЖЕН и НЕ ДОЛЖЕН, в RFC, я думаю, вы интерпретируете и ожидаете, что объекты будут соответствовать RFC _ полностью _... Ясно, что они этого не делают, и многие многие интернет-серверы и клиенты различных типов имеют ужасные ублюдочные полуреализации протоколов.

Хорошая реализация библиотеки / протокола должна прощать их по мере возможности. См. RFC1122 . Вполне возможно написать клиента протокола, прощающего (некоторые) нарушения протокола, не имея доступа к другому участнику связи.

Я считаю, что здесь дело обстоит не так, похоже, вы придерживаетесь жесткой точки зрения, что если хост не на 100% соответствует требованиям, то это плохо для него, мы не будем с ним разговаривать. Это очень плохо. Это как сказать: «Я говорю только на королевском английском, и если вы заговорите со мной на каком-то странном диалекте английского языка, я не стану с вами разговаривать».

Ясно, что это не лучший подход, если вся задача библиотеки - облегчить общение.

Поэтому я предлагаю подумать, где вы можете ослабить строгие требования протокола (возможно, с помощью метода kwargs, который включает "расслабленную" обработку), и посмотреть, сможете ли вы заставить вещи говорить, даже когда другая сторона не ведет себя должным образом. .

Помимо этой критики, я хотел бы поблагодарить вас и участников из aio-libs за всю вашу работу для сообщества. 👍 🥇

@asvetlov Просто обновите это ... Я понял, что моему (несовместимому) серверу требуется заголовок Upgrade: в верхнем регистре, прежде чем он заработает. Просто подумал, что упомяну об этом, если проблема заголовков Title Case рассматривается вами, ребята ... Это реальный случай нарушения протокола из-за плохо реализованного сервера, из-за которого клиент websocket не работает ..

Была ли эта страница полезной?
0 / 5 - 0 рейтинги