Запросы Python HTTPS (urllib2) к некоторым сайтам завершаются сбоем в Ubuntu 12.04 без прокси

У меня есть небольшое приложение, которое я написал на Python, и оно работало ... до вчерашнего дня, когда оно внезапно начало давать мне ошибку в соединении HTTPS. Я не помню, было ли обновление, но оба Python 2.7.3rc2 и Python 3.2 терпят неудачу точно так же.

Я погуглил и обнаружил, что это происходит, когда люди находятся за прокси-сервером, но я не являюсь (и в моей сети ничего не изменилось с момента последней работы) На компьютере моей систеры, работающем под управлением 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.

23
задан 26 March 2012 в 10:37

7 ответов

Для новичков в 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 вам необходимо переопределить класс HTTPSConnection, найденный в http.client. Я оставляю это как упражнение для читателя. : -)

0
ответ дан 26 March 2012 в 10:37

Похоже, это связано с добавлением поддержки TLS 1.1 и 1.2 к версии OpenSSL, найденной в 12.04. Ошибка соединения может быть воспроизведена с помощью инструмента командной строки 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
---

Соединение будет установлено успешно, если я заставлю соединение использовать TLS 1.0 с аргументом командной строки -tls1.

Я бы предложил вам отправить сообщение об ошибке по этой проблеме здесь:

https://bugs.launchpad.net/ubuntu/+filebug

0
ответ дан 26 March 2012 в 10:37

Проблема в ssl, она не имеет ничего общего с HTTP, поэтому зачем исправлять httplib, если вы можете исправлять ssl. Следующий код должен исправить все сокеты 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
0
ответ дан 26 March 2012 в 10:37

РЕДАКТИРОВАТЬ httplib.py (/usr/lib/pythonX.X/httplib.py в Linux)

НАЙТИ объявление класса HTTPSConnection

  class HTTPSConnection(HTTPConnection):
....

Внутри кода класса CHANGE строка

self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)

TO

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()
0
ответ дан 26 March 2012 в 10:37

Вы можете избежать изменения файла httplib.py, изменив объект HTTPSConnection:

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 не определен. Создание вашего собственного с добавлением параметра ssl_version заставит метод запроса использовать его. Тогда все остальное работает как обычно.

У меня была та же проблема, и это работает для меня.

Привет

0
ответ дан 26 March 2012 в 10:37

Простое исправление, которое сработало для меня, состояло в том, чтобы переопределить протокол SSL по умолчанию:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1
0
ответ дан 26 March 2012 в 10:37

Эта проблема, вероятно, связана с отключением SSLv2 на веб-сервере, но Python 2.x пытается установить соединение с PROTOCOL_SSLv23 по умолчанию.

Вот ссылка на мой ответ для аналогичной проблемы переполнения стека - https://stackoverflow.com/a/24166498/41957

Обновление: это функционально так же, как @ Темото ответ выше.

0
ответ дан 26 March 2012 в 10:37

Другие вопросы по тегам:

Похожие вопросы: