Django应用程序中的空REMOTE_ADDR值,当使用nginx作为反向代理与gunicorn
Posted
技术标签:
【中文标题】Django应用程序中的空REMOTE_ADDR值,当使用nginx作为反向代理与gunicorn【英文标题】:Empty REMOTE_ADDR value in Django application, when using nginx as reverse proxy with gunicorn 【发布时间】:2016-03-19 00:07:08 【问题描述】:我将 nginx 设置为反向代理,并在后台使用 gunicorn。这个网络服务器设置为我的 Django 应用程序提供数据,它有一个 postgresql 后端。整个设置托管在两台 Ubuntu 机器上(一个是应用程序机器,另一个是数据库机器)。
我只通过 gunicorn 测试了这个设置,没有 nginx。工作完美。接下来为了生产,我在 gunicorn 前面添加了 nginx 反向代理。我立刻遇到了一个令人沮丧的错误:invalid input syntax for type inet: ""(当用户尝试登录我的 Django 应用程序时出现)
登录我的应用程序的用户的 IP 保存在会话表中; Django 自己做到这一点。现在众所周知,Postgresql 要求所有客户端 IP 为 INET sort(其他一些数据库也允许字符串 IP,但不允许 postgres)。 INET 类型不允许“”(即空)值,而是抛出错误invalid input syntax for type inet: ""。
换句话说,我的 nginx 反向代理没有将 REMOTE_ADDR
的值发送到 Django 应用程序。仅使用 gunicorn 正确设置该值(因此一切正常)。如何让 nginx 在 Django 的 request.META 中将 $remote_addr
值传递给 REMOTE_ADDR
?
我尝试在我的 /etc/nginx/sites-avaialble/myproject 文件的 location
块中包含 proto_set_header REMOTE_ADDR $remote_addr;
。它不起作用 - 之后我可以在 request.META 中看到 HTTP_REMOTE_ADDR
值,但 REMOTE_ADDR
仍然是“”。
那么如何在 Django 的 request.META 中设置 REMOTE_ADDR(即客户端的 IP 地址)字段?也许我可以通过 gunicorn 明确地传递它?有人提到我应该在数据库端处理它 - 我不确定我该怎么做?我应该编辑 pg_hba.conf 或 postgresql.conf 还是什么?我查看了这些文件,没有选项可以记录“允许 IP 的空值”。此外,我宁愿将 $remote_addr 中的任何值传递给 Django,而不是让所有登录用户的 IP 为空。
别忘了,如果我单独使用 gunicorn,Django 的 request.META 中的 REMOTE_ADDR 会正确设置;所以我的猜测是问题在于我如何通过 nginx 传递它。
请帮忙!如果您觉得需要,请随时询问更多信息。
/etc/nginx/sites-available/myproject:
server
listen 80;
server_name example.cloudapp.net;
charset utf-8;
underscores_in_headers on;
location = /favicon.ico access_log off; log_not_found off;
location /static/
root /home/mhb11/folder/myproject;
location /
proxy_pass_request_headers on;
include proxy_params;
proxy_pass http://unix:/home/mhb11/folder/myproject/myproject.sock;
error_page 500 502 503 504 /500.html;
location = /500.html
root /home/mhb11/folder/myproject/templates/;
/etc/nginx/proxy_params:
proxy_set_header Host $host;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
【问题讨论】:
你能不能把proxy_set_header REMOTE_ADDR $remote_addr;
改成proxy_set_header REMOTE-ADDR $remote_addr;
?
试一试,不幸的是结果完全相同。我之前也尝试过,认为正确的语法可能是使用连字符而不是下划线,就像每个指令一样。没用。
也许有办法通过 gunicorn 显式传递这个?如果可以传递secure_scheme_headers:docs.gunicorn.org/en/19.3/settings.html#secure-scheme-headers,或许也可以传递REMOTE_ADDR 的值?
【参考方案1】:
我用下面的代码修复了它。
from django.utils.deprecation import MiddlewareMixin
class XForwardedForMiddleware(MiddlewareMixin):
def process_request(self, request):
if "HTTP_X_FORWARDED_FOR" in request.META:
request.META["REMOTE_ADDR"] = request.META["HTTP_X_FORWARDED_FOR"]
【讨论】:
【参考方案2】:这是一个已知问题。查看有关在运行域套接字服务器时 REMOTE_ADDR 无效的讨论:https://github.com/python-web-sig/wsgi-ng/issues/11
要确保所有依赖项保持不变,您可以做的一件事是编写中间件,在 Django 项目级别处理您的问题。
例如,像这样的:
class XForwardedForMiddleware():
def process_request(self, request):
if "HTTP_X_FORWARDED_FOR" in request.META:
request.META["HTTP_X_PROXY_REMOTE_ADDR"] = request.META["REMOTE_ADDR"]
parts = request.META["HTTP_X_FORWARDED_FOR"].split(",", 1)
request.META["REMOTE_ADDR"] = parts[0]
(source)
试试吧,它一定能解决你的问题。
【讨论】:
.has_key()
在 Python3 中不存在,但 if "HTTP_X_FORWARDED_FOR" in request.META
成功了。这解决了我的问题,谢谢!【参考方案3】:
ip_address = request.META.get("HTTP_X_REAL_IP")
它适用于大于 3 的 Django 版本。
【讨论】:
添加适用的版本,因为这是一个非常古老的问题。 EOR。【参考方案4】:我对提供的解决方案有一些小问题。在某些情况下,对于某些机器人,该值实际上包含“未知”作为第一个条目。所以我不得不对其进行调整以查看所有值,将第一个有效值作为REMOTE_ADDR
。
class XForwardedForMiddleware:
"""
Set REMOTE_ADDR if it's missing because of a reverse proxy (nginx + gunicorn) deployment.
https://***.com/questions/34251298/empty-remote-addr-value-in-django-application-when-using-nginx-as-reverse-proxy
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if 'HTTP_X_FORWARDED_FOR' in request.META:
remote_addrs = request.META['HTTP_X_FORWARDED_FOR'].split(',')
remote_addr = None
# for some bots, 'unknown' was prepended as the first value: `unknown, ***.***.***.***`
# in which case the second value actually is the correct one
for ip in remote_addrs:
ip = self._validated_ip(ip)
if ip is not None:
remote_addr = ip
break
if remote_addr is None:
raise SuspiciousOperation('Malformed X-Forwarded-For.')
request.META['HTTP_X_PROXY_REMOTE_ADDR'] = request.META['REMOTE_ADDR']
request.META['REMOTE_ADDR'] = remote_addr
return self.get_response(request)
def _validated_ip(self, ip):
ip = ip.strip()
try:
validate_ipv46_address(ip)
except ValidationError:
return None
return ip
【讨论】:
以上是关于Django应用程序中的空REMOTE_ADDR值,当使用nginx作为反向代理与gunicorn的主要内容,如果未能解决你的问题,请参考以下文章