Skip to content

Instantly share code, notes, and snippets.

@wolever
Created February 23, 2017 21:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wolever/30fa147c407f97dd837414a56e044cd4 to your computer and use it in GitHub Desktop.
Save wolever/30fa147c407f97dd837414a56e044cd4 to your computer and use it in GitHub Desktop.
Correctly monkeypatch gevent, PyOpenSSL, and urllib3 to all play well together and support SNI.
"""
The correct set of steps to monkeypatch:
- gevent into threading, socket, etc
- PyOpenSSL with gevent (via gevent_openssl)
- PyOpenSSL to be compatible with
- urllib3 and requests.packages.urllib3 to use PyOpenSSL, which
lets them connect to servers using SNI
"""
import logging
did_init = False
def _patch_urllib3(module):
from django.utils.module_loading import import_string
try:
inject_into_urllib3 = import_string(module + ".contrib.pyopenssl.inject_into_urllib3")
inject_into_urllib3()
except Exception as e:
import gevent
def log_error():
# Give the rest of Django a chance to warm up and configure
# loggers, otherwise this message won't go anywhere.
gevent.sleep(1)
log = logging.getLogger("akindi.__init__")
log.error(
"Error injecting PyOpenSSL into %s: %s "
"(things will still work, but SSL certificates "
"with SNI will fail to validate)",
module, e,
)
gevent.spawn(log_error)
def _patch_openssl():
""" Stock PyOpenSSL throws a TypeError if the domain name is unicode. """
from gevent_openssl.SSL import _real_connection as Connection
old_set_tlsext_host_name = Connection.set_tlsext_host_name
def set_tlsext_host_name(self, name):
if isinstance(name, unicode):
name = name.encode("idna")
return old_set_tlsext_host_name(self, name)
set_tlsext_host_name.old = old_set_tlsext_host_name
Connection.set_tlsext_host_name = set_tlsext_host_name
def init_app():
""" Initialize the app. This should be called as early as possible, either
from manage.py or app.wsgi. """
global did_init
if did_init:
return
from gevent import monkey
monkey.patch_all(subprocess=True)
import gevent_openssl
gevent_openssl.monkey_patch()
from gevent_openssl.SSL import Connection
def Connection_sendall_fixed(self, buf, flags=0):
# There is a bug with gevent which causes `sendall` to crash when
# the buffer is large. Work around this by manually implementing
# `sendall`. See also: https://github.com/gevent/gevent/issues/736
while buf:
sent = self.send(buf, flags)
buf = buf[sent:]
Connection.sendall = Connection_sendall_fixed
_patch_openssl()
_patch_urllib3("urllib3")
_patch_urllib3("requests.packages.urllib3")
did_init = True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment