Django - 中间件在多租户应用程序上失去与数据库的连接

问题描述 投票:0回答:1

在我的 Django 应用程序中,我使用带有隔离数据库的多租户方法。它运行良好,但因为它依赖于每个租户的子域,这是不可扩展的,我正在尝试改变它。为了实现此功能,我尝试使用中间件和会话从用户名中检索租户,并使用它为数据库路由器设置本地变量。逻辑是这样的:

如果用户未登录,BeforeLoginMiddleware 将激活并从用户检索租户名称。因此 username@tenant1 会将 tenant1 设置为会话。这是代码:

import threading
from users.forms import LoginForm

Thread_Local = threading.local()

class BeforeLoginMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        if request.path == '/login/':

            form = LoginForm(request.POST)

            if form.is_valid():

                complete_username = form.cleaned_data.get('username')
                current_db = complete_username.split('@')[1]
                request.session['current_db'] = current_db
                request.session.modified = True

        response = self.get_response(request)

        return response

如果用户已登录,第二个中间件将从会话中检索租户数据,并使用它来定义在数据库路由器上使用的函数上调用的 Thread_Local 变量:

class AppMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        current_db = request.session.get('current_db')
        setattr(Thread_Local, 'current_db', current_db)
        response = self.get_response(request)

        return response

def get_current_db_name():
    return getattr(Thread_Local, 'current_db', None)

这是 routers.py 文件:

class AppRouter:

    def db_for_read(self, model, **hints):
        return get_current_db_name()

    def db_for_write(self, model, **hints):
        return get_current_db_name()

    def allow_relation(self, *args, **kwargs):
        return True

    def allow_syncdb(self, *args, **kwargs):
        return None

    def allow_migrate(self, *args, **kwargs):
        return None

这是我的应用程序上的中间件设置:

MIDDLEWARE = [
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'myapp.middleware.BeforeLoginMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'myapp.middleware.AppMiddleware',
]

这按预期工作,但随机(或者至少看起来是随机)它失去了与数据库的连接,我认为,由于它无法检索会话数据,因此将用户再次重定向到登录页面(我我还使用了 login_required 装饰器)。有时我可以导航到几个不同的页面,一切都很好,然后就断开了连接。有时,登录后导航到主页后的第一页时会断开连接。如果页面空闲大约 1 分钟也会发生这种情况,这是没有意义的,因为我的 SESSION_COOKIE_AGE 设置是 1200。

问题是,我不知道是什么原因造成的,因为没有错误。我注意到发生这种情况时唯一一致的是,它是在浏览器的网络选项卡中注册的 302 状态(这是重定向到登录页面)和消息 Broken pipeline from ('127.0.0.1', 61980) 在终端中,末尾带有此随机代码。

到目前为止我所尝试的,上述行为没有任何改变:

  • 中间件有很多不同的设置。例如:一开始我写了一个 具有所有逻辑的单个中间件并将其放在最后 中间件列表。我认为这可能会导致问题,所以我分离了逻辑。另一种尝试是在中间件中使用 request.user.is_authenticated 而不是使用会话数据。
  • 基于这个答案我尝试禁用每个JS脚本 应用程序(只有几个)。这并不是一个真正的选择,但我认为它排除了成为原因的可能性。
  • 完全清除浏览器缓存/cookie。
  • SESSION_SAVE_EVERY_REQUEST 设置更改为 True。
  • SESSION_COOKIE_AGE 设置更改为 120000(我认为增加这个数字没有意义,但在这一点上,我愿意尝试任何事情)。

老实说,我已经没有选择了,所以我感谢任何帮助或建议。

python django django-middleware
1个回答
0
投票

回答这个问题以防对其他人有帮助:

我不完全确定这就是问题所在,但我认为依靠会话来检查登录数据是不可靠的。我的意思是,如果与服务器的连接被切断,即使是暂时的,也无法访问数据库,因此用户会被注销。所以我的解决方案是使用 URL 保留租户定义,但我没有不同的子域,而是有不同的 URL 后缀,如下所示:

mysite.com/tenant1
mysite.com/tenant2

这意味着大量的重构。当前的中间件如下所示:

class MyMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        full_path = request.path
        tenant = full_path.split('/')[1]
        tenants_dict = dict(Tenants.objects.values_list('alias', 'database',))
        current_db = tenants_dict.get(tenant)

        setattr(threading_file.Thread_Local, 'current_db', current_db)
        request.tenant_name = tenant

        response = self.get_response(request)

        return response

我必须在所有 URL、视图、重定向和模板上添加后缀:


path('<str:tenant_name>/page/', views.page, name='page'),

def page(request, tenant_name):
    # view code

redirect('other_page', tenant_name)

<a href="{% url 'page' request.tenant_name %}">Link name</a>

就像我说的,这是一项艰巨的工作,但就我而言,不再处理子域是完全值得的。

© www.soinside.com 2019 - 2024. All rights reserved.