AIOHTTP: Erkennung von DNS-Zeitüberschreitungen mit benutzerdefinierten Nameservern
Mit 'Client tracing' können wir die Variablen dns_cache_miss und host_resolved erzeugen, um festzustellen, ob im Resolver eine Ausnahme ausgelöst wurde.
Wenn Sie AIOHTTP verwenden, um Daten von einer Webseite im Internet abzurufen, verwenden Sie wahrscheinlich einen Timeout, um die maximale Wartezeit zu begrenzen.
Wenn Sie einen Domänennamen verwenden, muss die IP-Adresse aufgelöst werden. Ohne die Verwendung eines separaten Resolvers sind Sie vom zugrunde liegenden Betriebssystem abhängig. Jeder Fehler überträgt sich auf Ihre Anwendung.
Ich wollte diese Abhängigkeit nicht und habe die Nameserver selbst angegeben, indem ich AsyncResolver und TCPConnector verwendet habe.
Nehmen wir nun an, dass eine Zeitüberschreitung auftritt. Woher wissen wir, ob die Zeitüberschreitung durch den Resolver oder durch die Remote-Server-Verbindung verursacht wird?
Das Problem
Die Anfrage AIOHTTP besteht aus zwei Teilen:
- DNS auflösen
- Empfangen von Daten
|----------------- request ----------------->|
|---- resolve DNS --->|---- receive data --->|
| | |
----+---------------------+----------------------+---> t
start
Mit AIOHTTP können wir eine maximale Zeit für die Anfrage angeben. Wenn diese Zeit abläuft, wird eine TimeoutError -Ausnahme ausgelöst.
Diese bezieht sich jedoch auf die gesamte Anfrage. Es gibt keine separate Ausnahme für eine Zeitüberschreitung des Resolvers. Woher wissen wir also, ob die Zeitüberschreitung durch den DNS-Resolver oder durch den Remote-Server verursacht wird?
Client-Tracing zur Rettung
Glücklicherweise können wir den Ausführungsfluss einer Anfrage verfolgen, indem wir Listener-Coroutinen an die von der Instanz TraceConfig bereitgestellten Signale anhängen, die als Parameter für den Konstruktor ClientSession verwendet werden können.
Wenn wir uns die AIOHTTP 'Tracing Reference' ansehen, siehe Links unten, und auf 'Connection acquiring' und 'DNS resolving' zoomen, dann sehen wir, dass wir die folgenden Coroutinen benötigen:
- on_request_start
- on_dns_cache_miss
- on_dns_resolvehost_end
Wenn eine Zeitüberschreitung auftritt und 'on_dns_cache_miss' aufgerufen wurde und 'on_dns_resolvehost_end' nicht, dann können wir davon ausgehen, dass die Zeitüberschreitung durch den Resolver verursacht wurde.
Um die Coroutines zum Laufen zu bringen, erstellen wir ein TraceConfig -Objekt und hängen die Coroutines an. Alles, was wir in diesen Coroutines tun, ist die Zeit seit dem Start der Anfrage zu messen und diese in unserem 'trace_result'-Verzeichnis zu speichern, das als Kontext weitergegeben wird, mit den Anfangswerten None:
trace_results = {
'on_dns_cache_hit': None,
'on_dns_cache_miss': None,
'on_dns_resolvehost_end': None,
}
Der Code
Wenn eine Ausnahme ausgelöst wird, prüfen wir zunächst, ob der Fehler ein TimeoutError ist. Wenn dies der Fall ist, prüfen wir, ob die Ausnahme im Resolver aufgetreten ist, indem wir 'cache_miss' und 'host_resolved' verwenden. Wählen Sie entweder den funktionierenden Resolver mit Nameservern von quad9.net, oder verwenden Sie einfach eine IP-Adresse.
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))
Weitere Resolver-Fehler
Es gibt noch mehr Resolver-Fehler und AIOHTTP hilft uns auch hier nicht weiter, zum Beispiel:
- Could not contact DNS servers
- ConnectionRefusedError
Die erste hat sicherlich mit dem Resolver zu tun, aber die ConnectionRefusedError kann von beiden Aktionen in der Anfrage herrühren.
Zusammenfassung
Ich möchte wissen, ob eine ausgelöste Ausnahme vom Resolver oder von einem anderen Teil der Anfrage stammt. Wenn es der Resolver ist, dann kann ich diesen Resolver (vorübergehend) als ungültig markieren und einen anderen verwenden.
Ich hatte gehofft, die AIOHHTP-Ausnahmen würden mir alle Informationen liefern, aber das scheint nicht der Fall zu sein. Vielleicht wird es eines Tages implementiert, aber im Moment muss ich die Drecksarbeit selbst machen. Abgesehen davon ist AIOHTTP ein sehr schönes Paket!
Links / Impressum
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
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas