diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py index 08c57b26..bf8461f8 100644 --- a/datasette/utils/asgi.py +++ b/datasette/utils/asgi.py @@ -90,8 +90,7 @@ class Request: def actor(self): return self.scope.get("actor", None) - async def post_vars(self): - body = [] + async def post_body(self): body = b"" more_body = True while more_body: @@ -99,7 +98,10 @@ class Request: assert message["type"] == "http.request", message body += message.get("body", b"") more_body = message.get("more_body", False) + return body + async def post_vars(self): + body = await self.post_body() return dict(parse_qsl(body.decode("utf-8"), keep_blank_values=True)) @classmethod diff --git a/docs/internals.rst b/docs/internals.rst index e4d0ea50..7ae836e9 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -48,11 +48,14 @@ The request object is passed to various plugin hooks. It represents an incoming ``.actor`` - dictionary (str -> Any) or None The currently authenticated actor (see :ref:`actors `), or ``None`` if the request is unauthenticated. -The object also has one awaitable method: +The object also has two awaitable methods: ``await request.post_vars()`` - dictionary Returns a dictionary of form variables that were submitted in the request body via ``POST``. Don't forget to read about :ref:`internals_csrf`! +``await request.post_body()`` - bytes + Returns the un-parsed body of a request submitted by ``POST`` - useful for things like incoming JSON data. + .. _internals_multiparams: The MultiParams class diff --git a/tests/test_internals_request.py b/tests/test_internals_request.py index 8367a693..a659262b 100644 --- a/tests/test_internals_request.py +++ b/tests/test_internals_request.py @@ -1,4 +1,5 @@ from datasette.utils.asgi import Request +import json import pytest @@ -26,6 +27,34 @@ async def test_request_post_vars(): assert {"foo": "bar", "baz": "1", "empty": ""} == await request.post_vars() +@pytest.mark.asyncio +async def test_request_post_body(): + scope = { + "http_version": "1.1", + "method": "POST", + "path": "/", + "raw_path": b"/", + "query_string": b"", + "scheme": "http", + "type": "http", + "headers": [[b"content-type", b"application/json"]], + } + + data = {"hello": "world"} + + async def receive(): + return { + "type": "http.request", + "body": json.dumps(data, indent=4).encode("utf-8"), + "more_body": False, + } + + request = Request(scope, receive) + body = await request.post_body() + assert isinstance(body, bytes) + assert data == json.loads(body) + + def test_request_args(): request = Request.fake("/foo?multi=1&multi=2&single=3") assert "1" == request.args.get("multi")