异步 aiohttp 请求失败,但同步请求成功
asynchronous aiohttp requests fails, but synchronous requests succeed
当我使用异步 aiohttp
时,使用以下代码我得到 Cannot connect to host ...:443 ssl:True
。当我使用同步 requests
时,它成功了。
whitehouse.gov
链接失败,但 google.com
对于异步和同步情况都成功。
出了什么问题?这是在 FreeBSD8 上使用 python 3.4.2,aiohttp 0.14.4,请求 2.5.3
import asyncio
import aiohttp
import requests
urls = [
'http://www.whitehouse.gov/cea/',
'http://www.whitehouse.gov/omb',
'http://www.google.com']
def test_sync():
for url in urls:
r = requests.get(url)
print(r.status_code)
def test_async():
for url in urls:
try:
r = yield from aiohttp.request('get', url)
except aiohttp.errors.ClientOSError as e:
print('bad eternal link %s: %s' % (url, e))
else:
print(r.status)
if __name__ == '__main__':
print('async')
asyncio.get_event_loop().run_until_complete(test_async())
print('sync')
test_sync()
这个输出是:
async
bad eternal link http://www.whitehouse.gov/cea: Cannot connect to host www.whitehouse.gov:443 ssl:True
bad eternal link http://www.whitehouse.gov/omb: Cannot connect to host www.whitehouse.gov:443 ssl:True
200
sync
200
200
200
我怀疑您机器上的证书验证链已损坏。
正如@dano 提到的那样,在 Ubuntu 上一切正常。
无论如何,您可以通过创建自定义 Connector
实例来禁用 ssl 验证:
import asyncio
import aiohttp
urls = [
'http://www.whitehouse.gov/cea/',
'http://www.whitehouse.gov/omb',
'http://www.google.com']
def test_async():
connector = aiohttp.TCPConnector(verify_ssl=False)
for url in urls:
try:
r = yield from aiohttp.request('get', url, connector=connector)
except aiohttp.errors.ClientOSError as e:
print('bad eternal link %s: %s' % (url, e))
else:
print(r.status)
if __name__ == '__main__':
print('async')
asyncio.get_event_loop().run_until_complete(test_async())
顺便说一句,requests
库附带了自己的证书包。也许我们需要为 aiohttp 做同样的事情?
不要使用此答案,因为它可能等同于禁用证书检查。
正如 Le Hibou 在评论中指出的那样,ssl.Purpose.CLIENT_AUTH
用于在服务器端对客户端进行身份验证。
This value indicates that the context may be used to authenticate Web clients (therefore, it will be used to create server-side sockets).
查看 create_default_context()
的代码表明在这种情况下证书检查可能被禁用或至少是可选的。
我有同样的错误消息(在 Windows 上)并用以下方法解决了它:
import aiohttp
import ssl
client = aiohttp.ClientSession()
client.post(
'https://some.foo/bar/',
json={"foo": "bar"},
ssl=ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH))
这是使用 request() to set a different SSL context than the default. The default is ssl.create_default_context() 的 ssl
参数。
问题是 ssl.create_default_context()
的默认值是:
ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)
在客户端验证服务器证书时,Purpose.SERVER_AUTH
似乎不起作用。作为客户端验证时,请改为使用 Purpose.CLIENT_AUTH
。
我在 CA 根证书已过期的旧 Linux 服务器上遇到了同样的问题,并且在 SSLContext
中加载 certifi CA 证书包解决了这个问题。
import aiohttp
import ssl
import certifi
ssl_context = ssl.create_default_context(cafile=certifi.where())
async with aiohttp.ClientSession() as session:
async with session.get('https://some.foo/bar/', ssl=ssl_context) as response:
print(await response.text())
这对我有用:
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
当我使用异步 aiohttp
时,使用以下代码我得到 Cannot connect to host ...:443 ssl:True
。当我使用同步 requests
时,它成功了。
whitehouse.gov
链接失败,但 google.com
对于异步和同步情况都成功。
出了什么问题?这是在 FreeBSD8 上使用 python 3.4.2,aiohttp 0.14.4,请求 2.5.3
import asyncio
import aiohttp
import requests
urls = [
'http://www.whitehouse.gov/cea/',
'http://www.whitehouse.gov/omb',
'http://www.google.com']
def test_sync():
for url in urls:
r = requests.get(url)
print(r.status_code)
def test_async():
for url in urls:
try:
r = yield from aiohttp.request('get', url)
except aiohttp.errors.ClientOSError as e:
print('bad eternal link %s: %s' % (url, e))
else:
print(r.status)
if __name__ == '__main__':
print('async')
asyncio.get_event_loop().run_until_complete(test_async())
print('sync')
test_sync()
这个输出是:
async
bad eternal link http://www.whitehouse.gov/cea: Cannot connect to host www.whitehouse.gov:443 ssl:True
bad eternal link http://www.whitehouse.gov/omb: Cannot connect to host www.whitehouse.gov:443 ssl:True
200
sync
200
200
200
我怀疑您机器上的证书验证链已损坏。 正如@dano 提到的那样,在 Ubuntu 上一切正常。
无论如何,您可以通过创建自定义 Connector
实例来禁用 ssl 验证:
import asyncio
import aiohttp
urls = [
'http://www.whitehouse.gov/cea/',
'http://www.whitehouse.gov/omb',
'http://www.google.com']
def test_async():
connector = aiohttp.TCPConnector(verify_ssl=False)
for url in urls:
try:
r = yield from aiohttp.request('get', url, connector=connector)
except aiohttp.errors.ClientOSError as e:
print('bad eternal link %s: %s' % (url, e))
else:
print(r.status)
if __name__ == '__main__':
print('async')
asyncio.get_event_loop().run_until_complete(test_async())
顺便说一句,requests
库附带了自己的证书包。也许我们需要为 aiohttp 做同样的事情?
不要使用此答案,因为它可能等同于禁用证书检查。
正如 Le Hibou 在评论中指出的那样,ssl.Purpose.CLIENT_AUTH
用于在服务器端对客户端进行身份验证。
This value indicates that the context may be used to authenticate Web clients (therefore, it will be used to create server-side sockets).
查看 create_default_context()
的代码表明在这种情况下证书检查可能被禁用或至少是可选的。
我有同样的错误消息(在 Windows 上)并用以下方法解决了它:
import aiohttp
import ssl
client = aiohttp.ClientSession()
client.post(
'https://some.foo/bar/',
json={"foo": "bar"},
ssl=ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH))
这是使用 request() to set a different SSL context than the default. The default is ssl.create_default_context() 的 ssl
参数。
问题是 ssl.create_default_context()
的默认值是:
ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)
在客户端验证服务器证书时,Purpose.SERVER_AUTH
似乎不起作用。作为客户端验证时,请改为使用 Purpose.CLIENT_AUTH
。
我在 CA 根证书已过期的旧 Linux 服务器上遇到了同样的问题,并且在 SSLContext
中加载 certifi CA 证书包解决了这个问题。
import aiohttp
import ssl
import certifi
ssl_context = ssl.create_default_context(cafile=certifi.where())
async with aiohttp.ClientSession() as session:
async with session.get('https://some.foo/bar/', ssl=ssl_context) as response:
print(await response.text())
这对我有用:
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())