【问题标题】:python requests does not work for https from apache with wsgipython请求不适用于来自带有wsgi的apache的https
【发布时间】:2024-01-20 08:17:01
【问题描述】:

当我尝试使用请求加载外部 URL 的 django 视图时,我得到一个“模块”对象没有属性“create_connection”。但是,当我使用 urllib2 或交互式 shell 中的相同请求代码时,它可以工作。

我的环境:

  • Python 2.5.2
  • 请求 0.10.0(我正在使用需要此版本的 3rd 方 api)
  • Apache 和 WSGI 在我的 django 站点的 virtualenv 中
  • Django 1.4.1
  • Debian Linux 5
  • 我没有 SELinux(或任何类似的安全性)

我实际上为完全不同的功能使用了 2 个不同的 API。他们都需要请求,他们都给出了这个错误:

Exception Type: AttributeError
Exception Value: 'module' object has no attribute 'create_connection'
Exception Location: /my/virtualenv/dir/lib/python2.5/site-packages/requests/packages/urllib3/connectionpool.py in connect, line 67

异常中提到的行是:

sock = socket.create_connection((self.host, self.port), self.timeout)

貌似python 2.5自带的socket版本没有create_connection方法(2.6添加的)。但是,我尝试从 virtualenv 中的 python 交互式 shell 运行完全相同的代码,一切正常。此外,请求 0.10.0 应该适用于 python 2.5。

我创建了以下 2 个测试视图,因为我怀疑请求是问题的一部分:

def get_requests(request):
    import requests
    r = requests.get("https://google.ca")
    return HttpResponse(r.text)

def get_urllib(request):
    import urllib2
    r = urllib2.urlopen('https://google.ca')
    return HttpResponse(r.read())

urllib 视图有效,请求视图给出了与上述相同的错误。

urllib 工作的事实向我表明 Apache 有权连接到互联网(这不是防火墙问题)。

我在尝试视图时执行了 tcpdump,请求甚至从未尝试连接。

有什么想法吗?请不要建议使用请求以外的其他内容,因为我正在使用需要它的 2 个不同的 3rd 方 API。

谢谢。

【问题讨论】:

  • 您确定您在 shell 上运行的 Python 2.5 与从 Apache 运行的 Python 2.5 具有相同的可执行文件、相同的 stdlib 和站点包吗?特别是,如果 virtualenv 是独立的,请确保 socket.pyssl.py 与您的系统(或 apache)stdlib 中的文件相同,并且 requests/packages/urllib3/connectionpool.py 与您的系统(或 apache)站点中的文件相同 -包。
  • 另外,您是如何在 2.5 中获得 SSL 的?你是pip-2.5 install ssl,还是安装了 PyPI backport 包?安装 Debian python-ssl 包?它是否带有某种定制的 Python 2.5?
  • 我刚刚检查过,shell 和 apache 正在运行相同的 python 和相同的 socket.py 和请求文件。我从未安装过 python ssl。
  • 如果您从未安装过 ssl,我不明白您的示例代码是如何工作的——除非它通过忽略 https 并将其请求为纯 http 来工作?
  • 我刚刚安装了 ssl 使用:'pip-2.5 install ssl',现在它在交互式 shell 中出现了同样的错误。这很奇怪,因为使用 tcpdump,我知道交互式 shell 使用的是 https。我不明白为什么 apache 没有安装 ssl 就失败了,但是我安装后 shell 才失败。

标签: python django python-requests


【解决方案1】:

看起来您在安装了ssl 模块的 Python 2.5 中使用 HTTPS 的请求 0.10.0(或者,实际上是在 urllib3 中)遇到了错误。

如果您通过0.10.0 source 进行跟踪,您可以看到如果安装了ssl 并且您发出HTTPS 请求,您将进入VerifiedHTTPSConnection.connect 方法。 HTTPSConnectionPool 源中的 cmets 也对此进行了解释。但是你甚至不需要追溯源头,因为你已经从追溯中看到了这一点。

如果你查看 the source 到那个方法,它会无条件地调用 socket.create_connection,这在 2.5 中肯定会失败。

任何人修复此错误的可能性非常小。看起来它是在 0.10.0 中引入的,而 0.10.1 仅通过放弃 2.5 支持来解决它。 (我对此并不肯定,因为我在错误跟踪器中找不到它。)

那么,你能做些什么呢?

首先,请注意,虽然create_connectionconnect“更高级别”,但真正的优势是它会在决定创建哪种套接字之前先进行名称查找。如果您知道自己只关心 IPv4,则可以将其替换为:

self.sock = socket.socket()
self.sock.settimeout(self.timeout)
self.sock.connect((self.host, self.port))

如果你关心 IPv6,你可以借用 2.6 code 代替 create_connection

所以,你有几个选择:

  • fork 源并修补 urllib3.connectionpool.VerifiedHTTPConnection.connect 以使用解决方法而不是 create_connection
  • Monkeypatch urllib3.connectionpool.VerifiedHTTPConnection.connect 在运行时。
  • Monkeypatch socket 在运行时添加 create_connection 实现。

但是,鉴于历史,我不想保证 0.10.0 不会对 Python 2.5 产生进一步的问题。

【讨论】:

【解决方案2】:

从 0.10.1 开始,requests 不再支持 Python 2.5。

升级你的 Python 或使用 requests 0.10.0 版本的 requests。

0.10.1 (2012-01-23)

Python 3 支持!放弃 2.5 支持。 (向后不兼容)

【讨论】:

  • 我使用的是 0.10.0,由于第 3 方的要求,我无法升级它。而且升级 python 是一个更大的项目,需要大量的时间和测试。
【解决方案3】:

使用请求 0.9.3。这肯定适用于 Python 2.5。我们需要 Python 2.5 支持,要么 Kenneth 告诉我们使用那个版本,要么那是我们发现在实践中实际工作的最新版本。如果您需要重新使用该版本,请注意任何 API 差异。

【讨论】:

  • 我已经尝试将每个版本都安装回 0.8.9 并且它们都给出了相同的错误(并且它们都有 socket.create_connection 方法)。这是来自版本 0.8.2 github.com/kennethreitz/requests/blob/… 的源代码如您所见,它仍然存在。
  • 请参阅github.com/kennethreitz/requests/blob/… 这表明这是 urllib3 中的一个真正问题,您只是触发它,因为您为 Python 2.5 构建并安装了 ssl 模块,这导致启用证书验证。您是否特别想要进行证书验证?如果没有,请不要通过请求模块启用证书验证。
【解决方案4】:

作为 abarnert pointed out,这是由请求 0.10.0 中的错误引起的,由于版本 0.10.1 中删除了对 python 2.5 的支持,因此无法修复。

所以我编辑了这个文件 requests/packages/urllib3/connectionpool.py(在第 67 行)。

原行:

    sock = socket.create_connection((self.host, self.port), self.timeout)

我将其替换为:

    try:
        sock = socket.create_connection((self.host, self.port), self.timeout)
    except AttributeError:
        # python 2.5 fix
        sock = socket.socket()
        if self.timeout is not None:
            sock.settimeout(self.timeout)
        sock.connect((self.host, self.port))

有了这个改变,一切都正常了。

【讨论】:

    最近更新 更多