aiohttp с соединителем. Сеанс закрыт и исключения для незакрытого соединителя

Моя программа несколько раз отправляет запросы на сайт черезaiohttp. Еще мне нужно использовать прокси, поэтому я использую коннектор отaiohttp-socksбиблиотека. Но после реализации функционала я столкнулся с исключением.

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

требования.txt

      aiohttp==3.8.4
aiohttp-socks==0.8.0
PySocks==1.7.1

main.py

      import asyncio
from typing import Optional

import aiohttp
from aiohttp_socks import ProxyConnector


async def async_get(url: str, connector: Optional[ProxyConnector] = None, **kwargs) -> str:
    async with aiohttp.ClientSession(connector=connector) as session:
        print(session)
        async with session.get(url=url, **kwargs) as response:
            status_code = response.status
            if status_code <= 201:
                return await response.text()

            raise Exception(f'Unsuccessful request: {status_code}')


async def main():
    proxy = 'socks5://username:password@0.0.0.0:1111'
    connector = ProxyConnector.from_url(url=proxy, rdns=True)
    for _ in range(3):
        await async_get(url='https://www.google.com/', connector=connector)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Выход

      <aiohttp.client.ClientSession object at 0x0000011A5F468940>
Traceback (most recent call last):
  File "...\program\main.py", line 28, in <module>
    loop.run_until_complete(main())
  File "...\Python\Python38\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "...\program\main.py", line 23, in main
    await async_get(url='https://www.google.com/', connector=connector)
  File "...\program\main.py", line 11, in async_get
    async with session.get(url=url, **kwargs) as response:
  File "...\program\venv\lib\site-packages\aiohttp\client.py", line 1141, in __aenter__
    self._resp = await self._coro
  File "...\program\venv\lib\site-packages\aiohttp\client.py", line 400, in _request
    raise RuntimeError("Session is closed")
RuntimeError: Session is closed
<aiohttp.client.ClientSession object at 0x0000011A5F48FE50>

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

  1. Инициализируйте новый соединитель для каждого сеанса;
  2. Пройтиconnector_owner=Falseаргумент сеанса, чтобы он не закрывал соединитель.

Я решил использовать второй метод:

          async with aiohttp.ClientSession(connector=connector, connector_owner=False) as session:

Но когда я запустил программу с этим исправлением, появилось новое исключение:

      <aiohttp.client.ClientSession object at 0x0000024DD07D9940>
<aiohttp.client.ClientSession object at 0x0000024DD07FFE50>
<aiohttp.client.ClientSession object at 0x0000024DD07FF550>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x0000024DD08066A0>, 69074.984)]']
connector: <aiohttp_socks.connector.ProxyConnector object at 0x0000024DD07D9820>

1 ответ

После безуспешных попыток найти какое-то решение, я решил разобраться во всем самостоятельно.

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

соединитель.закрыть()

Самый очевидный способ сделать это — использовать специальную функцию.

main.py

      import asyncio
from typing import Optional

import aiohttp
from aiohttp_socks import ProxyConnector


async def async_get(url: str, connector: Optional[ProxyConnector] = None, **kwargs) -> str:
    async with aiohttp.ClientSession(connector=connector, connector_owner=False) as session:
        print(session)
        async with session.get(url=url, **kwargs) as response:
            status_code = response.status
            if status_code <= 201:
                return await response.text()

            raise Exception(f'Unsuccessful request: {status_code}')


async def main():
    proxy = 'socks5://username:password@0.0.0.0:1111'
    connector = ProxyConnector.from_url(url=proxy, rdns=True)
    for _ in range(3):
        await async_get(url='https://www.google.com/', connector=connector)

    connector.close()  # Close connector


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Этот метод подходит для простой программы, такой как пример. Моя программа гораздо сложнее, поэтому реализовать этот метод будет сложно. Кроме того, после нескольких запусков я заметил, что функция печати в некоторых случаях печатает две одинаковые сессии. Вы можете использовать print(id(session)) чтобы убедиться, что они идентичны.

      <aiohttp.client.ClientSession object at 0x0000027BC8ED8940>
<aiohttp.client.ClientSession object at 0x0000027BC8EFFE50>
<aiohttp.client.ClientSession object at 0x0000027BC8EFFE50>

Для моей программы такое поведение нежелательно. Поэтому я продолжал искать решения и придумал еще одно.

аргумент соединителя

Если я хочу, чтобы для каждого запроса использовался новый сеанс, мне нужно найти, как это сделать. Исследуя разъем базы, я нашеларгумент, который делает именно то, что мне нужно - закрывать соединение после запроса и использовать новое для каждого следующего запроса.

Итог: окончательное решение, которое я использую, выглядит следующим образом.

main.py

      import asyncio
from typing import Optional

import aiohttp
from aiohttp_socks import ProxyConnector


async def async_get(url: str, connector: Optional[ProxyConnector] = None, **kwargs) -> str:
    async with aiohttp.ClientSession(connector=connector, connector_owner=False) as session:
        print(session)
        async with session.get(url=url, **kwargs) as response:
            status_code = response.status
            if status_code <= 201:
                return await response.text()

            raise Exception(f'Unsuccessful request: {status_code}')


async def main():
    proxy = 'socks5://username:password@0.0.0.0:1111'
    connector = ProxyConnector.from_url(url=proxy, rdns=True, force_close=True)
    for _ in range(3):
        await async_get(url='https://www.google.com/', connector=connector)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Другие вопросы по тегам