Debug mode now has an off mode to silence tracebacks on 500, per redbo's suggestion.
This commit is contained in:
@@ -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:
|
||||||
|
@@ -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__':
|
||||||
|
Reference in New Issue
Block a user