Requests / aiohttp: закрытие объектов ответа - PullRequest
0 голосов
/ 01 ноября 2018

Я немного озадачен необходимостью .close() объекта ответа в requests и aiohttp. (Обратите внимание, что это отдельный метод экземпляра, чем session.close() - я говорю о самом объекте ответа.)

  • Требуется ли Response (requests) или ClientResponse (aiohttp) явный вызов .close()?
  • Если нет, какова цель использования самого ответа в качестве менеджера контекста? (async with session.request('GET', 'https://www.pastebin.com') ниже.) Зачем определять для этого два более сложных метода, если они неявно закрываются, как показано ниже?

Некоторые простые тесты (ниже), по-видимому, подразумевают, что ответы закрываются автоматически, когда они определены внутри диспетчера контекста сеанса. (который сам вызывает self.close() в __exit__ или __aexit__. Но это закрытие сеанса, а не объекта Response.)

Пример - requests

>>> import requests
>>> 
>>> with requests.Session() as s:
...     resp = s.request('GET', 'https://www.pastebin.com')
...     resp.raise_for_status()
...     print(resp.raw.closed)  # `raw` is urllib3.response.HTTPResponse object
...     print(resp.raw._pool)
...     print(resp.raw._connection)
...     c = resp.text
... 
True
HTTPSConnectionPool(host='pastebin.com', port=443)
None
>>>
>>> while 1:
...     print(resp.raw.closed)
...     print(resp.raw._pool)
...     print(resp.raw._connection)
...     break
... 
True
HTTPSConnectionPool(host='pastebin.com', port=443)
None

Пример - aiohttp

>>> import asyncio
>>> import aiohttp
>>>
>>> async def get():
...     async with aiohttp.ClientSession() as s:
...         # The response is already closed after this `with` block.
...         # Why would it need to be used as a context manager?
...         resp = await s.request('GET', 'https://www.pastebin.com')
...         print(resp._closed)
...         print(resp._connection)
...         print(resp._released)
...         c = await resp.text()
...     print()
...     print(resp._closed)
...     print(resp._connection)
...     print(resp._released)
...     return c
... 
>>> c = asyncio.run(get())  # Python 3.7 +
False
Connection<ConnectionKey(host='pastebin.com', port=443, is_ssl=True, ssl=None, proxy=None, proxy_auth=None, proxy_headers_hash=None)>
False

True
None
False

Вот источник requests.models.Response. Что означает «не следует обычно называть явно»? Какие исключения?

def close(self):
    """Releases the connection back to the pool. Once this method has been
    called the underlying ``raw`` object must not be accessed again.
    *Note: Should not normally need to be called explicitly.*
    """
    if not self._content_consumed:
        self.raw.close()

    release_conn = getattr(self.raw, 'release_conn', None)
    if release_conn is not None:
        release_conn()

1 Ответ

0 голосов
/ 04 ноября 2018

Requests: вам не нужно явно звонить close(). запрос будет автоматически закрыт после завершения, потому что он основан на urlopen (вот почему resp.raw.closed имеет значение True). Это упрощенный код после того, как я посмотрел session.py и adapters.py:

from urllib3 import PoolManager
import time
manager = PoolManager(10)
conn = manager.connection_from_host('host1.example.com')
conn2 = manager.connection_from_host('host2.example.com')
res = conn.urlopen(url="http://host1.example.com/",method="get")
print(len(manager.pools))
manager.clear()
print(len(manager.pools))
print(res.closed)

#2
#0
#True

Тогда что же сделал __exit__? Используется для очистки PoolManager(self.poolmanager=PoolManager(...)) и proxy.

# session.py
def __exit__(self, *args): #line 423
    self.close()
def close(self): #line 733
    for v in self.adapters.values():
        v.close()

# adapters.py
# v.close()
def close(self): #line 307
        self.poolmanager.clear()
        for proxy in self.proxy_manager.values():
            proxy.clear()

Поэтому, когда вам нужно использовать close(), как сказано в примечании Освобождает соединение обратно в пул , потому что DEFAULT_POOLSIZE = 10 (http / https независимы). Это означает, что если вы хотите получить доступ к более чем 10 веб-сайтам за один сеанс, вы можете закрыть некоторые из них, которые вам не нужны, иначе менеджер закроет соединение с первого на самый новый, когда у вас будет еще один. Но на самом деле вам не нужно заботиться об этом, вы можете указать размер пула, и это не будет тратить много времени на восстановление соединения

aiohttp aiohttp.ClientSession () использует один TCPConnector для всех запросов. Когда сработает __aexit__, self._connector будет закрыт.

Редактировать: s.request() настроил соединение с хоста, но он не получил ответа. await resp.text() можно сделать только после получения ответа, если вы не сделали такой шаг (дождитесь ответа), вы выйдете без ответа.

if connector is None: #line 132
    connector = TCPConnector(loop=loop)
...
self._connector = connector #line 151
# connection timeout
try:
    with CeilTimeout(real_timeout.connect,loop=self._loop):
    assert self._connector is not None
    conn = await self._connector.connect(
        req,
        traces=traces,
        timeout=real_timeout
        )
...
async def close(self) -> None:
        if not self.closed:
            if self._connector is not None and self._connector_owner:
                self._connector.close()
            self._connector = None
...
async def __aexit__(self,
                       ...) -> None:
        await self.close()

Это код, чтобы показать, что я сказал

import asyncio
import aiohttp
import time

async def get():
    async with aiohttp.ClientSession() as s:
        # The response is already closed after this `with` block.
        # Why would it need to be used as a context manager?
        resp = await s.request('GET', 'https://www.stackoverflow.com')
        resp2 = await s.request('GET', 'https://www.github.com')
        print("resp:",resp._closed)
        print("resp:",resp._connection)
        print("resp2:",resp2._closed)
        print("resp2:",resp2._connection)
        s.close()
        print(s.closed)
        c = await resp.text()
        d = await resp2.text()

    print()
    print(s._connector)
    print("resp:",resp._closed)
    print("resp:",resp._connection)
    print("resp2:",resp2._closed)
    print("resp2:",resp2._connection)

loop = asyncio.get_event_loop()
loop.run_until_complete(get())  # Python 3.5 +

#dead loop
...