URLからjpegをダウンロードして処理しようとしています。私の問題は、これらのURLが古く、信頼できない可能性があるため、証明書の検証が一部のURLで失敗することではありませんが、try...except...
SSLCertVerificationError
、私はまだトレースバックを取得します。
システム:Linux 4.17.14-Arch1-1-Arch、python 3.7.0-3、aiohttp 3.3.2
最小限の例:
import asyncio
import aiohttp
from ssl import SSLCertVerificationError
async def fetch_url(url, client):
try:
async with client.get(url) as resp:
print(resp.status)
print(await resp.read())
except SSLCertVerificationError as e:
print('Error handled')
async def main(urls):
tasks = []
async with aiohttp.ClientSession(loop=loop) as client:
for url in urls:
task = asyncio.ensure_future(fetch_url(url, client))
tasks.append(task)
return await asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
loop.run_until_complete(main(['https://images.photos.com/']))
出力:
SSL handshake failed on verifying the certificate
protocol: <asyncio.sslproto.SSLProtocol object at 0x7ffbecad8ac8>
transport: <_SelectorSocketTransport fd=6 read=polling write=<idle, bufsize=0>>
Traceback (most recent call last):
File "/usr/lib/python3.7/asyncio/sslproto.py", line 625, in _on_handshake_complete
raise handshake_exc
File "/usr/lib/python3.7/asyncio/sslproto.py", line 189, in feed_ssldata
self._sslobj.do_handshake()
File "/usr/lib/python3.7/ssl.py", line 763, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'images.photos.com'. (_ssl.c:1045)
SSL error in data received
protocol: <asyncio.sslproto.SSLProtocol object at 0x7ffbecad8ac8>
transport: <_SelectorSocketTransport closing fd=6 read=idle write=<idle, bufsize=0>>
Traceback (most recent call last):
File "/usr/lib/python3.7/asyncio/sslproto.py", line 526, in data_received
ssldata, appdata = self._sslpipe.feed_ssldata(data)
File "/usr/lib/python3.7/asyncio/sslproto.py", line 189, in feed_ssldata
self._sslobj.do_handshake()
File "/usr/lib/python3.7/ssl.py", line 763, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'images.photos.com'. (_ssl.c:1045)
Error handled
トレースバックは、イベントループの 例外ハンドラー を呼び出すSSLプロトコルのasyncioの実装によって生成されます。トランスポートとストリーミングインターフェースの間の相互作用の迷路を通じて、この例外はイベントループによってログに記録され、APIユーザーに伝達されます。それが起こる方法は次のとおりです:
SSLProtocol._on_handshake_complete
_ は、Non __handshake_exc
_を受信せず、それを(ハンドシェイクコンテキストで)「致命的なエラー」として扱います。つまり、_self._fatal_error
_を呼び出して戻ります。_fatal_error
_ イベントループの例外ハンドラを呼び出してエラーを記録します。ハンドラーは通常、キューに入れられたコールバックで発生する例外に対して呼び出されます。例外は伝搬する呼び出し元がなくなったため、トレースバックを標準エラーに記録し、例外がサイレントに渡されないようにします。しかしながら..._fatal_error
_は、プロトコルで_transport._force_close
_を呼び出す _connection_lost
_ を呼び出します。connection_lost
_ implementation は、ストリームリーダーの将来の結果として例外を設定し、それを待っているストリームAPIのユーザーに伝播します。同じ例外がイベントループによってログに記録され、_connection_lost
_に渡されるのはバグか機能かは明らかではありません。これは、_BaseProtocol.connection_lost
_が defined a no-op であることの回避策になる可能性があるため、追加のログにより、BaseProtocol
から単純に継承するプロトコルが機密情報である可能性を沈黙させないことが保証されますSSLハンドシェイク中に発生する例外。理由が何であれ、現在の動作はOPが経験する問題につながります。例外をキャッチしてもそれを抑制するには不十分であり、トレースバックは引き続き記録されます。
この問題を回避するには、例外ハンドラーをSSLCertVerificationError
を報告しないものに一時的に設定します。
_@contextlib.contextmanager
def suppress_ssl_exception_report():
loop = asyncio.get_event_loop()
old_handler = loop.get_exception_handler()
old_handler_fn = old_handler or lambda _loop, ctx: loop.default_exception_handler(ctx)
def ignore_exc(_loop, ctx):
exc = ctx.get('exception')
if isinstance(exc, SSLCertVerificationError):
return
old_handler_fn(loop, ctx)
loop.set_exception_handler(ignore_exc)
try:
yield
finally:
loop.set_exception_handler(old_handler)
_
_fetch_url
_のコードの周囲にwith suppress_ssl_exception_report()
を追加すると、不要なトレースバックが抑制されます。
上記は機能しますが、根本的な問題の回避策のように強く感じられ、APIの正しい使用法ではないので、トラッカーに バグレポート を提出しました。
不明な理由(バグ?)のため、aiohttpは例外がスローされる前でもエラー出力をコンソールに出力します。 contextlib.redirect_stderr を使用して、エラー出力を一時的にリダイレクトすることを回避できます。
import asyncio
import aiohttp
from ssl import SSLCertVerificationError
import os
from contextlib import redirect_stderr
async def fetch_url(url, client):
try:
f = open(os.devnull, 'w')
with redirect_stderr(f): # ignore any error output inside context
async with client.get(url) as resp:
print(resp.status)
print(await resp.read())
except SSLCertVerificationError as e:
print('Error handled')
# ...
P.S。クライアントをキャッチするために、より一般的な例外タイプ errors を使用できると思います。次に例を示します。
except aiohttp.ClientConnectionError as e:
print('Error handled')