Python requests SSLError: EOF occurred in violation of protocol

とあるs付きのAPIにリクエストを投げた時に発生したエラー_φ(・_・

  • python 2.7.10
  • opelssl 1.0.1e
  • requests 2.8.1

In [21]: r = requests.get(url)
---------------------------------------------------------------------------
SSLError                                  Traceback (most recent call last)
<ipython-input-21-0aec23ab0de0> in <module>()
----> 1 r = requests.get(url)

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/api.pyc in get(url, params, **kwargs)
     67 
     68     kwargs.setdefault('allow_redirects', True)
---> 69     return request('get', url, params=params, **kwargs)
     70 
     71 

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/api.pyc in request(method, url, **kwargs)
     48 
     49     session = sessions.Session()
---> 50     response = session.request(method=method, url=url, **kwargs)
     51     # By explicitly closing the session, we avoid leaving sockets open which
     52     # can trigger a ResourceWarning in some cases, and look like a memory leak

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/sessions.pyc in request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    466         }
    467         send_kwargs.update(settings)
--> 468         resp = self.send(prep, **send_kwargs)
    469 
    470         return resp

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/sessions.pyc in send(self, request, **kwargs)
    574 
    575         # Send the request
--> 576         r = adapter.send(request, **kwargs)
    577 
    578         # Total elapsed time of the request (approximately)

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/adapters.pyc in send(self, request, stream, timeout, verify, cert, proxies)
    431         except (_SSLError, _HTTPError) as e:
    432             if isinstance(e, _SSLError):
--> 433                 raise SSLError(e, request=request)
    434             elif isinstance(e, ReadTimeoutError):
    435                 raise ReadTimeout(e, request=request)

SSLError: EOF occurred in violation of protocol (_ssl.c:590)

使われるSSLのバージョンの問題らしい。
requestsで使われるのは基底となるurllib3の実装によるようなので、特定のバージョンを使いたい場合は、下記のようにHTTPAdapterのサブクラスを作って、ssl_versionを明示するようにするとのこと。
今回は、ssl_version=ssl.PROTOCOL_TLSv1 とすることでうまくいった。

In [28]: from requests.adapters import HTTPAdapter

In [29]: from requests.packages.urllib3.poolmanager import PoolManager

In [30]: import ssl

In [31]: 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)
   ....:         

In [32]: import requests

In [33]: s = requests.Session()

In [34]: s.mount('https://', MyAdapter())

In [35]: r = s.get(url)