Python 请求 requests.exceptions.SSLError: [Errno 8] _ssl.c:504: EOF 发生违反协议

Posted

技术标签:

【中文标题】Python 请求 requests.exceptions.SSLError: [Errno 8] _ssl.c:504: EOF 发生违反协议【英文标题】:Python Requests requests.exceptions.SSLError: [Errno 8] _ssl.c:504: EOF occurred in violation of protocol 【发布时间】:2012-12-15 15:52:09 【问题描述】:

我在 Ubuntu 12.10 上使用 OpenSSL 1.0.1c、python 2.7.3、Requests 1.0.3 和 1.0.4(都尝试过),并且在尝试使用以下 url 变量连接到网站时代码。

def SendInitialRequest(xmlmessage, redirecturl):
    url = 'https://centineltest.cardinalcommerce.com/maps/txns.asp'

    payload = 'cmpi_msg=' + ET.tostring(xmlmessage)
    headers = 
        'Content-Type': 'application/x-www-form-urlencoded',
    
    r = requests.post(url, data=payload, headers=headers, verify=None)
    print r.text

它会抛出以下错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "clams/libs/centinel/thinclient.py", line 134, in SendInitialRequest
    r = requests.post(url, data=payload, headers=headers, verify=None)
  File "/home/jasonamyers/.virtualenv/clams/lib/python2.7/site-packages/requests/api.py", line 87, in post
    return request('post', url, data=data, **kwargs)
  File "/home/jasonamyers/.virtualenv/clams/lib/python2.7/site-packages/requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "/home/jasonamyers/.virtualenv/clams/lib/python2.7/site-packages/requests/sessions.py", line 269, in request
    resp = self.send(prep, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
  File "/home/jasonamyers/.virtualenv/clams/lib/python2.7/site-packages/requests/sessions.py", line 364, in send
    r = adapter.send(request, **kwargs)
  File "/home/jasonamyers/.virtualenv/clams/lib/python2.7/site-packages/requests/adapters.py", line 163, in send
    raise SSLError(e)
requests.exceptions.SSLError: [Errno 8] _ssl.c:504: EOF occurred in violation of protocol

尝试使用 openssl 连接会返回以下内容:

$ openssl s_client -connect centineltest.cardinalcommerce.com:443
CONNECTED(00000003)
140019346777760:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:177:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 226 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---

如果我强制它使用 tls1 它可以工作(输出被截断):

$ openssl s_client -tls1 -connect centineltest.cardinalcommerce.com:443
CONNECTED(00000003)
depth=2 C = US, O = "thawte, Inc.", OU = Certification Services Division, OU
verify error:num=20:unable to get local issuer certificate
verify return:0
---

为此,我见过很多bug reports;但是,我还没有找到使用 python requests 库解决它的方法。任何帮助将不胜感激。

【问题讨论】:

你是通过包管理器安装openssl的吗?如果是,您是否检查了更新?我可以使用请求连接到该站点。 我确实通过包管理器安装了 openssl,没有更新。你能分享你正在使用的所有东西的版本吗?你是使用内置的python还是使用pythonbrews等来构建你自己的? 就我而言,答案是这样的:***.com/a/34891294/5953624 我在尝试使用 HTTPS 连接到 HTTP 端点时遇到此错误。我刚刚更改了网址,它工作正常。 【参考方案1】:

从the requests issue page 转发给其他人:

Requests' 在版本 1 之前不支持这样做。在版本 1 之后,您需要继承 HTTPAdapter,如下所示:

from requests.adapters import HTTPAdapter
from requests.packages.urllib3.poolmanager import PoolManager
import ssl

class MyAdapter(HTTPAdapter):
    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(num_pools=connections,
                                       maxsize=maxsize,
                                       block=block,
                                       ssl_version=ssl.PROTOCOL_TLSv1)

完成后,您可以这样做:

import requests
s = requests.Session()
s.mount('https://', MyAdapter())

通过该会话对象的任何请求都将使用 TLSv1。

【讨论】:

为了保持这个答案是最新的,我最终写了这个here。自发布此答案以来,子类 API 发生了一些变化,因此您应该使用该博客文章中的代码,而不是此答案中的代码。 =) @Lukasa 的链接似乎已离线,这是一个 archive'ed version【参考方案2】:

设置 verify=False 只会跳过验证服务器证书,但无助于解决 SSL 协议错误。

此问题可能是由于 Web 服务器上禁用了 SSLv2,但 Python 2.x 默认尝试与 PROTOCOL_SSLv23 建立连接。这发生在https://github.com/python/cpython/blob/360aa60b2a36f5f6e9e20325efd8d472f7559b1e/Lib/ssl.py#L1057

您可以通过覆盖 ssl_version 关键字参数来对 ssl 模块中的 ssl.wrap_socket() 进行猴子补丁。以下代码可以按原样使用。在发出任何请求之前,将其放在程序的开头。

import ssl
from functools import wraps
def sslwrap(func):
    @wraps(func)
    def bar(*args, **kw):
        kw['ssl_version'] = ssl.PROTOCOL_TLSv1
        return func(*args, **kw)
    return bar

ssl.wrap_socket = sslwrap(ssl.wrap_socket)

【讨论】:

非常感谢您提供这个猴子补丁 - 在使用请求库时,我一直在努力寻找设置 ssl_version 的方法 :) 像魅力一样工作!但是这种变化的范围是什么?只有我的程序设置为使用 TLS_1 还是全局的? 只要代码在任何 Python 调用中运行过,更改就会生效。如果您运行另一个不调用此代码的 Python 脚本/程序,则没有任何变化。您可以将代码放入它自己的模块(.py 文件)中,并将此文件导入任何需要此覆盖的脚本中。 @MA1,请提供有关您面临的问题的更多详细信息。 @chnrx 在这种情况下,python 不会尝试独占使用 SSLv2,它只是尝试发送与 SSLv2 兼容的 ClientHello 消息,该消息可能最终使用 SSLv2,但也可以使用更高版本。服务器可以禁用 SSLv2 支持,同时仍允许与 SSLv2 兼容的 ClientHello 消息启动 SSL 协商。但越来越多的情况是,服务器现在也禁止接受与 SSLv2 兼容的 ClientHello 消息。【参考方案3】:

requests 安装“安全”包附加功能为我解决了:

sudo apt-get install libffi-dev
sudo pip install -U requests[security]

【讨论】:

这修复了 OSX 中带有条带 SDK 的糟糕 SSL 协议错误问题,我不知道我是如何到达这里的,但我正在为未来的我添加一些额外的谷歌关键字。 SSLError: EOF 发生违反协议 (_ssl.c:590)) 该方法解决了我的问题,在我的 Ubuntu(WSL) 和 Windows 10 上。 在 Beaglebone 上,我必须首先 sudo apt-get install python3-dev libssl-dev 才能完成 requests[security] 安装。它仍然挂了几分钟,但最终成功了。唉,这对requests SSL 错误没有影响。 只是“pip install -U requests[security]”解决了我的问题。【参考方案4】:

这是一个已知的错误,您可以通过 hack 解决它:

打开site-packages/requests/packages/urllib3/connectionpool.py(或者只是在您自己的项目中制作请求的本地副本),并更改以下块:

def connect(self):
    # Add certificate verification
    sock = socket.create_connection((self.host, self.port), self.timeout)

    # Wrap socket using verification with the root certs in
    # trusted_root_certs
    self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file,
                                cert_reqs=self.cert_reqs,
                                ca_certs=self.ca_certs,
                                server_hostname=self.host,
                                ssl_version=self.ssl_version)

到:

def connect(self):
    # Add certificate verification
    sock = socket.create_connection((self.host, self.port), self.timeout)

    # Wrap socket using verification with the root certs in
    # trusted_root_certs
    self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file,
                                cert_reqs=self.cert_reqs,
                                ca_certs=self.ca_certs,
                                server_hostname=self.host,
                                ssl_version=ssl.PROTOCOL_TLSv1)

否则,我想在某个地方有一个不那么骇人听闻的替代,但我看了几眼就找不到。

注意:在旁注中,来自 MacOS 上 PIP (1.0.4) 的 requests 仅适用于您提供的 URL。

【讨论】:

我也让它在 MacOS 上完美运行,只是担心生产,而且当 Apple 更新 OpenSSL 时可能并非总是如此。非常感谢您的回复。看看我是否真的可以将这样的自定义包推到heroku :) @jasonamyers:好吧,你可以让它成为你项目的一部分,只需将 requests 文件夹复制到你自己的项目中,import requests 会更喜欢你的补丁版本而不是库存版本。跨度> 真的试图避免为许多其他连接中的一个连接这样做:(我会继续挖掘,感谢您的回复。 我又深入了解了一下,ssl_version 无法从 requests 模块的顶部被覆盖,而无需修改模块本身:( 您引用的代码是connection.py 而不是connectionpool.py,至少在我的请求版本(2.5.3)中。【参考方案5】:

我遇到了这个错误,修复似乎是关闭了 Python 2.7 不支持的 SNI:

http://bugs.python.org/issue5639

urllib3 on python 2.7 SNI error on Google App Engine

【讨论】:

【参考方案6】:

我遇到了同样的问题:

引发 SSLError(e) requests.exceptions.SSLError: [Errno 8] _ssl.c:504: EOF 发生违反协议

我有 fiddler 正在运行,我停止了 fiddler 捕获并且没有看到这个错误。可能是因为提琴手。

【讨论】:

【参考方案7】:

致无法解决上述问题的人。

不得不更改文件 ssl.py 来修复它。 查找函数 create_default_context 并更改行:

context = SSLContext(PROTOCOL_SSLv23)

context = SSLContext(PROTOCOL_TLSv1)

也许有人可以在不编辑 ssl.py 的情况下创建更简单的解决方案?

【讨论】:

【参考方案8】:

不幸的是,接受的答案对我不起作用。作为临时解决方法,您也可以在连接到安全网站时使用 verify=False

来自Python Requests throwing up SSLError

requests.get('https://example.com', verify=True)

【讨论】:

设置verify=False 是个坏主意。你需要找出真正的问题并修复它。【参考方案9】:

我遇到了类似的问题,我认为如果我们简单地忽略 ssl 验证将像魅力一样发挥作用,因为它对我有用。因此使用https 方案连接到服务器,但指示他们不要验证证书。

使用requests。只需提及verify=False 而不是None

    requests.post(url, data=payload, headers=headers, verify=False)

希望这对需要的人有用:)。

【讨论】:

【参考方案10】:

我在通过 TLS 连接到 RabbitMQ MQTT 服务器时遇到此错误。我很确定服务器坏了,但无论如何它可以与 OpenSSL 1.0.1 一起使用,但 不是 OpenSSL 1.0.2。

您可以使用以下方法在 Python 中检查您的版本:

import ssl
ssl.OPENSSL_VERSION

除了使用旧版本的 Python 之外,我不确定如何在 Python 中降级 OpenSSL(至少在 Windows 上似乎是静态链接的)。

【讨论】:

以上是关于Python 请求 requests.exceptions.SSLError: [Errno 8] _ssl.c:504: EOF 发生违反协议的主要内容,如果未能解决你的问题,请参考以下文章

Python语言基础——Python 请求模块

ajax如何请求python接口

Python爬虫知识点——请求

python http请求方法整理

python 怎么处理http post 的请求参数

Python使用requests发送请求