Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix error in make_asset_handler for html sources without extension #27

Merged
merged 2 commits into from
Feb 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions asgineer/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ def normalize_response(response):
return status, headers, body


def guess_content_type_from_body(body):
""" Guess the content-type based of the body.

* "text/html" for str bodies starting with ``<!DOCTYPE html>`` or ``<html>``.
* "text/plain" for other str bodies.
* "application/json" for dict bodies.
* "application/octet-stream" otherwise.
"""
if isinstance(body, str):
if body.startswith(("<!DOCTYPE html>", "<!doctype html>", "<html>")):
return "text/html"
else:
return "text/plain"
elif isinstance(body, dict):
return "application/json"
else:
return "application/octet-stream"


class BaseApplication:
""" Base ASGI application class.
"""
Expand Down Expand Up @@ -165,21 +184,20 @@ async def _handle_http(self, request, receive, send):

status, headers, body = normalize_response(result)

# Make sure that there is a content type
if "content-type" not in headers:
headers["content-type"] = guess_content_type_from_body(body)

# Convert the body
if isinstance(body, bytes):
pass # Make no assumptions about the content-type
pass
elif isinstance(body, str):
if body.startswith(("<!DOCTYPE html>", "<html>")):
headers.setdefault("content-type", "text/html")
else:
headers.setdefault("content-type", "text/plain")
body = body.encode()
elif isinstance(body, dict):
try:
body = json.dumps(body).encode()
except Exception as err:
raise ValueError(f"Could not JSON encode body: {err}")
headers.setdefault("content-type", "application/json")
elif inspect.isasyncgen(body):
pass
else:
Expand Down
8 changes: 3 additions & 5 deletions asgineer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import hashlib
import mimetypes

from ._app import normalize_response
from ._app import normalize_response, guess_content_type_from_body


__all__ = ["normalize_response", "make_asset_handler"]
__all__ = ["normalize_response", "make_asset_handler", "guess_content_type_from_body"]


def make_asset_handler(assets, max_age=0, min_compress_size=256):
Expand Down Expand Up @@ -92,10 +92,8 @@ async def some_handler(request):
ctype, enc = mimetypes.guess_type(key)
if ctype:
ctypes[key] = ctype
elif isinstance(val, bytes):
ctypes[key] = "application/octet-stream"
else:
pass # str bodies get a content-type from Asgineer core.
ctypes[key] = guess_content_type_from_body(val)

async def asset_handler(request, path=None):
assert request.method in ("GET", "HEAD")
Expand Down
4 changes: 3 additions & 1 deletion docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ Utility functions

The ``asgineer.utils`` module provides a few utilities for common tasks.

.. autofunction:: asgineer.utils.make_asset_handler

.. autofunction:: asgineer.utils.normalize_response

.. autofunction:: asgineer.utils.make_asset_handler
.. autofunction:: asgineer.utils.guess_content_type_from_body
2 changes: 1 addition & 1 deletion tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


def get_backend():
return os.environ.get("ASGI_SERVER", "hypercorn").lower()
return os.environ.get("ASGI_SERVER", "mock").lower()


def set_backend_from_argv():
Expand Down
15 changes: 15 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def test_make_asset_handler():
# Make a server
assets = {"foo.html": "bla", "foo.png": b"x" * 10000}
assets.update({"b.xx": b"x", "t.xx": "x", "h.xx": "<html>x</html>"})
assets.update({"big.html": "x" * 10000, "bightml": "<html>" + "x" * 100000})
handler = asgineer.utils.make_asset_handler(assets)
server = make_server(asgineer.to_asgi(handler))

Expand Down Expand Up @@ -85,6 +86,20 @@ def test_make_asset_handler():
assert r7.status == 200 and r8.status == 200
assert len(r1.body) == 3 and len(r2.body) == 10000

# Big html files will be zipped, but must retain content type
for fname in ("big.html", "bightml"):
r = server.get(fname)
assert r.status == 200
assert r.headers.get("content-encoding", "identity") == "identity"
assert r.headers["content-type"] == "text/html"
plainbody = r.body

r = server.get(fname, headers={"accept-encoding": "gzip"})
assert r.status == 200
assert r.headers.get("content-encoding", "identity") == "gzip"
assert r.headers["content-type"] == "text/html"
assert len(r.body) < len(plainbody)


if __name__ == "__main__":
test_make_asset_handler()