Урок 7.3 — Кастомные сессии
Узнаём, как создавать собственные HTTP-сессии для aiosend на базе httpx, requests и других библиотек, настраивать таймауты и прокси.
По умолчанию aiosend использует AiohttpSession на базе aiohttp. Но ты можешь создать свою сессию, используя любую HTTP-библиотеку: httpx, requests (через threading), curl_cffi и другие. Для этого нужно унаследоваться от BaseSession и реализовать метод request().
AiohttpSession — стандартная сессия
AiohttpSession используется по умолчанию. Она построена на базе aiohttp.ClientSession и подходит для 99% проектов. Рассмотрим её внутреннее устройство.
import ssl
import certifi
from aiohttp import ClientSession, ClientTimeout, TCPConnector
class AiohttpSession(BaseSession):
"""Сессия на базе aiohttp."""
def __init__(self, network: Network, timeout: float = 300):
super().__init__(network, timeout)
self._session: ClientSession | None = None
async def request(self, token, client, method):
ssl_context = ssl.create_default_context(cafile=certifi.where())
self._session = ClientSession(
timeout=ClientTimeout(self.timeout),
connector=TCPConnector(ssl_context=ssl_context),
)
async with self._session as session:
resp = await session.post(
url=self.network.url(method),
data=method.model_dump_json(exclude_none=True),
headers={
"Crypto-Pay-API-Token": token,
"Content-Type": "application/json",
"User-Agent": f"Python/3.x aiohttp/x aiosend/3.x",
},
)
response = self._check_response(client, method, await resp.text())
return response.result
💡 Почему AiohttpSession по умолчанию?
aiohttp — это самая популярная асинхронная HTTP-библиотека в Python. Она быстрая, хорошо поддерживается, имеет встроенную поддержку SSL, таймаутов и пула соединений. Для большинства проектов её более чем достаточно.
BaseSession — абстрактный базовый класс
BaseSession — это абстрактный базовый класс (ABC), от которого должны наследоваться все сессии. Он определяет интерфейс и предоставляет вспомогательные методы для обработки ответов API.
| Метод / Атрибут | Описание |
|---|---|
__init__(network, timeout) |
Сохраняет network и timeout. |
request(token, client, method) |
Абстрактный метод — ты должен его реализовать. |
_check_response(client, method, text) |
Парсит ответ API и проверяет на ошибки. |
network |
Объект Network (c полями name, base). |
from aiosend.client.session.base import BaseSession
from aiosend.client.network import Network
class MyCustomSession(BaseSession):
"""Моя кастомная сессия."""
def __init__(self, network: Network, timeout: float = 300):
super().__init__(network, timeout)
# Здесь инициализируем свой HTTP-клиент
async def request(self, token, client, method):
"""
Отправляет POST-запрос к API.
Аргументы:
token — API-токен
client — ссылка на клиент (необязательно)
method — объект метода API (например, create_invoice)
"""
# 1. Формируем URL
url = self.network.url(method)
# 2. Формируем заголовки
headers = {
"Crypto-Pay-API-Token": token,
"Content-Type": "application/json",
}
# 3. Сериализуем тело запроса
data = method.model_dump_json(exclude_none=True)
# 4. Отправляем запрос (твоя реализация)
response_text = await self._send_post(url, headers, data)
# 5. Обрабатываем ответ через встроенный метод
return self._check_response(client, method, response_text)
async def _send_post(self, url, headers, data):
# Твоя логика HTTP-запроса
raise NotImplementedError
Кастомная сессия на httpx
httpx — современная альтернатива aiohttp. Она имеет схожий API, поддерживает HTTP/2, таймауты, прокси и многое другое.
from aiosend import CryptoPay
from aiosend.client.session.base import BaseSession
from aiosend.client.network import Network
import httpx
class HttpxSession(BaseSession):
"""Сессия на базе httpx."""
def __init__(self, network: Network, timeout: float = 300):
super().__init__(network, timeout)
self._client = httpx.AsyncClient(
timeout=httpx.Timeout(timeout),
)
async def request(self, token, client, method):
url = self.network.url(method)
headers = {
"Crypto-Pay-API-Token": token,
"Content-Type": "application/json",
}
data = method.model_dump_json(exclude_none=True)
resp = await self._client.post(url, content=data, headers=headers)
return self._check_response(client, method, resp.text)
# Использование:
cp = CryptoPay(
token="1234:TOKEN",
session=HttpxSession, # передаём КЛАСС, не объект!
timeout=60,
)
🔍 Важно: session — это класс, а не объект
Параметр session принимает класс (type), а не экземпляр. aiosend внутри вызывает session(network, timeout), чтобы создать экземпляр, передав ему текущую сеть и таймаут. Это сделано для того, чтобы сессия могла использовать network и timeout из конструктора CryptoPay.
Настройка таймаута для httpx
class HttpxSessionWithTimeout(BaseSession):
"""Сессия на httpx с раздельными таймаутами."""
def __init__(self, network: Network, timeout: float = 300):
super().__init__(network, timeout)
# httpx позволяет раздельно настроить:
# connect, read, write, pool — таймауты
self._client = httpx.AsyncClient(
timeout=httpx.Timeout(
connect=10.0, # подключение
read=timeout, # чтение ответа
write=10.0, # запись запроса
pool=30.0, # ожидание в пуле
),
)
async def request(self, token, client, method):
# ... та же реализация, что выше
url = self.network.url(method)
headers = {
"Crypto-Pay-API-Token": token,
"Content-Type": "application/json",
}
data = method.model_dump_json(exclude_none=True)
resp = await self._client.post(url, content=data, headers=headers)
return self._check_response(client, method, resp.text)
Кастомная сессия на requests (синхронная)
requests — синхронная библиотека. Чтобы использовать её в асинхронном контексте, нужно запускать запрос в отдельном потоке через run_in_executor или asyncio.to_thread.
import asyncio
import requests
from aiosend.client.session.base import BaseSession
from aiosend.client.network import Network
class RequestsSession(BaseSession):
"""Сессия на базе requests (синхронная, через поток)."""
def __init__(self, network: Network, timeout: float = 300):
super().__init__(network, timeout)
self._session = requests.Session()
self._session.headers.update({
"Content-Type": "application/json",
})
async def request(self, token, client, method):
url = self.network.url(method)
headers = {"Crypto-Pay-API-Token": token}
data = method.model_dump_json(exclude_none=True)
# Запускаем синхронный запрос в потоке
resp = await asyncio.to_thread(
self._session.post,
url,
data=data,
headers=headers,
timeout=self.timeout,
)
return self._check_response(client, method, resp.text)
# Использование:
cp = CryptoPay(
token="1234:TOKEN",
session=RequestsSession,
)
⚠️ Когда использовать requests?
requests — хороший выбор, если у тебя уже есть код на requests и ты не хочешь добавлять новую зависимость. Однако для новых проектов рекомендуется httpx или aiohttp — они асинхронные и быстрее.
Прокси для сессий
В некоторых случаях может понадобиться прокси — например, если сервер находится в регионе с ограничениями или нужно использовать корпоративный прокси-сервер.
Прокси через AiohttpSession (расширение)
Стандартная AiohttpSession не поддерживает прокси напрямую. Но ты можешь создать подкласс и добавить поддержку:
from aiosend.client.session.aiohttp import AiohttpSession
class ProxiedAiohttpSession(AiohttpSession):
"""Aiohttp-сессия с поддержкой прокси."""
def __init__(self, network, timeout=300, proxy=None):
super().__init__(network, timeout)
self._proxy = proxy
async def request(self, token, client, method):
import ssl
import certifi
from aiohttp import ClientSession, ClientTimeout, TCPConnector
ssl_context = ssl.create_default_context(cafile=certifi.where())
async with ClientSession(
timeout=ClientTimeout(self.timeout),
connector=TCPConnector(ssl_context=ssl_context),
) as session:
resp = await session.post(
url=self.network.url(method),
data=method.model_dump_json(exclude_none=True),
headers={
"Crypto-Pay-API-Token": token,
"Content-Type": "application/json",
},
proxy=self._proxy, # используем прокси
)
return self._check_response(
client, method, await resp.text()
)
# Использование:
cp = CryptoPay(
token="1234:TOKEN",
session=ProxiedAiohttpSession,
proxy="http://user:pass@proxy.example.com:8080",
)
Прокси через httpx
httpx имеет встроенную поддержку прокси:
import httpx
from aiosend.client.session.base import BaseSession
class HttpxProxySession(BaseSession):
"""httpx-сессия с прокси."""
def __init__(self, network, timeout=300, proxy_url=None):
super().__init__(network, timeout)
self._client = httpx.AsyncClient(
timeout=httpx.Timeout(timeout),
proxies=proxy_url, # httpx поддерживает прокси
)
async def request(self, token, client, method):
url = self.network.url(method)
headers = {
"Crypto-Pay-API-Token": token,
"Content-Type": "application/json",
}
data = method.model_dump_json(exclude_none=True)
resp = await self._client.post(url, content=data, headers=headers)
return self._check_response(client, method, resp.text)
# Использование:
cp = CryptoPay(
token="1234:TOKEN",
session=HttpxProxySession,
proxy_url="http://user:pass@proxy:8080",
)
💡 Альтернатива: кастомная сеть
Вместо прокси-сессии можно использовать кастомный Network с URL прокси-сервера. Если твой прокси просто перенаправляет запросы к API, то достаточно создать Network с базовым URL прокси, не меняя сессию:
from aiosend.client.network import Network
PROXY_NET = Network(
name="PROXY",
base="https://my-proxy.com/cryptobot/api/{method}",
)
cp = CryptoPay(token="TOKEN", network=PROXY_NET)
Настройка таймаутов
Таймаут определяет максимальное время ожидания ответа от сервера API. По умолчанию — 300 секунд. В кастомной сессии ты можешь настроить его более гибко.
| Вид таймаута | Описание | Рекомендация |
|---|---|---|
| connect | Время на установку соединения | 10-30 секунд |
| read | Время на чтение ответа | 30-300 секунд |
| pool | Время ожидания в пуле соединений | 10-30 секунд |
class CustomTimeoutSession(BaseSession):
"""Сессия с раздельными таймаутами."""
def __init__(self, network, timeout=300):
super().__init__(network, timeout)
# Используем timeout из конструктора как read timeout
# connect, write, pool — фиксированные
self._client = httpx.AsyncClient(
timeout=httpx.Timeout(
connect=15.0,
read=timeout, # наследуется от CryptoPay(timeout=...)
write=15.0,
pool=20.0,
),
)
async def request(self, token, client, method):
url = self.network.url(method)
headers = {
"Crypto-Pay-API-Token": token,
"Content-Type": "application/json",
}
data = method.model_dump_json(exclude_none=True)
resp = await self._client.post(url, content=data, headers=headers)
return self._check_response(client, method, resp.text)
Полный пример: выбор сессии
import asyncio
from aiosend import CryptoPay
# 1. Стандартная AiohttpSession (по умолчанию)
cp_aiohttp = CryptoPay(token="1234:TOKEN")
# 2. HttpxSession (кастомная)
cp_httpx = CryptoPay(
token="1234:TOKEN",
session=HttpxSession,
timeout=60,
)
# 3. RequestsSession (через потоки)
cp_requests = CryptoPay(
token="1234:TOKEN",
session=RequestsSession,
)
# 4. Сессия с прокси
cp_proxied = CryptoPay(
token="1234:TOKEN",
session=ProxiedAiohttpSession,
proxy="http://user:pass@proxy:8080",
)
async def check_all():
for name, cp in [
("Aiohttp", cp_aiohttp),
("Httpx", cp_httpx),
("Requests", cp_requests),
("Proxied", cp_proxied),
]:
me = await cp.get_me()
print(f"{name}: {me.name} (через {cp.session.__class__.__name__})")
asyncio.run(check_all())
Что важно запомнить
request().network и timeout.asyncio.to_thread().Практическое задание
Задание: Создай кастомную сессию на httpx
Напиши и протестируй кастомную сессию на httpx:
- Создай класс
MyHttpxSession, наследующий отBaseSession - Реализуй
__init__с созданиемhttpx.AsyncClient - Реализуй
request()с отправкой POST и обработкой через_check_response - Создай
CryptoPayс этой сессией - Вызови
get_me()для проверки
Подсказка:
import asyncio
import httpx
from aiosend import CryptoPay
from aiosend.client.session.base import BaseSession
class MyHttpxSession(BaseSession):
def __init__(self, network, timeout=300):
super().__init__(network, timeout)
self._client = httpx.AsyncClient(
timeout=httpx.Timeout(timeout),
)
async def request(self, token, client, method):
url = self.network.url(method)
headers = {
"Crypto-Pay-API-Token": token,
"Content-Type": "application/json",
}
data = method.model_dump_json(exclude_none=True)
resp = await self._client.post(url, content=data, headers=headers)
return self._check_response(client, method, resp.text)
async def main():
cp = CryptoPay(
token="YOUR_TOKEN",
session=MyHttpxSession,
)
me = await cp.get_me()
print(f"Успех: {me.name}")
asyncio.run(main())
Урок 7.3: Кастомные сессии
5 вопросов