Skip to content

Commit 396ec1f

Browse files
[3.5] plain text http responses to errors (#3483) (#3499)
(cherry picked from commit b42a23b) Co-authored-by: Samuel Colvin <samcolvin@gmail.com>
1 parent 42ff0b2 commit 396ec1f

File tree

5 files changed

+83
-17
lines changed

5 files changed

+83
-17
lines changed

CHANGES/3483.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Internal Server Errors in plain text if the browser does not support HTML.

aiohttp/test_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ async def _make_runner(self,
219219
debug: bool=True,
220220
**kwargs: Any) -> ServerRunner:
221221
srv = Server(
222-
self._handler, loop=self._loop, debug=True, **kwargs)
222+
self._handler, loop=self._loop, debug=debug, **kwargs)
223223
return ServerRunner(srv, debug=debug, **kwargs)
224224

225225

aiohttp/web_protocol.py

+23-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from collections import deque
66
from contextlib import suppress
77
from html import escape as html_escape
8+
from http import HTTPStatus
89
from logging import Logger
910
from typing import (
1011
TYPE_CHECKING,
@@ -523,24 +524,34 @@ def handle_error(self,
523524
information. It always closes current connection."""
524525
self.log_exception("Error handling request", exc_info=exc)
525526

526-
if status == 500:
527-
msg = "<h1>500 Internal Server Error</h1>"
527+
ct = 'text/plain'
528+
if status == HTTPStatus.INTERNAL_SERVER_ERROR:
529+
title = '{0.value} {0.phrase}'.format(
530+
HTTPStatus.INTERNAL_SERVER_ERROR
531+
)
532+
msg = HTTPStatus.INTERNAL_SERVER_ERROR.description
533+
tb = None
528534
if self.debug:
529535
with suppress(Exception):
530536
tb = traceback.format_exc()
537+
538+
if 'text/html' in request.headers.get('Accept', ''):
539+
if tb:
531540
tb = html_escape(tb)
532-
msg += '<br><h2>Traceback:</h2>\n<pre>'
533-
msg += tb
534-
msg += '</pre>'
541+
msg = '<h2>Traceback:</h2>\n<pre>{}</pre>'.format(tb)
542+
message = (
543+
"<html><head>"
544+
"<title>{title}</title>"
545+
"</head><body>\n<h1>{title}</h1>"
546+
"\n{msg}\n</body></html>\n"
547+
).format(title=title, msg=msg)
548+
ct = 'text/html'
535549
else:
536-
msg += "Server got itself in trouble"
537-
msg = ("<html><head><title>500 Internal Server Error</title>"
538-
"</head><body>" + msg + "</body></html>")
539-
resp = Response(status=status, text=msg, content_type='text/html')
540-
else:
541-
resp = Response(status=status, text=message,
542-
content_type='text/html')
550+
if tb:
551+
msg = tb
552+
message = title + '\n\n' + msg
543553

554+
resp = Response(status=status, text=message, content_type=ct)
544555
resp.force_close()
545556

546557
# some data already got sent, connection is broken

tests/test_web_protocol.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ async def test_handle_error__utf(
299299
await asyncio.sleep(0)
300300

301301
assert b'HTTP/1.0 500 Internal Server Error' in buf
302-
assert b'Content-Type: text/html; charset=utf-8' in buf
302+
assert b'Content-Type: text/plain; charset=utf-8' in buf
303303
pattern = escape("RuntimeError: что-то пошло не так")
304304
assert pattern.encode('utf-8') in buf
305305
assert not srv._keepalive

tests/test_web_server.py

+57-3
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ async def handler(request):
2626
raise exc
2727

2828
logger = mock.Mock()
29-
server = await aiohttp_raw_server(handler, logger=logger)
29+
server = await aiohttp_raw_server(handler, logger=logger, debug=False)
3030
cli = await aiohttp_client(server)
3131
resp = await cli.get('/path/to')
3232
assert resp.status == 500
33+
assert resp.headers['Content-Type'].startswith('text/plain')
3334

3435
txt = await resp.text()
35-
assert "<h1>500 Internal Server Error</h1>" in txt
36+
assert txt.startswith('500 Internal Server Error')
37+
assert 'Traceback' not in txt
3638

3739
logger.exception.assert_called_with(
3840
"Error handling request",
@@ -102,10 +104,62 @@ async def handler(request):
102104
cli = await aiohttp_client(server)
103105
resp = await cli.get('/path/to')
104106
assert resp.status == 500
107+
assert resp.headers['Content-Type'].startswith('text/plain')
105108

106109
txt = await resp.text()
107-
assert "<h2>Traceback:</h2>" in txt
110+
assert 'Traceback (most recent call last):\n' in txt
108111

109112
logger.exception.assert_called_with(
110113
"Error handling request",
111114
exc_info=exc)
115+
116+
117+
async def test_raw_server_html_exception(aiohttp_raw_server, aiohttp_client):
118+
exc = RuntimeError("custom runtime error")
119+
120+
async def handler(request):
121+
raise exc
122+
123+
logger = mock.Mock()
124+
server = await aiohttp_raw_server(handler, logger=logger, debug=False)
125+
cli = await aiohttp_client(server)
126+
resp = await cli.get('/path/to', headers={'Accept': 'text/html'})
127+
assert resp.status == 500
128+
assert resp.headers['Content-Type'].startswith('text/html')
129+
130+
txt = await resp.text()
131+
assert txt == (
132+
'<html><head><title>500 Internal Server Error</title></head><body>\n'
133+
'<h1>500 Internal Server Error</h1>\n'
134+
'Server got itself in trouble\n'
135+
'</body></html>\n'
136+
)
137+
138+
logger.exception.assert_called_with(
139+
"Error handling request", exc_info=exc)
140+
141+
142+
async def test_raw_server_html_exception_debug(aiohttp_raw_server,
143+
aiohttp_client):
144+
exc = RuntimeError("custom runtime error")
145+
146+
async def handler(request):
147+
raise exc
148+
149+
logger = mock.Mock()
150+
server = await aiohttp_raw_server(handler, logger=logger, debug=True)
151+
cli = await aiohttp_client(server)
152+
resp = await cli.get('/path/to', headers={'Accept': 'text/html'})
153+
assert resp.status == 500
154+
assert resp.headers['Content-Type'].startswith('text/html')
155+
156+
txt = await resp.text()
157+
assert txt.startswith(
158+
'<html><head><title>500 Internal Server Error</title></head><body>\n'
159+
'<h1>500 Internal Server Error</h1>\n'
160+
'<h2>Traceback:</h2>\n'
161+
'<pre>Traceback (most recent call last):\n'
162+
)
163+
164+
logger.exception.assert_called_with(
165+
"Error handling request", exc_info=exc)

0 commit comments

Comments
 (0)