某些站点的Python HTTPS请求(urllib2)在没有代理的Ubuntu 12.04上失败

我有一个小应用程序,我用Python编写,它曾经工作……直到昨天,它突然开始在HTTPS连接中给我一个错误。 我不记得是否有更新,但Python 2.7.3rc2和Python 3.2都失败了。

我搜索了它,发现当人们在代理后面时会发生这种情况,但我不是(自上次工作以来我的网络没有任何变化)。 我的syster的运行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:  

怎么了? 任何帮助表示赞赏。

PS。:旧的python版本也不工作,不在我的系统中,也不在USB的实时会话中,但DO在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,则连接成功。

我建议你在这里提交一个关于这个问题的bug报告:

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)

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

此问题可能是由于Web服务器上禁用了SSLv2,但Python 2.x尝试默认情况下与PROTOCOL_SSLv23建立连接。

这是我对Stack Overflow上类似问题的答案的链接 – https://stackoverflow.com/a/24166498/41957

更新:这在function上与@ temoto上面的答案相同。

一个对我有用的简单修复是覆盖SSL的默认协议:

 import ssl ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1