diff --git a/docs/sanic/request_data.md b/docs/sanic/request_data.md index e778faf6..4f6bc970 100644 --- a/docs/sanic/request_data.md +++ b/docs/sanic/request_data.md @@ -75,6 +75,10 @@ The following variables are accessible as properties on `Request` objects: - `ip` (str) - IP address of the requester. +- `port` (str) - Port address of the requester. + +- `socket` (tuple) - (IP, port) of the requester. + - `app` - a reference to the Sanic application object that is handling this request. This is useful when inside blueprints or other handlers in modules that do not have access to the global `app` object. ```python diff --git a/sanic/request.py b/sanic/request.py index 26f19baf..d4f6dc6f 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -46,7 +46,8 @@ class Request(dict): __slots__ = ( 'app', 'headers', 'version', 'method', '_cookies', 'transport', 'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files', - '_ip', '_parsed_url', 'uri_template', 'stream', '_remote_addr' + '_ip', '_parsed_url', 'uri_template', 'stream', '_remote_addr', + '_socket', '_port' ) def __init__(self, url_bytes, headers, version, method, transport): @@ -167,11 +168,27 @@ class Request(dict): @property def ip(self): - if not hasattr(self, '_ip'): - self._ip = (self.transport.get_extra_info('peername') or - (None, None)) + if not hasattr(self, '_socket'): + self._get_address() return self._ip + @property + def port(self): + if not hasattr(self, '_socket'): + self._get_address() + return self._port + + @property + def socket(self): + if not hasattr(self, '_socket'): + self._get_socket() + return self._socket + + def _get_address(self): + self._socket = (self.transport.get_extra_info('peername') or + (None, None)) + self._ip, self._port = self._socket + @property def remote_addr(self): """Attempt to return the original client ip based on X-Forwarded-For. diff --git a/tests/test_requests.py b/tests/test_requests.py index f0696c7f..e47520c4 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -27,6 +27,16 @@ def test_sync(): assert response.text == 'Hello' +def test_remote_address(): + app = Sanic('test_text') + + @app.route('/') + def handler(request): + return text("{}".format(request.ip)) + + request, response = app.test_client.get('/') + + assert response.text == '127.0.0.1' def test_text(): app = Sanic('test_text')