From: Ben Darnell Date: Thu, 19 Aug 2010 18:25:15 +0000 (-0400) Subject: Make the SSL handshake non-blocking. X-Git-Tag: v1.1.0~25 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d605f2bb21dba9a0b0b2c974fd173b98e93157a;p=thirdparty%2Ftornado.git Make the SSL handshake non-blocking. Based on work by mgenti. See http://github.com/mgenti/tornado/commit/197e88e62f1970b65a3807ac37160194fab7d496 and its ancestors. --- diff --git a/tornado/httpserver.py b/tornado/httpserver.py index e2975d03d..f031deb5a 100644 --- a/tornado/httpserver.py +++ b/tornado/httpserver.py @@ -225,10 +225,24 @@ class HTTPServer(object): raise if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" - connection = ssl.wrap_socket( - connection, server_side=True, **self.ssl_options) + try: + connection = ssl.wrap_socket(connection, + server_side=True, + do_handshake_on_connect=False, + **self.ssl_options) + except ssl.SSLError, err: + logging.error("SSL Error in SSL wrap:", exc_info=True) + if err.args[0] == ssl.SSL_ERROR_EOF: + return connection.close() + except socket.error, err: + logging.error("Socket Error in SSL wrap:", exc_info=True) + if err.args[0] == errno.ECONNABORTED: + return connection.close() try: - stream = iostream.IOStream(connection, io_loop=self.io_loop) + if self.ssl_options is not None: + stream = iostream.SSLIOStream(connection, io_loop=self.io_loop) + else: + stream = iostream.IOStream(connection, io_loop=self.io_loop) HTTPConnection(stream, address, self.request_callback, self.no_keep_alive, self.xheaders) except: diff --git a/tornado/iostream.py b/tornado/iostream.py index 6ea104279..84feedd50 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -22,6 +22,11 @@ import socket from tornado import ioloop +try: + import ssl # Python 2.6+ +except ImportError: + ssl = None + class IOStream(object): """A utility class to write to and read from a non-blocking socket. @@ -241,3 +246,44 @@ class IOStream(object): if not self._state & state: self._state = self._state | state self.io_loop.update_handler(self.socket.fileno(), self._state) + + +class SSLIOStream(IOStream): + """Sets up an SSL connection in a non-blocking manner""" + def __init__(self, *args, **kwargs): + super(SSLIOStream, self).__init__(*args, **kwargs) + self._ssl_accepting = True + self._do_ssl_handshake() + + def _do_ssl_handshake(self): + # Based on code from test_ssl.py in the python stdlib + try: + self.socket.do_handshake() + except ssl.SSLError, err: + if err.args[0] == ssl.SSL_ERROR_WANT_READ: + self._add_io_state(self.io_loop.READ) + return + elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: + self._add_io_state(self.io_loop.WRITE) + return + elif err.args[0] in (ssl.SSL_ERROR_EOF, + ssl.SSL_ERROR_ZERO_RETURN): + return self.close() + raise + except socket.error, err: + if err.args[0] == errno.ECONNABORTED: + return self.close() + else: + self._ssl_accepting = False + + def _handle_read(self): + if self._ssl_accepting: + self._do_ssl_handshake() + return + super(SSLIOStream, self)._handle_read() + + def _handle_write(self): + if self._ssl_accepting: + self._do_ssl_handshake() + return + super(SSLIOStream, self)._handle_write()