From 00ba55aaafb7aba9bfaced3d70abf0e97710cfe1 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Fri, 4 Feb 2011 09:40:40 -0800 Subject: [PATCH] Debug mode now has an off mode to silence tracebacks on 500, per redbo's suggestion. --- eventlet/wsgi.py | 23 +++++++++----- tests/wsgi_test.py | 77 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index a098628..e9959e1 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -359,13 +359,16 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): write('') except Exception: self.close_connection = 1 - exc = traceback.format_exc() - self.server.log_message(exc) + tb = traceback.format_exc() + self.server.log_message(tb) if not headers_set: + err_body = "" + if(self.server.debug): + err_body = tb start_response("500 Internal Server Error", [('Content-type', 'text/plain'), - ('Content-length', len(exc))]) - write(exc) + ('Content-length', len(err_body))]) + write(err_body) finally: if hasattr(result, 'close'): result.close() @@ -455,6 +458,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): self.connection.close() + class Server(BaseHTTPServer.HTTPServer): def __init__(self, socket, @@ -467,7 +471,8 @@ class Server(BaseHTTPServer.HTTPServer): minimum_chunk_size=None, log_x_forwarded_for=True, keepalive=True, - log_format=DEFAULT_LOG_FORMAT): + log_format=DEFAULT_LOG_FORMAT, + debug=True): self.outstanding_requests = 0 self.socket = socket @@ -486,6 +491,7 @@ class Server(BaseHTTPServer.HTTPServer): protocol.minimum_chunk_size = minimum_chunk_size self.log_x_forwarded_for = log_x_forwarded_for self.log_format = log_format + self.debug = debug def get_environ(self): d = { @@ -531,7 +537,8 @@ def server(sock, site, log_x_forwarded_for=True, custom_pool=None, keepalive=True, - log_format=DEFAULT_LOG_FORMAT): + log_format=DEFAULT_LOG_FORMAT, + debug=True): """ Start up a wsgi server handling requests from the supplied server socket. This function loops forever. The *sock* object will be closed after server exits, but the underlying file descriptor will remain open, so if you have a dup() of *sock*, @@ -550,6 +557,7 @@ def server(sock, site, :param custom_pool: A custom GreenPool instance which is used to spawn client green threads. If this is supplied, max_size is ignored. :param keepalive: If set to False, disables keepalives on the server; all connections will be closed after serving one request. :param log_format: A python format string that is used as the template to generate log lines. The following values can be formatted into it: client_ip, date_time, request_line, status_code, body_length, wall_seconds. The default is a good example of how to use it. + :param debug: True if the server should send exception tracebacks to the clients on 500 errors. If False, the server will respond with empty bodies. """ serv = Server(sock, sock.getsockname(), site, log, @@ -559,7 +567,8 @@ def server(sock, site, minimum_chunk_size=minimum_chunk_size, log_x_forwarded_for=log_x_forwarded_for, keepalive=keepalive, - log_format=log_format) + log_format=log_format, + debug=debug) if server_event is not None: server_event.send(serv) if max_size is None: diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py index c6fc3af..b257efb 100644 --- a/tests/wsgi_test.py +++ b/tests/wsgi_test.py @@ -124,7 +124,7 @@ def read_http(sock): raise ConnectionClosed raise if not response_line: - raise ConnectionClosed + raise ConnectionClosed(response_line) header_lines = [] while True: @@ -174,7 +174,6 @@ class _TestBase(LimitedTestCase): eventlet.sleep(0) # give previous server a chance to start if self.killer: greenthread.kill(self.killer) - eventlet.sleep(0) # give killer a chance to kill new_kwargs = dict(max_size=128, log=self.logfile, @@ -952,6 +951,58 @@ class TestHttpd(_TestBase): self.assertEqual(headers['connection'], 'close') self.assert_('unicode' in body) + def test_ipv6(self): + try: + sock = eventlet.listen(('::1', 0), family=socket.AF_INET6) + except (socket.gaierror, socket.error): # probably no ipv6 + return + log = StringIO() + # first thing the server does is try to log the IP it's bound to + def run_server(): + try: + server = wsgi.server(sock=sock, log=log, site=Site()) + except ValueError: + log.write('broked') + eventlet.spawn_n(run_server) + logval = log.getvalue() + while not logval: + eventlet.sleep(0.0) + logval = log.getvalue() + if 'broked' in logval: + self.fail('WSGI server raised exception with ipv6 socket') + + def test_debug(self): + self.spawn_server(debug=False) + def crasher(env, start_response): + raise RuntimeError("intentional crash") + self.site.application = crasher + + sock = eventlet.connect(('localhost', self.port)) + fd = sock.makefile('w') + fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() + response_line, headers, body = read_http(sock) + self.assert_(response_line.startswith('HTTP/1.1 500 Internal Server Error')) + self.assertEqual(body, '') + self.assertEqual(headers['connection'], 'close') + self.assert_('transfer-encoding' not in headers) + + # verify traceback when debugging enabled + self.spawn_server(debug=True) + self.site.application = crasher + sock = eventlet.connect(('localhost', self.port)) + fd = sock.makefile('w') + fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() + response_line, headers, body = read_http(sock) + self.assert_(response_line.startswith('HTTP/1.1 500 Internal Server Error')) + self.assert_('intentional crash' in body) + self.assert_('RuntimeError' in body) + self.assert_('Traceback' in body) + self.assertEqual(headers['connection'], 'close') + self.assert_('transfer-encoding' not in headers) + + def read_headers(sock): fd = sock.makefile() try: @@ -1136,27 +1187,7 @@ class TestChunkedInput(_TestBase): signal.alarm(0) signal.signal(signal.SIGALRM, signal.SIG_DFL) - assert not got_signal, "caught alarm signal. infinite loop detected." - - def test_ipv6(self): - try: - sock = eventlet.listen(('::1', 0), family=socket.AF_INET6) - except (socket.gaierror, socket.error): # probably no ipv6 - return - log = StringIO() - # first thing the server does is try to log the IP it's bound to - def run_server(): - try: - server = wsgi.server(sock=sock, log=log, site=Site()) - except ValueError: - log.write('broked') - eventlet.spawn_n(run_server) - logval = log.getvalue() - while not logval: - eventlet.sleep(0.0) - logval = log.getvalue() - if 'broked' in logval: - self.fail('WSGI server raised exception with ipv6 socket') + assert not got_signal, "caught alarm signal. infinite loop detected." if __name__ == '__main__':