mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
feat(proxy): proxy for aiohttp,base sessions
Add support for proxies in aiohttp session with aiohttp_socks library, edit BaseSession class to support proxies for other sessions in future.
This commit is contained in:
parent
0bd7fc2c7e
commit
aa289cdd93
6 changed files with 99 additions and 10 deletions
|
|
@ -1,29 +1,70 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import AsyncGenerator, Callable, Optional, TypeVar, cast
|
from typing import AsyncGenerator, Callable, Optional, TypeVar, Tuple, Dict, Any, cast
|
||||||
|
|
||||||
from aiohttp import ClientSession, ClientTimeout, FormData
|
from aiohttp import ClientSession, ClientTimeout, FormData, BasicAuth, TCPConnector
|
||||||
|
|
||||||
from aiogram.api.methods import Request, TelegramMethod
|
from aiogram.api.methods import Request, TelegramMethod
|
||||||
|
|
||||||
from .base import PRODUCTION, BaseSession, TelegramAPIServer
|
from .base import PRODUCTION, BaseSession, TelegramAPIServer
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
_ProxyType = Tuple[str, BasicAuth]
|
||||||
|
|
||||||
|
|
||||||
class AiohttpSession(BaseSession):
|
class AiohttpSession(BaseSession[_ProxyType]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
api: TelegramAPIServer = PRODUCTION,
|
api: TelegramAPIServer = PRODUCTION,
|
||||||
|
proxy: Optional[_ProxyType] = None,
|
||||||
json_loads: Optional[Callable] = None,
|
json_loads: Optional[Callable] = None,
|
||||||
json_dumps: Optional[Callable] = None,
|
json_dumps: Optional[Callable] = None,
|
||||||
):
|
):
|
||||||
super(AiohttpSession, self).__init__(api=api, json_loads=json_loads, json_dumps=json_dumps)
|
super(AiohttpSession, self).__init__(
|
||||||
|
api=api,
|
||||||
|
json_loads=json_loads,
|
||||||
|
json_dumps=json_dumps,
|
||||||
|
proxy=proxy
|
||||||
|
)
|
||||||
self._session: Optional[ClientSession] = None
|
self._session: Optional[ClientSession] = None
|
||||||
|
self.cfg.connector_type = TCPConnector
|
||||||
|
self.cfg.connector_init = cast(Dict[str, Any], {})
|
||||||
|
|
||||||
|
if self.proxy:
|
||||||
|
proxy_url, proxy_auth = self.proxy
|
||||||
|
|
||||||
|
try:
|
||||||
|
from aiohttp_socks import ProxyConnector
|
||||||
|
from aiohttp_socks.utils import parse_proxy_url
|
||||||
|
except ImportError as exc:
|
||||||
|
raise UserWarning(
|
||||||
|
"In order to use aiohttp client for proxy requests, install "
|
||||||
|
"https://pypi.org/project/aiohttp-socks/0.3.4"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
if proxy_url.startswith('socks5://') or proxy_url.startswith('socks4://'):
|
||||||
|
self.cfg.connector_type = ProxyConnector
|
||||||
|
|
||||||
|
proxy_type, host, port, username, password = parse_proxy_url(proxy_url)
|
||||||
|
if proxy_auth:
|
||||||
|
if not username:
|
||||||
|
username = proxy_auth.login
|
||||||
|
if not password:
|
||||||
|
password = proxy_auth.password
|
||||||
|
|
||||||
|
self.cfg.connector_init.update(
|
||||||
|
dict(
|
||||||
|
proxy_type=proxy_type, host=host, port=port,
|
||||||
|
username=username, password=password,
|
||||||
|
rdns=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def create_session(self) -> ClientSession:
|
async def create_session(self) -> ClientSession:
|
||||||
if self._session is None or self._session.closed:
|
if self._session is None or self._session.closed:
|
||||||
self._session = ClientSession()
|
self._session = ClientSession(
|
||||||
|
connector=self.cfg.connector_type(**self.cfg.connector_init)
|
||||||
|
)
|
||||||
|
|
||||||
return self._session
|
return self._session
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ from __future__ import annotations
|
||||||
import abc
|
import abc
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
from typing import Any, AsyncGenerator, Callable, Optional, TypeVar, Union
|
import types
|
||||||
|
from typing import Any, AsyncGenerator, Callable, Optional, TypeVar, Union, Generic
|
||||||
|
|
||||||
from aiogram.utils.exceptions import TelegramAPIError
|
from aiogram.utils.exceptions import TelegramAPIError
|
||||||
|
|
||||||
|
|
@ -11,12 +12,14 @@ from ...methods import Response, TelegramMethod
|
||||||
from ..telegram import PRODUCTION, TelegramAPIServer
|
from ..telegram import PRODUCTION, TelegramAPIServer
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
_ProxyType = TypeVar("_ProxyType")
|
||||||
|
|
||||||
|
|
||||||
class BaseSession(abc.ABC):
|
class BaseSession(abc.ABC, Generic[_ProxyType]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
api: Optional[TelegramAPIServer] = None,
|
api: Optional[TelegramAPIServer] = None,
|
||||||
|
proxy: Optional[ProxyType] = None,
|
||||||
json_loads: Optional[Callable[[Any], Any]] = None,
|
json_loads: Optional[Callable[[Any], Any]] = None,
|
||||||
json_dumps: Optional[Callable[[Any], Any]] = None,
|
json_dumps: Optional[Callable[[Any], Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
@ -30,6 +33,9 @@ class BaseSession(abc.ABC):
|
||||||
self.api = api
|
self.api = api
|
||||||
self.json_loads = json_loads
|
self.json_loads = json_loads
|
||||||
self.json_dumps = json_dumps
|
self.json_dumps = json_dumps
|
||||||
|
self.proxy = proxy
|
||||||
|
|
||||||
|
self.cfg: types.SimpleNamespace = types.SimpleNamespace()
|
||||||
|
|
||||||
def raise_for_status(self, response: Response[T]) -> None:
|
def raise_for_status(self, response: Response[T]) -> None:
|
||||||
if response.ok:
|
if response.ok:
|
||||||
|
|
|
||||||
23
poetry.lock
generated
23
poetry.lock
generated
|
|
@ -24,6 +24,18 @@ yarl = ">=1.0,<2.0"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
speedups = ["aiodns", "brotlipy", "cchardet"]
|
speedups = ["aiodns", "brotlipy", "cchardet"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Proxy connector for aiohttp"
|
||||||
|
name = "aiohttp-socks"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.3.4"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
aiohttp = ">=2.3.2"
|
||||||
|
attrs = ">=19.2.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
|
|
@ -861,7 +873,7 @@ testing = ["jaraco.itertools", "func-timeout"]
|
||||||
fast = ["uvloop"]
|
fast = ["uvloop"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
content-hash = "2eb50b5b57d0fac4780f1eb3f92ff129d891fd346e0c00856c1a56c58feffb03"
|
content-hash = "2a95c67ca1bea20c29bf63542bf5ace592b9ee5dcc70e5bf8ed304bd9b37ded2"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
|
|
@ -883,6 +895,10 @@ aiohttp = [
|
||||||
{file = "aiohttp-3.6.2-py3-none-any.whl", hash = "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4"},
|
{file = "aiohttp-3.6.2-py3-none-any.whl", hash = "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4"},
|
||||||
{file = "aiohttp-3.6.2.tar.gz", hash = "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326"},
|
{file = "aiohttp-3.6.2.tar.gz", hash = "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326"},
|
||||||
]
|
]
|
||||||
|
aiohttp-socks = [
|
||||||
|
{file = "aiohttp_socks-0.3.4-py3-none-any.whl", hash = "sha256:654203308e24aa4f012f96d4af7675404565f4cc39a113d110e3d345ede0838f"},
|
||||||
|
{file = "aiohttp_socks-0.3.4.tar.gz", hash = "sha256:cdea1d99c14fd3884968a34a6ecb2784f4c48e657aec99ec1b986a10812287bd"},
|
||||||
|
]
|
||||||
appdirs = [
|
appdirs = [
|
||||||
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
|
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
|
||||||
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
|
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
|
||||||
|
|
@ -1083,6 +1099,11 @@ markupsafe = [
|
||||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
|
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
|
||||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
|
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
|
||||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
|
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
|
||||||
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
|
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
|
||||||
]
|
]
|
||||||
mccabe = [
|
mccabe = [
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ pymdown-extensions = "^6.1"
|
||||||
lxml = "^4.4"
|
lxml = "^4.4"
|
||||||
ipython = "^7.10"
|
ipython = "^7.10"
|
||||||
markdown-include = "^0.5.1"
|
markdown-include = "^0.5.1"
|
||||||
|
aiohttp-socks = "^0.3.4"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
fast = ["uvloop"]
|
fast = ["uvloop"]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import AsyncContextManager, AsyncGenerator
|
from typing import AsyncContextManager, AsyncGenerator
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import aiohttp_socks
|
||||||
import pytest
|
import pytest
|
||||||
from aresponses import ResponsesMockServer
|
from aresponses import ResponsesMockServer
|
||||||
|
|
||||||
|
|
@ -29,6 +30,20 @@ class TestAiohttpSession:
|
||||||
assert session._session is not None
|
assert session._session is not None
|
||||||
assert isinstance(aiohttp_session, aiohttp.ClientSession)
|
assert isinstance(aiohttp_session, aiohttp.ClientSession)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_proxy_session(self):
|
||||||
|
session = AiohttpSession(
|
||||||
|
proxy=("socks5://proxy.url/", aiohttp.BasicAuth("login", "password", "encoding"))
|
||||||
|
)
|
||||||
|
|
||||||
|
assert session.cfg.connector_type == aiohttp_socks.ProxyConnector
|
||||||
|
|
||||||
|
assert isinstance(session.cfg.connector_init, dict)
|
||||||
|
assert session.cfg.connector_init["proxy_type"] is aiohttp_socks.ProxyType.SOCKS5
|
||||||
|
|
||||||
|
aiohttp_session = await session.create_session()
|
||||||
|
assert isinstance(aiohttp_session.connector, aiohttp_socks.ProxyConnector)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_close_session(self):
|
async def test_close_session(self):
|
||||||
session = AiohttpSession()
|
session = AiohttpSession()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
import types
|
||||||
import datetime
|
import datetime
|
||||||
from typing import AsyncContextManager, AsyncGenerator
|
from typing import AsyncContextManager, AsyncGenerator, Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ except ImportError:
|
||||||
from unittest.mock import AsyncMock as CoroutineMock, patch # type: ignore
|
from unittest.mock import AsyncMock as CoroutineMock, patch # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class CustomSession(BaseSession):
|
class CustomSession(BaseSession[Any]):
|
||||||
async def close(self):
|
async def close(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -44,6 +45,10 @@ class TestBaseSession(DataMixin):
|
||||||
session = CustomSession(api=api)
|
session = CustomSession(api=api)
|
||||||
assert session.api == api
|
assert session.api == api
|
||||||
|
|
||||||
|
def test_init_cfg_namespace(self):
|
||||||
|
session = CustomSession()
|
||||||
|
assert isinstance(session.cfg, types.SimpleNamespace)
|
||||||
|
|
||||||
def test_prepare_value(self):
|
def test_prepare_value(self):
|
||||||
session = CustomSession()
|
session = CustomSession()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue