Debug mode now has an off mode to silence tracebacks on 500, per redbo's suggestion.

This commit is contained in:
Ryan Williams
2011-02-04 09:40:40 -08:00
parent 8c808e9184
commit 00ba55aaaf
2 changed files with 70 additions and 30 deletions

View File

@@ -359,13 +359,16 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
write('') write('')
except Exception: except Exception:
self.close_connection = 1 self.close_connection = 1
exc = traceback.format_exc() tb = traceback.format_exc()
self.server.log_message(exc) self.server.log_message(tb)
if not headers_set: if not headers_set:
err_body = ""
if(self.server.debug):
err_body = tb
start_response("500 Internal Server Error", start_response("500 Internal Server Error",
[('Content-type', 'text/plain'), [('Content-type', 'text/plain'),
('Content-length', len(exc))]) ('Content-length', len(err_body))])
write(exc) write(err_body)
finally: finally:
if hasattr(result, 'close'): if hasattr(result, 'close'):
result.close() result.close()
@@ -455,6 +458,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
self.connection.close() self.connection.close()
class Server(BaseHTTPServer.HTTPServer): class Server(BaseHTTPServer.HTTPServer):
def __init__(self, def __init__(self,
socket, socket,
@@ -467,7 +471,8 @@ class Server(BaseHTTPServer.HTTPServer):
minimum_chunk_size=None, minimum_chunk_size=None,
log_x_forwarded_for=True, log_x_forwarded_for=True,
keepalive=True, keepalive=True,
log_format=DEFAULT_LOG_FORMAT): log_format=DEFAULT_LOG_FORMAT,
debug=True):
self.outstanding_requests = 0 self.outstanding_requests = 0
self.socket = socket self.socket = socket
@@ -486,6 +491,7 @@ class Server(BaseHTTPServer.HTTPServer):
protocol.minimum_chunk_size = minimum_chunk_size protocol.minimum_chunk_size = minimum_chunk_size
self.log_x_forwarded_for = log_x_forwarded_for self.log_x_forwarded_for = log_x_forwarded_for
self.log_format = log_format self.log_format = log_format
self.debug = debug
def get_environ(self): def get_environ(self):
d = { d = {
@@ -531,7 +537,8 @@ def server(sock, site,
log_x_forwarded_for=True, log_x_forwarded_for=True,
custom_pool=None, custom_pool=None,
keepalive=True, 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 """ 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, 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*, 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 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 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 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(), serv = Server(sock, sock.getsockname(),
site, log, site, log,
@@ -559,7 +567,8 @@ def server(sock, site,
minimum_chunk_size=minimum_chunk_size, minimum_chunk_size=minimum_chunk_size,
log_x_forwarded_for=log_x_forwarded_for, log_x_forwarded_for=log_x_forwarded_for,
keepalive=keepalive, keepalive=keepalive,
log_format=log_format) log_format=log_format,
debug=debug)
if server_event is not None: if server_event is not None:
server_event.send(serv) server_event.send(serv)
if max_size is None: if max_size is None:

View File

@@ -124,7 +124,7 @@ def read_http(sock):
raise ConnectionClosed raise ConnectionClosed
raise raise
if not response_line: if not response_line:
raise ConnectionClosed raise ConnectionClosed(response_line)
header_lines = [] header_lines = []
while True: while True:
@@ -174,7 +174,6 @@ class _TestBase(LimitedTestCase):
eventlet.sleep(0) # give previous server a chance to start eventlet.sleep(0) # give previous server a chance to start
if self.killer: if self.killer:
greenthread.kill(self.killer) greenthread.kill(self.killer)
eventlet.sleep(0) # give killer a chance to kill
new_kwargs = dict(max_size=128, new_kwargs = dict(max_size=128,
log=self.logfile, log=self.logfile,
@@ -952,6 +951,58 @@ class TestHttpd(_TestBase):
self.assertEqual(headers['connection'], 'close') self.assertEqual(headers['connection'], 'close')
self.assert_('unicode' in body) 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): def read_headers(sock):
fd = sock.makefile() fd = sock.makefile()
try: try:
@@ -1136,27 +1187,7 @@ class TestChunkedInput(_TestBase):
signal.alarm(0) signal.alarm(0)
signal.signal(signal.SIGALRM, signal.SIG_DFL) signal.signal(signal.SIGALRM, signal.SIG_DFL)
assert not got_signal, "caught alarm signal. infinite loop detected." 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')
if __name__ == '__main__': if __name__ == '__main__':