问题描述
我有一个用Python编写的小应用程序,它曾经可以工作……直到昨天,突然它开始给我HTTPS连接错误。我不记得是否有更新,但是Python 2.7.3rc2和Python 3.2都失败了。
我在Google上进行了搜索,发现当人们在代理背后时会发生这种情况,但是我却没有(自从上次工作以来,我的网络没有任何变化)。我的系统的运行Windows和Python 2.7.2的计算机没有问题(在同一网络中)。
>>> url = 'https://www.mediafire.com/api/user/get_session_token.php'
>>> response = urllib2.urlopen(url).read()
File "/usr/lib/python2.7/urllib2.py", line 126, in urlopen
return _opener.open(url, data, timeout)
File "/usr/lib/python2.7/urllib2.py", line 400, in open
response = self._open(req, data)
File "/usr/lib/python2.7/urllib2.py", line 418, in _open
'_open', req)
File "/usr/lib/python2.7/urllib2.py", line 378, in _call_chain
result = func(*args)
File "/usr/lib/python2.7/urllib2.py", line 1215, in https_open
return self.do_open(httplib.HTTPSConnection, req)
File "/usr/lib/python2.7/urllib2.py", line 1177, in do_open
raise URLError(err)
urllib2.URLError: <urlopen error [Errno 8] _ssl.c:504: EOF occurred in violation of protocol>
怎么了?任何帮助表示赞赏。
PS .:较旧的python版本也不起作用,在我的系统中和USB的实时会话中均不起作用,但请在Ubuntu 11.10实时会话中起作用。
最佳思路
这似乎与在12.04中找到的OpenSSL版本增加了TLS 1.1和1.2支持有关。可以使用OpenSSL命令行工具重现连接失败:
$ openssl s_client -connect www.mediafire.com:443
CONNECTED(00000003)
140491065808544: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 320 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---
如果我强制连接使用带有-tls1
命令行参数的TLS 1.0,则连接成功。
我建议您在此处提交有关此问题的错误报告:
https://bugs.launchpad.net/ubuntu/+filebug
次佳思路
对于像我这样的python新手来说,这是覆盖httplib的最简单方法。在python脚本的顶部,包括以下几行:
import httplib
from httplib import HTTPConnection, HTTPS_PORT
import ssl
class HTTPSConnection(HTTPConnection):
"This class allows communication via SSL."
default_port = HTTPS_PORT
def __init__(self, host, port=None, key_file=None, cert_file=None,
strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
HTTPConnection.__init__(self, host, port, strict, timeout,
source_address)
self.key_file = key_file
self.cert_file = cert_file
def connect(self):
"Connect to a host on a given (SSL) port."
sock = socket.create_connection((self.host, self.port),
self.timeout, self.source_address)
if self._tunnel_host:
self.sock = sock
self._tunnel()
# this is the only line we modified from the httplib.py file
# we added the ssl_version variable
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)
#now we override the one in httplib
httplib.HTTPSConnection = HTTPSConnection
# ssl_version corrections are done
从这里开始,您可以像通常一样使用urllib或任何您使用的东西。
注意:这适用于python 2.7。对于python 3.x解决方案,您需要覆盖http.client中的HTTPSConnection类。我把它留给读者作为练习。 🙂
第三种思路
您可以通过修改HTTPSConnection对象来避免修改httplib.py文件:
import httplib, ssl, socket
conn = httplib.HTTPSConnection(URL.hostname)
sock = socket.create_connection((conn.host, conn.port), conn.timeout, conn.source_address)
conn.sock = ssl.wrap_socket(sock, conn.key_file, conn.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)
conn.request('POST', URL.path + URL.query)
仅当未定义connection.sock时,request方法才会创建一个新的套接字。创建自己的添加ssl_version参数将使请求方法使用它。然后,其他所有内容将照常运行。
我遇到了同样的问题,这对我有用。
问候
第四种思路
问题出在ssl
,它与HTTP无关,因此,如果可以修补ssl
,为什么要修补httplib
。以下代码应修复所有SSL套接字,包括但不限于HTTPS(适用于Python 2.6 +)(内置ssl
,未尝试使用pyopenssl
)。
import functools
import ssl
old_init = ssl.SSLSocket.__init__
@functools.wraps(old_init)
def ubuntu_openssl_bug_965371(self, *args, **kwargs):
kwargs['ssl_version'] = ssl.PROTOCOL_TLSv1
old_init(self, *args, **kwargs)
ssl.SSLSocket.__init__ = ubuntu_openssl_bug_965371
第五种思路
编辑httplib.py(在Linux上为/usr/lib/pythonX.X/httplib.py)
FIND HTTPSConnection类声明
class HTTPSConnection(HTTPConnection):
....
内部类代码CHANGE行
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
至
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)
然后httplib HTTPS请求应该可以工作
import httplib
from urlparse import urlparse
url = XXX
URL = urlparse(url)
connection = httplib.HTTPSConnection(URL.hostname)
connection.request('POST', URL.path + URL.query)
response = connection.getresponse()