mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Use aiofiles for static, refs #272
This commit is contained in:
parent
ca03940f6d
commit
8a1a15d725
4 changed files with 50 additions and 12 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import aiofiles
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
import collections
|
import collections
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
@ -733,7 +734,13 @@ async def asgi_send_html(send, html, status=200, headers=None):
|
||||||
|
|
||||||
|
|
||||||
async def asgi_send(send, content, status, headers, content_type="text/plain"):
|
async def asgi_send(send, content, status, headers, content_type="text/plain"):
|
||||||
# TODO: watch out for Content-Type due to mixed case:
|
await asgi_start(send, status, headers, content_type)
|
||||||
|
await send({"type": "http.response.body", "body": content.encode("utf8")})
|
||||||
|
|
||||||
|
|
||||||
|
async def asgi_start(send, status, headers, content_type="text/plain"):
|
||||||
|
# Remove any existing content-type header
|
||||||
|
headers = dict([(k, v) for k, v in headers.items() if k.lower() != "content-type"])
|
||||||
headers["content-type"] = content_type
|
headers["content-type"] = content_type
|
||||||
await send(
|
await send(
|
||||||
{
|
{
|
||||||
|
|
@ -745,20 +752,39 @@ async def asgi_send(send, content, status, headers, content_type="text/plain"):
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await send({"type": "http.response.body", "body": content.encode("utf8")})
|
|
||||||
|
|
||||||
|
|
||||||
def asgi_static(root_path):
|
def asgi_static(root_path, chunk_size=4096):
|
||||||
async def inner_static(scope, receive, send):
|
async def inner_static(scope, receive, send):
|
||||||
path = scope["url_route"]["kwargs"]["path"]
|
path = scope["url_route"]["kwargs"]["path"]
|
||||||
# TODO: prevent ../../ style paths
|
full_path = (Path(root_path) / path).absolute()
|
||||||
full_path = Path(root_path) / path
|
# Ensure full_path is within root_path to avoid weird "../" tricks
|
||||||
await asgi_send(
|
try:
|
||||||
send,
|
full_path.relative_to(root_path)
|
||||||
full_path.open().read(),
|
except ValueError:
|
||||||
200,
|
await asgi_send_html(send, "404", 404)
|
||||||
{},
|
return
|
||||||
content_type=guess_type(str(full_path))[0] or "text/plain",
|
first = True
|
||||||
)
|
try:
|
||||||
|
async with aiofiles.open(full_path, mode="rb") as fp:
|
||||||
|
if first:
|
||||||
|
await asgi_start(
|
||||||
|
send, 200, {}, guess_type(str(full_path))[0] or "text/plain"
|
||||||
|
)
|
||||||
|
first = False
|
||||||
|
more_body = True
|
||||||
|
while more_body:
|
||||||
|
chunk = await fp.read(chunk_size)
|
||||||
|
more_body = len(chunk) == chunk_size
|
||||||
|
await send(
|
||||||
|
{
|
||||||
|
"type": "http.response.body",
|
||||||
|
"body": chunk,
|
||||||
|
"more_body": more_body,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
await asgi_send_html(send, "404", 404)
|
||||||
|
return
|
||||||
|
|
||||||
return inner_static
|
return inner_static
|
||||||
|
|
|
||||||
1
setup.py
1
setup.py
|
|
@ -49,6 +49,7 @@ setup(
|
||||||
"pint==0.8.1",
|
"pint==0.8.1",
|
||||||
"pluggy>=0.12.0",
|
"pluggy>=0.12.0",
|
||||||
"uvicorn>=0.8.1",
|
"uvicorn>=0.8.1",
|
||||||
|
"aiofiles==0.4.0",
|
||||||
],
|
],
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[console_scripts]
|
[console_scripts]
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,9 @@ class TestClient:
|
||||||
)
|
)
|
||||||
await instance.send_input({"type": "http.request"})
|
await instance.send_input({"type": "http.request"})
|
||||||
# First message back should be response.start with headers and status
|
# First message back should be response.start with headers and status
|
||||||
|
messages = []
|
||||||
start = await instance.receive_output(2)
|
start = await instance.receive_output(2)
|
||||||
|
messages.append(start)
|
||||||
assert start["type"] == "http.response.start"
|
assert start["type"] == "http.response.start"
|
||||||
headers = dict(
|
headers = dict(
|
||||||
[(k.decode("utf8"), v.decode("utf8")) for k, v in start["headers"]]
|
[(k.decode("utf8"), v.decode("utf8")) for k, v in start["headers"]]
|
||||||
|
|
@ -62,6 +64,7 @@ class TestClient:
|
||||||
body = b""
|
body = b""
|
||||||
while True:
|
while True:
|
||||||
message = await instance.receive_output(2)
|
message = await instance.receive_output(2)
|
||||||
|
messages.append(message)
|
||||||
assert message["type"] == "http.response.body"
|
assert message["type"] == "http.response.body"
|
||||||
body += message["body"]
|
body += message["body"]
|
||||||
if not message.get("more_body"):
|
if not message.get("more_body"):
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,14 @@ def test_homepage(app_client_two_attached_databases):
|
||||||
] == table_links
|
] == table_links
|
||||||
|
|
||||||
|
|
||||||
|
def test_static(app_client):
|
||||||
|
response = app_client.get("/-/static/app2.css")
|
||||||
|
assert response.status == 404
|
||||||
|
response = app_client.get("/-/static/app.css")
|
||||||
|
assert response.status == 200
|
||||||
|
assert "text/css" == response.headers["content-type"]
|
||||||
|
|
||||||
|
|
||||||
def test_memory_database_page():
|
def test_memory_database_page():
|
||||||
for client in make_app_client(memory=True):
|
for client in make_app_client(memory=True):
|
||||||
response = client.get("/:memory:")
|
response = client.get("/:memory:")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue