Repository: sanic-org/sanic
Pull request #2790 (2023-07-13 13:48:34)

Add request.client_ip
This has been on TODO forever. I think it is time to get it sorted out. The name `client_ip` matches what others are calling this (Caddy just implemented the same thing).


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sanic-org/sanic/pull/2790?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) Report
Patch coverage: **`100.000`**% and project coverage change: **`+0.004`** :tada:
> Comparison is base [(`dc3c4d1`)](https://app.codecov.io/gh/sanic-org/sanic/commit/dc3c4d13932a3f753bd6eb5da815bbf642a02185?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) 88.906% compared to head [(`ffffc7c`)](https://app.codecov.io/gh/sanic-org/sanic/pull/2790?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) 88.911%.

> :exclamation: Current head ffffc7c differs from pull request most recent head 809e64a. Consider uploading reports for the commit 809e64a to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@              Coverage Diff              @@
##              main     #2790       +/-   ##
=============================================
+ Coverage   88.906%   88.911%   +0.004%     
=============================================
  Files           92        92               
  Lines         7031      7034        +3     
  Branches      1195      1195               
=============================================
+ Hits          6251      6254        +3     
  Misses         534       534               
  Partials       246       246               
```


| [Impacted Files](https://app.codecov.io/gh/sanic-org/sanic/pull/2790?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) | Coverage Δ | |
|---|---|---|
| [sanic/request/types.py](https://app.codecov.io/gh/sanic-org/sanic/pull/2790?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org#diff-c2FuaWMvcmVxdWVzdC90eXBlcy5weQ==) | `94.957% <100.000%> (+0.042%)` | :arrow_up: |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/sanic-org/sanic/pull/2790?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).


Patch:
diff --git a/sanic/request/types.py b/sanic/request/types.py
index 98f6e908..53d1a402 100644
--- a/sanic/request/types.py
+++ b/sanic/request/types.py
@@ -838,19 +838,31 @@ class Request(Generic[sanic_type, ctx_type]):
     @property
     def remote_addr(self) -> str:
         """
-        Client IP address, if available.
-        1. proxied remote address `self.forwarded['for']`
-        2. local remote address `self.ip`
+        Client IP address, if available from proxy.
 
         :return: IPv4, bracketed IPv6, UNIX socket name or arbitrary string
         :rtype: str
         """
         if not hasattr(self, "_remote_addr"):
-            self._remote_addr = str(
-                self.forwarded.get("for", "")
-            )  # or self.ip
+            self._remote_addr = str(self.forwarded.get("for", ""))
         return self._remote_addr
 
+    @property
+    def client_ip(self) -> str:
+        """
+        Client IP address.
+        1. proxied remote address `self.forwarded['for']`
+        2. local peer address `self.ip`
+
+        New in Sanic 23.6. Prefer this over `remote_addr` for determining the
+        client address regardless of whether the service runs behind a proxy
+        or not (proxy deployment needs separate configuration).
+
+        :return: IPv4, bracketed IPv6, UNIX socket name or arbitrary string
+        :rtype: str
+        """
+        return self.remote_addr or self.ip
+
     @property
     def scheme(self) -> str:
         """


Test patch:
diff --git a/tests/test_requests.py b/tests/test_requests.py
index 4a995d4c..6719025b 100644
--- a/tests/test_requests.py
+++ b/tests/test_requests.py
@@ -513,6 +513,7 @@ def test_standard_forwarded(app):
     request, response = app.test_client.get("/", headers=headers)
     assert response.json == {"for": "127.0.0.2", "proto": "ws"}
     assert request.remote_addr == "127.0.0.2"
+    assert request.client_ip == "127.0.0.2"
     assert request.scheme == "ws"
     assert request.server_name == "local.site"
     assert request.server_port == 80
@@ -737,6 +738,7 @@ def test_remote_addr_with_two_proxies(app):
     headers = {"X-Forwarded-For": "127.0.1.1"}
     request, response = app.test_client.get("/", headers=headers)
     assert request.remote_addr == ""
+    assert request.client_ip == "127.0.0.1"
     assert response.body == b""
 
     headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}