diff --git a/docs/requests.md b/docs/requests.md index e07d6874..295fb854 100644 --- a/docs/requests.md +++ b/docs/requests.md @@ -61,6 +61,15 @@ Router path parameters are exposed as a dictionary interface. For example: `request.path_params['username']` +#### Client Address + +The client's remote address is exposed as a named two-tuple `request.client`. +Either item in the tuple may be `None`. + +The hostname or IP address: `request.client.host` + +The port number from which the client is connecting: `request.client.port` + #### Cookies Cookies are exposed as a regular dictionary interface. diff --git a/starlette/datastructures.py b/starlette/datastructures.py index 50f0d5bc..c6fe1e7c 100644 --- a/starlette/datastructures.py +++ b/starlette/datastructures.py @@ -1,8 +1,11 @@ import typing +from collections import namedtuple from urllib.parse import ParseResult, parse_qsl, urlencode, urlparse from starlette.types import Scope +Address = namedtuple("Address", ["host", "port"]) + class URL: def __init__( diff --git a/starlette/requests.py b/starlette/requests.py index 9fae0332..21321519 100644 --- a/starlette/requests.py +++ b/starlette/requests.py @@ -3,7 +3,7 @@ import json import typing from collections.abc import Mapping -from starlette.datastructures import URL, Headers, QueryParams +from starlette.datastructures import URL, Address, Headers, QueryParams from starlette.formparsers import FormParser, MultiPartParser from starlette.types import Message, Receive, Scope @@ -71,6 +71,11 @@ class HTTPConnection(Mapping): self._cookies = cookies return self._cookies + @property + def client(self) -> Address: + host, port = self._scope.get("client") or (None, None) + return Address(host=host, port=port) + @property def session(self) -> dict: assert ( diff --git a/tests/test_requests.py b/tests/test_requests.py index 90ad4e8a..9e02c9ff 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -63,6 +63,22 @@ def test_request_headers(): } +def test_request_client(): + def app(scope): + async def asgi(receive, send): + request = Request(scope, receive) + response = JSONResponse( + {"host": request.client.host, "port": request.client.port} + ) + await response(receive, send) + + return asgi + + client = TestClient(app) + response = client.get("/") + assert response.json() == {"host": "testclient", "port": 50000} + + def test_request_body(): def app(scope): async def asgi(receive, send):