Skip to content

Commit c290565

Browse files
Show available files in static 404 (#577)
1 parent e291fe2 commit c290565

File tree

2 files changed

+21
-11
lines changed

2 files changed

+21
-11
lines changed

aiohttp_devtools/runserver/serve.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -334,15 +334,15 @@ def __init__(self, *args: Any, add_tail_snippet: bool = False,
334334
super().__init__(*args, **kwargs)
335335
self._show_index = True
336336

337-
def modify_request(self, request: web.Request) -> None:
337+
def modify_request(self, request: web.Request) -> Path:
338338
"""
339339
Apply common path conventions eg. / > /index.html, /foobar > /foobar.html
340340
"""
341341
filename = URL.build(path=request.match_info['filename'], encoded=True).path
342-
raw_path = self._directory.joinpath(filename)
342+
raw_path = self._directory / filename
343343
try:
344344
filepath = raw_path.resolve(strict=True)
345-
except FileNotFoundError:
345+
except (FileNotFoundError, NotADirectoryError):
346346
try:
347347
html_file = raw_path.with_name(raw_path.name + '.html').resolve().relative_to(self._directory)
348348
except (FileNotFoundError, ValueError):
@@ -358,6 +358,7 @@ def modify_request(self, request: web.Request) -> None:
358358
except ValueError:
359359
# path is not not relative to self._directory
360360
pass
361+
return raw_path
361362

362363
def _insert_footer(self, response: web.StreamResponse) -> web.StreamResponse:
363364
if not isinstance(response, web.FileResponse) or not self._add_tail_snippet:
@@ -377,17 +378,20 @@ def _insert_footer(self, response: web.StreamResponse) -> web.StreamResponse:
377378
return resp
378379

379380
async def _handle(self, request: web.Request) -> web.StreamResponse:
380-
self.modify_request(request)
381+
raw_path = self.modify_request(request)
381382
try:
382383
response = await super()._handle(request)
383-
response = self._insert_footer(response)
384-
except HTTPNotModified:
385-
raise
386384
except HTTPNotFound:
387-
# TODO include list of files in 404 body
388-
_404_msg = '404: Not Found\n'
389-
response = web.Response(body=_404_msg.encode(), status=404, content_type='text/plain')
385+
while not raw_path.is_dir():
386+
raw_path = raw_path.parent
387+
paths = "\n".join(
388+
" {}{}".format(p.relative_to(self._directory), "/" if p.is_dir() else "")
389+
for p in raw_path.iterdir())
390+
msg = "404: Not Found\n\nAvailable files under '{}/':\n{}\n".format(
391+
raw_path.relative_to(self._directory), paths)
392+
response = web.Response(text=msg, status=404, content_type="text/plain")
390393
else:
394+
response = self._insert_footer(response)
391395
# Inject CORS headers to allow webfonts to load correctly
392396
response.headers['Access-Control-Allow-Origin'] = '*'
393397

tests/test_serve.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,17 @@ async def test_simple_serve(cli, tmpworkdir):
2626
assert text == 'hello world'
2727

2828

29-
async def test_file_missing(cli):
29+
async def test_file_missing(cli, tmpworkdir):
30+
mktree(tmpworkdir, {
31+
"bar": "hello world",
32+
"baz/foo": "hello world",
33+
})
3034
r = await cli.get('/foo')
3135
assert r.status == 404
3236
text = await r.text()
3337
assert '404: Not Found\n' in text
38+
assert "bar\n" in text
39+
assert "baz/\n" in text
3440

3541

3642
async def test_browser_cache(event_loop, aiohttp_client, tmpworkdir):

0 commit comments

Comments
 (0)