异步aiohttp请求失败,但同步请求成功

14
使用以下代码时,我在使用异步aiohttp时得到无法连接到主机...:443 ssl:True的错误。但是当我使用同步requests时,它成功了。 whitehouse.gov链接失败,但是google.com的情况下异步和同步都成功。
出了什么问题?这是在FreeBSD8上使用Python 3.4.2、aiohttp 0.14.4、requests 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

这对我来说在Ubuntu机器上无法重现,就算有价值也是如此。 - dano
4个回答

18

我怀疑您的计算机上证书验证链出现问题。正如@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做同样的事情?

更新。 另请参阅https://github.com/aio-libs/aiohttp/issues/341


2
正是问题所在,谢谢。如果我没有巧妙地隐藏在try/except后面,我本可以得到更多的信息来帮助解决问题:ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:600)。你关闭验证的解决方案很管用。 - Tim
@Tim,异常的完整信息指向异常链中的错误源,不是吗? - Andrew Svetlov
是的 - 没有 try/except,所有正确的信息都会显示出来。我用 try/except 掩盖了错误,只得到了我的异常文本 + "无法连接到 www.whitehouse.gov:443"。如果我进行了裸连接,我就会看到完整的回溯 - 从 ssl.SSLError 开始,以 aiohttp.errors.ClientOSError 结束。吸取教训(在开发代码时不要掩盖异常!) - Tim
3
当在TCPConnector中设置ssl=False时,我经常会收到相同的异常。 - Brad Solomon

6

我在一个旧的Linux服务器上遇到了同样的问题,由于根证书已过期,加载certifi CA证书捆绑包到SSLContext中解决了这个问题。

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())

唯一对我有用的答案,而且我的代码已经准备好插入它了。谢谢。 - OneMadGypsy

0
这对我有效:
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

0

不要使用此答案,因为它可能等同于禁用证书检查。

正如Le Hibou在评论中指出的那样,ssl.Purpose.CLIENT_AUTH用于在服务器端对客户端进行身份验证。

此值表示上下文可用于验证Web客户端(因此,它将用于创建服务器端套接字)。

查看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()ssl 参数来设置不同于默认值的 SSL 上下文。默认值为 ssl.create_default_context()

问题在于 ssl.create_default_context() 的默认值为:

ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)

在客户端验证服务器证书时,Purpose.SERVER_AUTH 似乎无法正常工作。请改用 Purpose.CLIENT_AUTH


这很令人困惑...根据Python标准库文档Purpose.CLIENT_AUTH用于创建服务器端套接字。 - Le Hibou
好的,我忽略了那个。看一下create_default_context()的源代码,似乎使用ssl.Purpose.CLIENT_AUTH可以禁用或选择性地进行证书检查。 - Tillmann Radmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接