From 85e75d572dd997f1d8ba180fa1a7f58983d4415d Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 19 Jul 2009 03:55:54 -0700 Subject: [PATCH] Finished up SSL.Connection compatibility. Backwards compatibility change: it used to be that you could just call close() on an ssl socket, but now you have to call shutdown() and then close(). This is in line with the normal OpenSSL behavior and means that we no longer penalize correctly-written clients. Fixed wsgi and api_test to reflect this changed assumption. --- eventlet/greenio.py | 128 ++++++++++++++++++++++++++++++++++++------ eventlet/wsgi.py | 4 ++ greentest/api_test.py | 15 +++-- 3 files changed, 123 insertions(+), 24 deletions(-) diff --git a/eventlet/greenio.py b/eventlet/greenio.py index 2a03609..0695478 100644 --- a/eventlet/greenio.py +++ b/eventlet/greenio.py @@ -233,12 +233,6 @@ class GreenSocket(object): if self.closed: return self.closed = True - if self.is_secure: - # *NOTE: This is not quite the correct SSL shutdown sequence. - # We should actually be checking the return value of shutdown. - # Note also that this is not the same as calling self.shutdown(). - self.fd.shutdown() - fn = self.close = self.fd.close try: res = fn(*args, **kw) @@ -362,10 +356,7 @@ class GreenSocket(object): return fn(*args, **kw) def shutdown(self, *args, **kw): - if self.is_secure: - fn = self.shutdown = self.fd.sock_shutdown - else: - fn = self.shutdown = self.fd.shutdown + fn = self.shutdown = self.fd.shutdown return fn(*args, **kw) def settimeout(self, howlong): @@ -543,10 +534,69 @@ class GreenSSL(GreenSocket): "GreenSSL can only be constructed with an "\ "OpenSSL Connection object") self.sock = self + + def close(self): + # *NOTE: in older versions of eventlet, we called shutdown() on SSL sockets + # before closing them. That wasn't right because correctly-written clients + # would have already called shutdown, and calling shutdown a second time + # triggers unwanted bidirectional communication. + super(GreenSSL, self).close() + + def do_handshake(self): + """ Perform an SSL handshake (usually called after renegotiate or one of + set_accept_state or set_accept_state). This can raise the same exceptions as + send and recv. """ + if self.act_non_blocking: + return self.fd.do_handshake() + while True: + try: + return self.fd.do_handshake() + except util.SSL.WantReadError: + trampoline(self.fd.fileno(), + read=True, + timeout=self.timeout, + timeout_exc=socket.timeout) + except util.SSL.WantWriteError: + trampoline(self.fd.fileno(), + write=True, + timeout=self.timeout, + timeout_exc=socket.timeout) + + def dup(self): + raise NotImplementedError("Dup not supported on SSL sockets") + + def get_app_data(self, *args, **kw): + fn = self.get_app_data = self.fd.get_app_data + return fn(*args, **kw) + + def set_app_data(self, *args, **kw): + fn = self.set_app_data = self.fd.set_app_data + return fn(*args, **kw) + + def get_cipher_list(self, *args, **kw): + fn = self.get_cipher_list = self.fd.get_cipher_list + return fn(*args, **kw) + + def get_context(self, *args, **kw): + fn = self.get_context = self.fd.get_context + return fn(*args, **kw) + + def get_peer_certificate(self, *args, **kw): + fn = self.get_peer_certificate = self.fd.get_peer_certificate + return fn(*args, **kw) + + def makefile(self, mode='r', bufsize=-1): + raise NotImplementedError("Makefile not supported on SSL sockets") + + def pending(self, *args, **kw): + fn = self.pending = self.fd.pending + return fn(*args, **kw) def read(self, size): """Works like a blocking call to SSL_read(), whose behavior is described here: http://www.openssl.org/docs/ssl/SSL_read.html""" + if self.act_non_blocking: + return self.fd.read(size) while True: try: return self.fd.read(size) @@ -565,12 +615,18 @@ class GreenSSL(GreenSocket): return '' recv = read + + def renegotiate(self, *args, **kw): + fn = self.renegotiate = self.fd.renegotiate + return fn(*args, **kw) def write(self, data): """Works like a blocking call to SSL_write(), whose behavior is described here: http://www.openssl.org/docs/ssl/SSL_write.html""" if not data: return 0 # calling SSL_write() with 0 bytes to be sent is undefined + if self.act_non_blocking: + return self.fd.write(data) while True: try: return self.fd.write(data) @@ -596,15 +652,55 @@ class GreenSSL(GreenSocket): tail = self.send(data) while tail < len(data): tail += self.send(data[tail:]) + + def set_accept_state(self, *args, **kw): + fn = self.set_accept_state = self.fd.set_accept_state + return fn(*args, **kw) - def dup(self): - raise NotImplementedError("Dup not supported on SSL sockets") + def set_connect_state(self, *args, **kw): + fn = self.set_connect_state = self.fd.set_connect_state + return fn(*args, **kw) + + def shutdown(self): + if self.act_non_blocking: + return self.fd.shutdown() + while True: + try: + return self.fd.shutdown() + except util.SSL.WantReadError: + trampoline(self.fd.fileno(), + read=True, + timeout=self.timeout, + timeout_exc=socket.timeout) + except util.SSL.WantWriteError: + trampoline(self.fd.fileno(), + write=True, + timeout=self.timeout, + timeout_exc=socket.timeout) + + + def get_shutdown(self, *args, **kw): + fn = self.get_shutdown = self.fd.get_shutdown + return fn(*args, **kw) + + def set_shutdown(self, *args, **kw): + fn = self.set_shutdown = self.fd.set_shutdown + return fn(*args, **kw) + + def sock_shutdown(self, *args, **kw): + fn = self.sock_shutdown = self.fd.sock_shutdown + return fn(*args, **kw) + + def state_string(self, *args, **kw): + fn = self.state_string = self.fd.state_string + return fn(*args, **kw) - def makefile(self, mode='r', bufsize=-1): - raise NotImplementedError("Makefile not supported on SSL sockets") + def want_read(self, *args, **kw): + fn = self.want_read = self.fd.want_read + return fn(*args, **kw) - def pending(self, *args, **kw): - fn = self.pending = self.fd.pending + def want_write(self, *args, **kw): + fn = self.want_write = self.fd.want_write return fn(*args, **kw) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index a61678b..580c439 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -358,6 +358,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): def finish(self): BaseHTTPServer.BaseHTTPRequestHandler.finish(self) + if self.connection.is_secure: + self.connection.shutdown() self.connection.close() @@ -459,6 +461,8 @@ def server(sock, site, break finally: try: + if sock.is_secure: + sock.shutdown() sock.close() except socket.error, e: if e[0] != errno.EPIPE: diff --git a/greentest/api_test.py b/greentest/api_test.py index d22b564..d926a21 100644 --- a/greentest/api_test.py +++ b/greentest/api_test.py @@ -21,6 +21,7 @@ import os import os.path +import socket from unittest import TestCase, main from eventlet import api @@ -60,7 +61,7 @@ class TestApi(TestCase): def accept_once(listenfd): try: conn, addr = listenfd.accept() - fd = conn.makeGreenFile() + fd = conn.makefile() conn.close() fd.write('hello\n') fd.close() @@ -71,7 +72,7 @@ class TestApi(TestCase): api.spawn(accept_once, server) client = api.connect_tcp(('127.0.0.1', server.getsockname()[1])) - fd = client.makeGreenFile() + fd = client.makefile() client.close() assert fd.readline() == 'hello\n' @@ -84,9 +85,7 @@ class TestApi(TestCase): def accept_once(listenfd): try: conn, addr = listenfd.accept() - fl = conn.makeGreenFile('w') - fl.write('hello\r\n') - fl.close() + conn.write('hello\r\n') conn.close() finally: listenfd.close() @@ -98,10 +97,10 @@ class TestApi(TestCase): client = util.wrap_ssl( api.connect_tcp(('127.0.0.1', server.getsockname()[1]))) - client = client.makeGreenFile() + fd = socket._fileobject(client, 'rb', 8192) - assert client.readline() == 'hello\r\n' - assert client.read() == '' + assert fd.readline() == 'hello\r\n' + assert fd.read() == '' client.close() def test_server(self):