Skip to content

Commit

Permalink
add etag to static responses
Browse files Browse the repository at this point in the history
  • Loading branch information
redraw committed Mar 16, 2024
1 parent 8b6f155 commit 2fba5f9
Showing 1 changed file with 32 additions and 1 deletion.
33 changes: 32 additions & 1 deletion datasette/utils/asgi.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import hashlib
import json
from datasette.utils import MultiParams
from mimetypes import guess_type
Expand All @@ -12,6 +13,8 @@
# Thanks, Starlette:
# https://github.com/encode/starlette/blob/519f575/starlette/responses.py#L17

etag_cache = {}


class Base400(Exception):
status = 400
Expand Down Expand Up @@ -285,6 +288,7 @@ async def asgi_send_file(
headers = headers or {}
if filename:
headers["content-disposition"] = f'attachment; filename="{filename}"'

first = True
headers["content-length"] = str((await aiofiles.os.stat(str(filepath))).st_size)
async with aiofiles.open(str(filepath), mode="rb") as fp:
Expand All @@ -305,8 +309,27 @@ async def asgi_send_file(
)


async def calculate_etag(filepath, chunk_size=4096):
if filepath in etag_cache:
return etag_cache[filepath]

hasher = hashlib.md5()
async with aiofiles.open(filepath, "rb") as f:
while True:
chunk = await f.read(chunk_size)
if not chunk:
break
hasher.update(chunk)

etag = f'"{hasher.hexdigest()}"'
etag_cache[filepath] = etag

return etag


def asgi_static(root_path, chunk_size=4096, headers=None, content_type=None):
root_path = Path(root_path)
headers = headers or {}

async def inner_static(request, send):
path = request.scope["url_route"]["kwargs"]["path"]
Expand All @@ -325,7 +348,15 @@ async def inner_static(request, send):
await asgi_send_html(send, "404: Path not inside root path", 404)
return
try:
await asgi_send_file(send, full_path, chunk_size=chunk_size)
# Calculate ETag for filepath
etag = await calculate_etag(full_path, chunk_size=chunk_size)
headers["ETag"] = etag
if_none_match = request.headers.get("if-none-match")
if if_none_match and if_none_match == etag:
return await asgi_send(send, "", 304)
await asgi_send_file(
send, full_path, chunk_size=chunk_size, headers=headers
)
except FileNotFoundError:
await asgi_send_html(send, "404: File not found", 404)
return
Expand Down

0 comments on commit 2fba5f9

Please sign in to comment.