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
|
@ -86,7 +86,7 @@ DB_USER = 'appuser'
|
||||||
Out of the box there are just a few predefined values which can be overwritten when creating the application.
|
Out of the box there are just a few predefined values which can be overwritten when creating the application.
|
||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
| ------------------------- | --------- | --------------------------------------------------------- |
|
| ------------------------- | ----------------- | --------------------------------------------------------------------------- |
|
||||||
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
||||||
| REQUEST_BUFFER_QUEUE_SIZE | 100 | Request streaming buffer queue size |
|
| REQUEST_BUFFER_QUEUE_SIZE | 100 | Request streaming buffer queue size |
|
||||||
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
|
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
|
||||||
|
@ -95,6 +95,9 @@ Out of the box there are just a few predefined values which can be overwritten w
|
||||||
| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) |
|
| 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) |
|
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
|
||||||
| ACCESS_LOG | True | Disable or enable access log |
|
| 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:
|
### The different Timeout variables:
|
||||||
|
|
||||||
|
@ -143,3 +146,17 @@ Firefox client hard keepalive limit = 115 seconds
|
||||||
Opera 11 client hard keepalive limit = 120 seconds
|
Opera 11 client hard keepalive limit = 120 seconds
|
||||||
Chrome 13+ client keepalive limit > 300+ 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.
|
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
|
## Disable debug logging
|
||||||
|
|
||||||
To improve the performance add `debug=False` and `access_log=False` in the `run` arguments.
|
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,
|
"WEBSOCKET_WRITE_LIMIT": 2 ** 16,
|
||||||
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
||||||
"ACCESS_LOG": True,
|
"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
|
@property
|
||||||
def remote_addr(self):
|
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.
|
:return: original client ip.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, "_remote_addr"):
|
if not hasattr(self, "_remote_addr"):
|
||||||
forwarded_for = self.headers.get("X-Forwarded-For", "").split(",")
|
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 = [
|
remote_addrs = [
|
||||||
addr
|
addr
|
||||||
for addr in [addr.strip() for addr in forwarded_for]
|
for addr in [addr.strip() for addr in forwarded_for]
|
||||||
if addr
|
if addr
|
||||||
]
|
]
|
||||||
if len(remote_addrs) > 0:
|
if self.app.config.PROXIES_COUNT == -1:
|
||||||
self._remote_addr = remote_addrs[0]
|
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:
|
else:
|
||||||
self._remote_addr = ""
|
self._remote_addr = ""
|
||||||
return self._remote_addr
|
return self._remote_addr
|
||||||
|
|
|
@ -29,7 +29,7 @@ def test_sync(app):
|
||||||
assert response.text == "Hello"
|
assert response.text == "Hello"
|
||||||
|
|
||||||
|
|
||||||
def test_remote_address(app):
|
def test_ip(app):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("{}".format(request.ip))
|
return text("{}".format(request.ip))
|
||||||
|
@ -203,11 +203,23 @@ def test_content_type(app):
|
||||||
assert response.text == "application/json"
|
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("/")
|
@app.route("/")
|
||||||
async def handler(request):
|
async def handler(request):
|
||||||
return text(request.remote_addr)
|
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"}
|
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.1"
|
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 request.remote_addr == "127.0.0.1"
|
||||||
assert response.text == "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):
|
def test_match_info(app):
|
||||||
@app.route("/api/v1/user/<user_id>/")
|
@app.route("/api/v1/user/<user_id>/")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user