diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py
index 600b1948c99..cfd7efd765d 100644
--- a/aiohttp/web_urldispatcher.py
+++ b/aiohttp/web_urldispatcher.py
@@ -21,8 +21,8 @@
from .file_sender import FileSender
from .protocol import HttpVersion11
from .web_exceptions import (HTTPMethodNotAllowed, HTTPNotFound,
- HTTPExpectationFailed)
-from .web_reqrep import StreamResponse
+ HTTPExpectationFailed, HTTPForbidden)
+from .web_reqrep import Response, StreamResponse
__all__ = ('UrlDispatcher', 'UrlMappingMatchInfo',
@@ -440,7 +440,8 @@ class StaticRoute(Route):
def __init__(self, name, prefix, directory, *,
expect_handler=None, chunk_size=256*1024,
- response_factory=StreamResponse):
+ response_factory=StreamResponse,
+ show_index=False):
assert prefix.startswith('/'), prefix
assert prefix.endswith('/'), prefix
super().__init__(
@@ -460,6 +461,7 @@ def __init__(self, name, prefix, directory, *,
self._directory = directory
self._file_sender = FileSender(resp_factory=response_factory,
chunk_size=chunk_size)
+ self._show_index = show_index
def match(self, path):
if not path.startswith(self._prefix):
@@ -492,13 +494,56 @@ def handle(self, request):
request.app.logger.exception(error)
raise HTTPNotFound() from error
- # Make sure that filepath is a file
- if not filepath.is_file():
- raise HTTPNotFound()
+ # on opening a dir, load it's contents if allowed
+ if filepath.is_dir():
+ if self._show_index:
+ ret = Response(text=self._directory_as_html(filepath))
+ else:
+ raise HTTPForbidden()
+ elif filepath.is_file():
+ ret = yield from self._file_sender.send(request, filepath)
+ else:
+ raise HTTPNotFound
- ret = yield from self._file_sender.send(request, filepath)
return ret
+ def _directory_as_html(self, filepath):
+ "returns directory's index as html"
+ # sanity check
+ assert filepath.is_dir()
+
+ posix_dir_len = len(self._directory.as_posix())
+
+ # remove the beginning of posix path, so it would be relative
+ # to our added static path
+ relative_path_to_dir = filepath.as_posix()[posix_dir_len:]
+ index_of = "Index of /{}".format(relative_path_to_dir)
+ head = "
\n{}\n".format(index_of)
+ h1 = "{}
".format(index_of)
+
+ index_list = []
+ dir_index = filepath.iterdir()
+ for _file in dir_index:
+ # show file url as relative to static path
+ file_url = _file.as_posix()[posix_dir_len:]
+
+ # if file is a directory, add '/' to the end of the name
+ if _file.is_dir():
+ file_name = "{}/".format(_file.name)
+ else:
+ file_name = _file.name
+
+ index_list.append(
+ '{name}'.format(url=file_url,
+ name=file_name)
+ )
+ ul = "".format('\n'.join(index_list))
+ body = "\n{}\n{}\n".format(h1, ul)
+
+ html = "\n{}\n{}\n".format(head, body)
+
+ return html
+
def __repr__(self):
name = "'" + self.name + "' " if self.name is not None else ""
return " {directory!r}".format(
@@ -723,7 +768,8 @@ def add_route(self, method, path, handler,
expect_handler=expect_handler)
def add_static(self, prefix, path, *, name=None, expect_handler=None,
- chunk_size=256*1024, response_factory=StreamResponse):
+ chunk_size=256*1024, response_factory=StreamResponse,
+ show_index=False):
"""
Adds static files view
:param prefix - url prefix
@@ -735,6 +781,7 @@ def add_static(self, prefix, path, *, name=None, expect_handler=None,
route = StaticRoute(name, prefix, path,
expect_handler=expect_handler,
chunk_size=chunk_size,
- response_factory=response_factory)
+ response_factory=response_factory,
+ show_index=show_index)
self.register_route(route)
return route
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 8cf12a7a3b6..7668a33d605 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -231,7 +231,7 @@ Any web site has static files: images, JavaScript sources, CSS files etc.
The best way to handle static in production is setting up reverse
proxy like NGINX or using CDN services.
-But for development handling static files by aiohttp server is very convinient.
+But for development handling static files by aiohttp server is very convenient.
Fortunatelly it can be done easy by single call::
diff --git a/docs/web_reference.rst b/docs/web_reference.rst
index 79368a98f73..29f4fc7816b 100644
--- a/docs/web_reference.rst
+++ b/docs/web_reference.rst
@@ -1203,7 +1203,8 @@ Router is any object that implements :class:`AbstractRouter` interface.
:returns: new :class:`PlainRoute` or :class:`DynamicRoute` instance.
.. method:: add_static(prefix, path, *, name=None, expect_handler=None, \
- chunk_size=256*1024, response_factory=StreamResponse)
+ chunk_size=256*1024, response_factory=StreamResponse \
+ show_index=False)
Adds a router and a handler for returning static files.
@@ -1255,6 +1256,10 @@ Router is any object that implements :class:`AbstractRouter` interface.
.. versionadded:: 0.17
+ :param bool show_index: flag for allowing to show indexes of a directory,
+ by default it's not allowed and HTTP/403 will
+ be returned on directory access.
+
:returns: new :class:`StaticRoute` instance.
.. coroutinemethod:: resolve(requst)
diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py
index 907e9e972ac..8feeaa9e1b3 100644
--- a/tests/test_web_urldispatcher.py
+++ b/tests/test_web_urldispatcher.py
@@ -29,12 +29,15 @@ def teardown():
return tmp_dir
+@pytest.mark.parametrize("show_index,status", [(False, 403), (True, 200)])
@pytest.mark.run_loop
-def test_access_root_of_static_handler(tmp_dir_path, create_app_and_client):
+def test_access_root_of_static_handler(tmp_dir_path, create_app_and_client,
+ show_index, status):
"""
Tests the operation of static file server.
Try to access the root of static file server, and make
- sure that a proper not found error is returned.
+ sure that correct HTTP statuses are returned depending if we directory
+ index should be shown or not.
"""
# Put a file inside tmp_dir_path:
my_file_path = os.path.join(tmp_dir_path, 'my_file')
@@ -44,12 +47,11 @@ def test_access_root_of_static_handler(tmp_dir_path, create_app_and_client):
app, client = yield from create_app_and_client()
# Register global static route:
- app.router.add_static('/', tmp_dir_path)
+ app.router.add_static('/', tmp_dir_path, show_index=show_index)
# Request the root of the static directory.
- # Expect an 404 error page.
r = yield from client.get('/')
- assert r.status == 404
+ assert r.status == status
# data = (yield from r.read())
yield from r.release()