Add options to control the behavior of Request.remote_addr (#1539)
* Add options to control the behavior of Request.remote_addr * Update tests for Request.remote_addr * Update documentation for Request.remote_addr
This commit is contained in:
parent
5631a31099
commit
5c9ba189bc
|
@ -85,16 +85,19 @@ DB_USER = 'appuser'
|
|||
|
||||
Out of the box there are just a few predefined values which can be overwritten when creating the application.
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ------------------------- | --------- | --------------------------------------------------------- |
|
||||
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
||||
| REQUEST_BUFFER_QUEUE_SIZE | 100 | Request streaming buffer queue size |
|
||||
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
|
||||
| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) |
|
||||
| KEEP_ALIVE | True | Disables keep-alive when False |
|
||||
| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) |
|
||||
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
|
||||
| ACCESS_LOG | True | Disable or enable access log |
|
||||
| Variable | Default | Description |
|
||||
| ------------------------- | ----------------- | --------------------------------------------------------------------------- |
|
||||
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
||||
| REQUEST_BUFFER_QUEUE_SIZE | 100 | Request streaming buffer queue size |
|
||||
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
|
||||
| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) |
|
||||
| KEEP_ALIVE | True | Disables keep-alive when False |
|
||||
| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) |
|
||||
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
|
||||
| ACCESS_LOG | True | Disable or enable access log |
|
||||
| PROXIES_COUNT | -1 | The number of proxy servers in front of the app (e.g. nginx; see below) |
|
||||
| FORWARDED_FOR_HEADER | "X-Forwarded-For" | The name of "X-Forwarded-For" HTTP header that contains client and proxy ip |
|
||||
| REAL_IP_HEADER | "X-Real-IP" | The name of "X-Real-IP" HTTP header that contains real client ip |
|
||||
|
||||
### The different Timeout variables:
|
||||
|
||||
|
@ -143,3 +146,17 @@ Firefox client hard keepalive limit = 115 seconds
|
|||
Opera 11 client hard keepalive limit = 120 seconds
|
||||
Chrome 13+ client keepalive limit > 300+ seconds
|
||||
```
|
||||
|
||||
### About proxy servers and client ip
|
||||
|
||||
When you use a reverse proxy server (e.g. nginx), the value of `request.ip` will contain ip of a proxy, typically `127.0.0.1`. To determine the real client ip, `X-Forwarded-For` and `X-Real-IP` HTTP headers are used. But client can fake these headers if they have not been overridden by a proxy. Sanic has a set of options to determine the level of confidence in these headers.
|
||||
|
||||
* If you have a single proxy, set `PROXIES_COUNT` to `1`. Then Sanic will use `X-Real-IP` if available or the last ip from `X-Forwarded-For`.
|
||||
|
||||
* If you have multiple proxies, set `PROXIES_COUNT` equal to their number to allow Sanic to select the correct ip from `X-Forwarded-For`.
|
||||
|
||||
* If you don't use a proxy, set `PROXIES_COUNT` to `0` to ignore these headers and prevent ip falsification.
|
||||
|
||||
* If you don't use `X-Real-IP` (e.g. your proxy sends only `X-Forwarded-For`), set `REAL_IP_HEADER` to an empty string.
|
||||
|
||||
The real ip will be available in `request.remote_addr`. If HTTP headers are unavailable or untrusted, `request.remote_addr` will be an empty string; in this case use `request.ip` instead.
|
||||
|
|
|
@ -64,6 +64,26 @@ of the memory leak.
|
|||
|
||||
See the [Gunicorn Docs](http://docs.gunicorn.org/en/latest/settings.html#max-requests) for more information.
|
||||
|
||||
## Running behind a reverse proxy
|
||||
|
||||
Sanic can be used with a reverse proxy (e.g. nginx). There's a simple example of nginx configuration:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.org;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to get real client ip, you should configure `X-Real-IP` and `X-Forwarded-For` HTTP headers and set `app.config.PROXIES_COUNT` to `1`; see the configuration page for more information.
|
||||
|
||||
## Disable debug logging
|
||||
|
||||
To improve the performance add `debug=False` and `access_log=False` in the `run` arguments.
|
||||
|
|
|
@ -25,6 +25,9 @@ DEFAULT_CONFIG = {
|
|||
"WEBSOCKET_WRITE_LIMIT": 2 ** 16,
|
||||
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
||||
"ACCESS_LOG": True,
|
||||
"PROXIES_COUNT": -1,
|
||||
"FORWARDED_FOR_HEADER": "X-Forwarded-For",
|
||||
"REAL_IP_HEADER": "X-Real-IP",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -355,19 +355,38 @@ class Request(dict):
|
|||
|
||||
@property
|
||||
def remote_addr(self):
|
||||
"""Attempt to return the original client ip based on X-Forwarded-For.
|
||||
"""Attempt to return the original client ip based on X-Forwarded-For
|
||||
or X-Real-IP. If HTTP headers are unavailable or untrusted, returns
|
||||
an empty string.
|
||||
|
||||
:return: original client ip.
|
||||
"""
|
||||
if not hasattr(self, "_remote_addr"):
|
||||
forwarded_for = self.headers.get("X-Forwarded-For", "").split(",")
|
||||
remote_addrs = [
|
||||
addr
|
||||
for addr in [addr.strip() for addr in forwarded_for]
|
||||
if addr
|
||||
]
|
||||
if len(remote_addrs) > 0:
|
||||
self._remote_addr = remote_addrs[0]
|
||||
if self.app.config.PROXIES_COUNT == 0:
|
||||
self._remote_addr = ""
|
||||
elif self.app.config.REAL_IP_HEADER and self.headers.get(
|
||||
self.app.config.REAL_IP_HEADER
|
||||
):
|
||||
self._remote_addr = self.headers[
|
||||
self.app.config.REAL_IP_HEADER
|
||||
]
|
||||
elif self.app.config.FORWARDED_FOR_HEADER:
|
||||
forwarded_for = self.headers.get(
|
||||
self.app.config.FORWARDED_FOR_HEADER, ""
|
||||
).split(",")
|
||||
remote_addrs = [
|
||||
addr
|
||||
for addr in [addr.strip() for addr in forwarded_for]
|
||||
if addr
|
||||
]
|
||||
if self.app.config.PROXIES_COUNT == -1:
|
||||
self._remote_addr = remote_addrs[0]
|
||||
elif len(remote_addrs) >= self.app.config.PROXIES_COUNT:
|
||||
self._remote_addr = remote_addrs[
|
||||
-self.app.config.PROXIES_COUNT
|
||||
]
|
||||
else:
|
||||
self._remote_addr = ""
|
||||
else:
|
||||
self._remote_addr = ""
|
||||
return self._remote_addr
|
||||
|
|
|
@ -29,7 +29,7 @@ def test_sync(app):
|
|||
assert response.text == "Hello"
|
||||
|
||||
|
||||
def test_remote_address(app):
|
||||
def test_ip(app):
|
||||
@app.route("/")
|
||||
def handler(request):
|
||||
return text("{}".format(request.ip))
|
||||
|
@ -203,11 +203,23 @@ def test_content_type(app):
|
|||
assert response.text == "application/json"
|
||||
|
||||
|
||||
def test_remote_addr(app):
|
||||
def test_remote_addr_with_two_proxies(app):
|
||||
app.config.PROXIES_COUNT = 2
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text(request.remote_addr)
|
||||
|
||||
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == "127.0.0.2"
|
||||
assert response.text == "127.0.0.2"
|
||||
|
||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == ""
|
||||
assert response.text == ""
|
||||
|
||||
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == "127.0.0.1"
|
||||
|
@ -222,6 +234,86 @@ def test_remote_addr(app):
|
|||
assert request.remote_addr == "127.0.0.1"
|
||||
assert response.text == "127.0.0.1"
|
||||
|
||||
headers = {
|
||||
"X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2"
|
||||
}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == "127.0.0.1"
|
||||
assert response.text == "127.0.0.1"
|
||||
|
||||
|
||||
def test_remote_addr_with_infinite_number_of_proxies(app):
|
||||
app.config.PROXIES_COUNT = -1
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text(request.remote_addr)
|
||||
|
||||
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == "127.0.0.2"
|
||||
assert response.text == "127.0.0.2"
|
||||
|
||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == "127.0.1.1"
|
||||
assert response.text == "127.0.1.1"
|
||||
|
||||
headers = {
|
||||
"X-Forwarded-For": "127.0.0.5, 127.0.0.4, 127.0.0.3, 127.0.0.2, 127.0.0.1"
|
||||
}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == "127.0.0.5"
|
||||
assert response.text == "127.0.0.5"
|
||||
|
||||
|
||||
def test_remote_addr_without_proxy(app):
|
||||
app.config.PROXIES_COUNT = 0
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text(request.remote_addr)
|
||||
|
||||
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == ""
|
||||
assert response.text == ""
|
||||
|
||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == ""
|
||||
assert response.text == ""
|
||||
|
||||
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == ""
|
||||
assert response.text == ""
|
||||
|
||||
|
||||
def test_remote_addr_custom_headers(app):
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "Client-IP"
|
||||
app.config.FORWARDED_FOR_HEADER = "Forwarded"
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text(request.remote_addr)
|
||||
|
||||
headers = {"X-Real-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == "127.0.1.1"
|
||||
assert response.text == "127.0.1.1"
|
||||
|
||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == ""
|
||||
assert response.text == ""
|
||||
|
||||
headers = {"Client-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
||||
request, response = app.test_client.get("/", headers=headers)
|
||||
assert request.remote_addr == "127.0.0.2"
|
||||
assert response.text == "127.0.0.2"
|
||||
|
||||
|
||||
def test_match_info(app):
|
||||
@app.route("/api/v1/user/<user_id>/")
|
||||
|
|
Loading…
Reference in New Issue
Block a user