Add max_age argument to send_file()

This commit is contained in:
Miguel Grinberg
2023-03-20 12:06:26 +00:00
parent 573e303a98
commit e684ee32d9
3 changed files with 43 additions and 4 deletions

View File

@@ -662,6 +662,15 @@ object for a file::
def index(request):
return send_file('/static/index.html')
A suggested caching duration can be returned to the client in the `max_age`
argument::
from microdot import send_file
@app.get('/')
def image(request):
return send_file('/static/image.jpg', max_age=3600) # in seconds
.. note::
Unlike other web frameworks, Microdot does not automatically configure a
route to serve static files. The following is an example route that can be
@@ -673,7 +682,7 @@ object for a file::
if '..' in path:
# directory traversal is not allowed
return 'Not found', 404
return send_file('static/' + path)
return send_file('static/' + path, max_age=86400)
Streaming Responses
^^^^^^^^^^^^^^^^^^^

View File

@@ -525,6 +525,10 @@ class Response():
#: ``Content-Type`` header.
default_content_type = 'text/plain'
#: The default cache control max age used by :meth:`send_file`. A value
#: of ``None`` means that no ``Cache-Control`` header is added.
default_send_file_max_age = None
#: Special response used to signal that a response does not need to be
#: written to the client. Used to exit WebSocket connections cleanly.
already_handled = None
@@ -651,7 +655,8 @@ class Response():
return cls(status_code=status_code, headers={'Location': location})
@classmethod
def send_file(cls, filename, status_code=200, content_type=None):
def send_file(cls, filename, status_code=200, content_type=None,
max_age=None):
"""Send file contents in a response.
:param filename: The filename of the file.
@@ -660,6 +665,10 @@ class Response():
:param content_type: The ``Content-Type`` header to use in the
response. If omitted, it is generated
automatically from the file extension.
:param max_age: The ``Cache-Control`` header's ``max-age`` value in
seconds. If omitted, the value of the
:attr:`Response.default_send_file_max_age` attribute is
used.
Security note: The filename is assumed to be trusted. Never pass
filenames provided by the user without validating and sanitizing them
@@ -671,9 +680,15 @@ class Response():
content_type = Response.types_map[ext]
else:
content_type = 'application/octet-stream'
headers = {'Content-Type': content_type}
if max_age is None:
max_age = cls.default_send_file_max_age
if max_age is not None:
headers['Cache-Control'] = 'max-age={}'.format(max_age)
f = open(filename, 'rb')
return cls(body=f, status_code=status_code,
headers={'Content-Type': content_type})
return cls(body=f, status_code=status_code, headers=headers)
class URLPattern():

View File

@@ -235,6 +235,21 @@ class TestResponse(unittest.TestCase):
b'HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\nfoo\n')
Response.send_file_buffer_size = original_buffer_size
def test_send_file_max_age(self):
res = Response.send_file('tests/files/test.txt', max_age=123)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.headers['Cache-Control'], 'max-age=123')
Response.default_send_file_max_age = 456
res = Response.send_file('tests/files/test.txt')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.headers['Cache-Control'], 'max-age=456')
res = Response.send_file('tests/files/test.txt', max_age=123)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.headers['Cache-Control'], 'max-age=123')
Response.default_send_file_max_age = None
def test_default_content_type(self):
original_content_type = Response.default_content_type
res = Response('foo')