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的主要内容,如果未能解决你的问题,请参考以下文章

“last_login”列中的空值违反非空约束

Django 的 FormWizard 中的空 ModelFormset

检查 django 中的空查询集

Django - ajax 请求中的空会话数据

禁用 Django ModelForm 中的空选项

Django:过滤管理模板中的空模型条目