My program send requests to the site several times via aiohttp
. I also need to use a proxy, so I use a connector from the aiohttp-socks
library. But after implementing the functionality, I encountered an exception.
I made an illustrative example that can be used to reproduce the exception.
requirements.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())
Output
<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>
After searching other people's solutions, I realized that after closing the session and the connector, it keeps pointing to the same session. In order for this not to happen, I need to do one of the following:
connector_owner=False
argument to the session so that it doesn't close the connector.I decided to use the second method:
async with aiohttp.ClientSession(connector=connector, connector_owner=False) as session:
But when I ran the program with this fix a new exception appeared:
<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>
After trying unsuccessfully to find some solutions, I decided to figure it out on my own.
Since the exception says that the connector isn't closed, then it's logical that I should close it after the program has finished. I found two ways to do it.
The most obvious way to do this is to use a special function.
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())
This method is suitable for a simple program like the example. My program is much more complex, so it will be difficult to implement this method. Besides, after several runs I noticed that the print function in some cases prints two identical sessions. You can use print(id(session)) to make sure they are identical.
<aiohttp.client.ClientSession object at 0x0000027BC8ED8940>
<aiohttp.client.ClientSession object at 0x0000027BC8EFFE50>
<aiohttp.client.ClientSession object at 0x0000027BC8EFFE50>
For my program, this behavior is undesirable. So I kept looking for solutions and came up with one more.
force_close=True
connector argumentIf I want a new session to be used for each request, I need to find how to do that. By exploring the base connector I found the force_close=True
argument which does exactly what I need it to do - close connection after request and use a new one for each following request.
Bottom line, the final solution I am using is as follows.
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())