AIOHTTP: Обнаружение тайм-аута DNS с пользовательскими серверами имен
Используя 'Client tracing', мы можем создать переменные dns_cache_miss и host_resolved , чтобы определить, было ли вызвано исключение в резольвере.
При использовании AIOHTTP для получения данных с веб-страницы в Интернете вы, вероятно, используете тайм-аут для ограничения максимального времени ожидания.
Если вы используете доменное имя, то IP-адрес должен быть разрешен. Без использования отдельного преобразователя вы зависите от базовой операционной системы. Любые ошибки распространяются на ваше приложение.
Я не хотел такой зависимости и указал серверы имен самостоятельно, используя AsyncResolver и TCPConnector.
Теперь предположим, что произойдет тайм-аут. Как узнать, вызван ли тайм-аут резольвером или соединением с удаленным сервером?
Проблема
Запрос AIOHTTP состоит из двух частей:
- Разрешить DNS
- Получение данных
|----------------- request ----------------->|
|---- resolve DNS --->|---- receive data --->|
| | |
----+---------------------+----------------------+---> t
start
С помощью AIOHTTP мы можем указать максимальное время для запроса. Когда это время истекает, возникает исключение TimeoutError .
Но это касается всего запроса. Нет отдельного исключения для таймаута резольвера. Опять же, как узнать, вызван ли тайм-аут резольвером DNS или удаленным сервером?
Трассировка клиента в помощь
К счастью, мы можем проследить поток выполнения запроса, прикрепив короутины слушателей к сигналам, предоставляемым экземпляром TraceConfig , который можно использовать в качестве параметра для конструктора ClientSession .
Если мы посмотрим на AIOHTTP 'Tracing Reference', см. ссылки ниже, и увеличим 'Connection acquiring' и 'DNS resolving', то увидим, что нам нужны следующие корутины:
- on_request_start
- on_dns_cache_miss
- on_dns_resolvehost_end
Если произошел тайм-аут, и 'on_dns_cache_miss' был вызван, а 'on_dns_resolvehost_end' не был вызван, то можно предположить, что тайм-аут вызван резольвером.
Для того чтобы запустить корутины, мы создаем объект TraceConfig и подключаем к нему корутины. Все, что мы делаем в этих корутинах, это измеряем время с начала запроса и сохраняем его в нашем словаре 'trace_result', передаваемом в качестве контекста, с начальным значением None:
trace_results = {
'on_dns_cache_hit': None,
'on_dns_cache_miss': None,
'on_dns_resolvehost_end': None,
}
Код
Когда возникает исключение, мы сначала проверяем, является ли ошибка TimeoutError. Если это так, мы проверяем, произошло ли исключение в резольвере, используя 'cache_miss' и 'host_resolved'. Выберите либо рабочий резолвер с серверами имен quad9.net, либо просто используйте какой-нибудь IP-адрес.
import asyncio
import aiohttp
from aiohttp.resolver import AsyncResolver
import socket
import sys
import traceback
class Runner:
def __init__(self):
pass
async def on_request_start(self, session, trace_config_ctx, params):
trace_config_ctx.start = asyncio.get_event_loop().time()
async def on_dns_cache_miss(self, session, trace_config_ctx, params):
elapsed = asyncio.get_event_loop().time() - trace_config_ctx.start
trace_config_ctx.trace_request_ctx['on_dns_cache_miss'] = elapsed
async def on_dns_resolvehost_end(self, session, trace_config_ctx, params):
elapsed = asyncio.get_event_loop().time() - trace_config_ctx.start
trace_config_ctx.trace_request_ctx['on_dns_resolvehost_end'] = elapsed
async def get_trace_config(self):
trace_config = aiohttp.TraceConfig()
trace_config.on_request_start.append(self.on_request_start)
trace_config.on_dns_cache_miss.append(self.on_dns_cache_miss)
trace_config.on_dns_resolvehost_end.append(self.on_dns_resolvehost_end)
trace_results = {
'on_dns_cache_hit': None,
'on_dns_cache_miss': None,
'on_dns_resolvehost_end': None,
}
return trace_config, trace_results
async def run(self, url):
# quad9.net dns server
resolver = AsyncResolver(nameservers=['9.9.9.9', '149.112.112.112'])
# ip address of www.example.com, using this causes a resolver timeout
resolver = AsyncResolver(nameservers=['93.184.216.34'])
connector = aiohttp.TCPConnector(
family=socket.AF_INET,
resolver=resolver,
)
trace_config, trace_results = await self.get_trace_config()
error = None
e_str = None
try:
async with aiohttp.ClientSession(
connector=connector,
timeout=aiohttp.ClientTimeout(total=.5),
trace_configs=[trace_config],
) as session:
async with session.get(
url,
trace_request_ctx=trace_results,
) as client_response:
html = await client_response.text()
except Exception as e:
print(traceback.format_exc())
error = type(e).__name__
e_str = str(e)
print('url = {}'.format(url))
print('error = {}'.format(type(e).__name__))
print('e_str = {}'.format(e_str))
print('e.args = {}'.format(e.args))
finally:
print('url = {}'.format(url))
for k, v in trace_results.items():
print('trace_results: {} = {}'.format(k, v))
dns_cache_miss = True if trace_results['on_dns_cache_miss'] else False
host_resolved = True if trace_results['on_dns_resolvehost_end'] else False
if error == 'TimeoutError':
if dns_cache_miss and not host_resolved:
error = 'DNSTimeoutError'
print('error = {}, e_str = {}'.format(error, e_str))
if __name__=='__main__':
# 'fast' website
url = 'http://www.example.com'
# 'slow' website
url = 'http://www.imdb.com'
runner = Runner()
loop = asyncio.get_event_loop()
loop.run_until_complete(runner.run(url))
Другие ошибки резолвера
Есть и другие ошибки резолвера, и AIOHTTP здесь нам тоже не поможет, например:
- Could not contact DNS servers
- ConnectionRefusedError
Первый, конечно, имеет отношение к резолверу, но ConnectionRefusedError, может возникнуть из-за обоих действий в запросе.
Резюме
Я хочу знать, является ли возникшее исключение результатом работы резольвера или другой части запроса. Если это резольвер, то я могу пометить этот резольвер (временно) недействительным и использовать другой.
Я надеялся, что исключения AIOHHTP дадут мне всю информацию, но это оказалось не так. Возможно, когда-нибудь это будет реализовано, но пока что я должен делать грязную работу сам. Кроме того, AIOHTTP - очень хороший пакет!
Ссылки / кредиты
AIOHTTP - Client exceptions
https://docs.aiohttp.org/en/stable/client_reference.html?highlight=exceptions#client-exceptions
AIOHTTP - Tracing Reference
https://docs.aiohttp.org/en/stable/tracing_reference.html
Monitoring network calls in Python using TIG stack
https://calendar.perfplanet.com/2020/monitoring-network-calls-in-python-using-tig-stack
Недавний
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
- Don't Repeat Yourself (DRY) с Jinja2
- SQLAlchemy, PostgreSQL, максимальное количество строк для user
- Показать значения в динамических фильтрах SQLAlchemy
- Безопасная передача данных с помощью шифрования Public Key и pyNaCl
- rqlite: альтернатива dist с высокой степенью готовности и SQLite
Большинство просмотренных
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- Подключение к службе на хосте Docker из контейнера Docker
- Использование PyInstaller и Cython для создания исполняемого файла Python
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов
- Flask Удовлетворительный запрос API проверка параметров запроса с помощью схем Маршмэллоу