diff --git a/starlette/request.py b/starlette/request.py index 388d9be4..49d19510 100644 --- a/starlette/request.py +++ b/starlette/request.py @@ -1,5 +1,6 @@ from starlette.datastructures import URL, Headers, QueryParams from collections.abc import Mapping +from urllib.parse import unquote import json import typing @@ -37,11 +38,23 @@ class Request(Mapping): url = "%s://%s%s" % (scheme, host, path) if query_string: - url += "?" + query_string.decode() + url += "?" + unquote(query_string.decode()) self._url = URL(url) return self._url + @property + def relative_url(self) -> URL: + if not hasattr(self, "_relative_url"): + url = self._scope["path"] + query_string = self._scope["query_string"] + + if query_string: + url += "?" + unquote(query_string.decode()) + + self._relative_url = URL(url) + return self._relative_url + @property def headers(self) -> Headers: if not hasattr(self, "_headers"): diff --git a/tests/test_request.py b/tests/test_request.py index fbbc4cd9..ac904120 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -19,6 +19,24 @@ def test_request_url(): assert response.json() == {"method": "GET", "url": "https://example.org:123/"} +def test_request_relative_url(): + def app(scope): + async def asgi(receive, send): + request = Request(scope, receive) + data = {"method": request.method, "relative_url": request.relative_url} + response = JSONResponse(data) + await response(receive, send) + + return asgi + + client = TestClient(app) + response = client.get("/123?a=abc") + assert response.json() == {"method": "GET", "relative_url": "/123?a=abc"} + + response = client.get("https://example.org:123/") + assert response.json() == {"method": "GET", "relative_url": "/"} + + def test_request_query_params(): def app(scope): async def asgi(receive, send):