【问题标题】:Access Apache SetEnv variable from Django wsgi.py file从 Django wsgi.py 文件访问 Apache SetEnv 变量
【发布时间】:2013-11-14 07:27:41
【问题描述】:

正如广泛建议的那样,我正在尝试将 Django 的密钥和 DB 传递到环境变量中,这样我就可以在本地/生产服务器之间使用相同的代码库。

我遇到的问题是正确设置然后读取运行 Apache + mod_wsgi 的生产服务器上的环境变量。

在我的用户配置文件中设置的变量不可用,因为 Apache 不是以该用户身份运行的。在虚拟主机文件中使用SetEnv 设置的变量不可用,因为范围有所不同。

我已经阅读了一些1,2 的 SO 答案,这导致了 this blog 的解决方案。

我不知道如何将解决方案应用于使用 wsgi.py 文件的当前版本的 Django,如下所示:

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

如何将该博客解决方案应用到 wsgi.py 文件,或者是否有更好的地方来存储 Django 可以获取的环境变量?

【问题讨论】:

    标签: django apache ubuntu


    【解决方案1】:

    如果其他人对格雷厄姆的回答感到沮丧,这里有一个真正适用于原始问题的解决方案。我个人觉得从 Apache 设置环境变量非常有用和实用,尤其是因为我配置了自己的托管环境并且可以做任何我想做的事情。

    wsgi.py(在 Django 1.5.4 中测试)

    from django.core.handlers.wsgi import WSGIHandler
    
    class WSGIEnvironment(WSGIHandler):
    
        def __call__(self, environ, start_response):
    
            os.environ['SETTINGS_CONFIG'] = environ['SETTINGS_CONFIG']
            return super(WSGIEnvironment, self).__call__(environ, start_response)
    
    application = WSGIEnvironment()
    

    需要注意的是,您丢失了django.core.wsgi.get_wsgi_application 的面向未来的方法,该方法目前仅返回WSGIHandler()。如果 WSGIHandler.__call__ 方法被更新并且您也更新了 Django,那么如果参数发生变化,您可能必须更新 WSGIEnvironment 类。我认为这是为了方便而支付的非常小的罚款。

    【讨论】:

    • 从 Django 1.8.2(可能还有更早的版本)开始,get_wsgi_application() 多了一行:import djangodjango.setup()return WSGIHandler()
    • @Izkata 你能举一个完整的例子来说明更新的做事方式吗?
    • @Cerin 答案的最后一段。在 1.5.4 和 1.8.2 之间的某个时候,该功能发生了变化,因此如答案中所述,您现在确实会因为不使用面向未来的 get_wsgi_application() 而失去 - 请参阅下面的其他答案,了解如何添加此功能中缺少的功能回答。
    【解决方案2】:

    FWIW。依靠环境变量进行细粒度的配置设置通常不是一个好主意。这是因为并非所有 WSGI 托管环境或商业 PaaS 产品都支持这一概念。使用环境变量进行细粒度设置还可以有效地将您锁定在特定的 PaaS 产品中,您可以将特定命名环境变量的查找直接嵌入到您的代码中,其中该环境变量的命名约定特定于该托管服务。因此,尽管环境变量的使用是由某些服务推动的,但始终要小心依赖环境变量,因为它会降低 WSGI 应用程序的可移植性,并使在部署机制之间移动变得更加困难。

    话虽如此,您提到的博客文章通常不会有帮助。这是因为它使用了一个讨厌的技巧,即根据在 Apache 中使用 SetEnv 设置的每个请求 WSGI 环境设置,在每个请求上设置进程环境变量。如果环境变量的值可能因 URL 上下文不同而不同,这可能会导致多线程配置中出现各种问题。对于 Django 的情况,它没有帮助,因为 Django 设置模块通常会在处理任何请求之前被导入,这意味着环境变量在需要的时候将不可用。

    整个部署配置领域迫切需要更好的处理方式,但坦率地说,这主要是一个失败的原因,因为托管服务不会改变事情以适应更好的 WSGI 部署策略。他们已经完成了他们的工作,让他们的客户锁定了他们已经完成的方式,并且即使存在更好的方式,也不打算为自己创造工作和改变事情。

    无论如何,“计算机科学中的所有问题都可以通过另一个间接级别来解决”。 (http://en.wikipedia.org/wiki/Indirection) 这就是你可以在这里做的。

    没有您的应用程序查找环境变量。让它导入一个特定于部署的 Python 配置模块,其中包含使用 API 获取配置设置的方法。此配置模块将根据部署机制实现获取实际设置的不同方式。在某些情况下,它可以从环境变量中获取值。对于其他,例如 Apache/mod_wsgi,这些值可以在该配置模块中,或者从单独的配置文件中读取,该文件可以是 ini、json 或 yaml 格式。在提供 API 时,它还可以将配置设置的名称映射到不同 PaaS 产品使用的不同名称。

    此配置模块不需要成为您的应用程序代码的一部分,但可以手动放置到目标系统上的“/etc/”子目录中。然后,您需要设置 Python 模块搜索路径,以便您的应用程序可以看到它。作为更广泛、更好的 WSGI 部署标准的一部分,整个系统可以变得相当优雅,但正如我所说,当现有的 PaaS 产品极不可能改变以使用这样的标准时,几乎没有动力去努力创造这样的东西.

    【讨论】:

    【解决方案3】:

    这是另一种解决方案,与 get_wsgi_application 一样具有前瞻性。它甚至可以让你设置环境变量以在你的 Django 初始化中使用。

    # in wsgi.py
    
    KEYS_TO_LOAD = [
        # A list of the keys you'd like to load from the WSGI environ
        # into os.environ
    ]
    
    def loading_app(wsgi_environ, start_response):
        global real_app
        import os
        for key in KEYS_TO_LOAD:
            try:
                os.environ[key] = wsgi_environ[key]
            except KeyError:
                # The WSGI environment doesn't have the key
                pass
        from django.core.wsgi import get_wsgi_application
        real_app = get_wsgi_application()
        return real_app(wsgi_environ, start_response)
    
    real_app = loading_app
    
    application = lambda env, start: real_app(env, start)
    

    我不是 100% 清楚 mod_wsgi 如何管理其进程,但我认为它不会经常重新加载 WSGI 应用程序。如果是这样,初始化 Django 的性能损失只会发生一次,在第一个请求中。

    或者,如果您不需要在初始化 Django 之前设置环境变量,您可以使用以下内容:

    # in wsgi.py
    
    KEYS_TO_LOAD = [
        # A list of the keys you'd like to load from the WSGI environ
        # into os.environ
    ]
    
    from django.core.wsgi import get_wsgi_application
    django_app = get_wsgi_application()
    
    def loading_app(wsgi_environ, start_response):
        global real_app
        import os
        for key in KEYS_TO_LOAD:
            try:
                os.environ[key] = wsgi_environ[key]
            except KeyError:
                # The WSGI environment doesn't have the key
                pass
        real_app = django_app
        return real_app(wsgi_environ, start_response)
    
    real_app = loading_app
    
    application = lambda env, start: real_app(env, start)
    

    【讨论】:

    • 它有效,但你错过了:os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
    【解决方案4】:

    对于 Django 1.11:

    Apache 配置:

    <VirtualHost *:80 >
        ...
        SetEnv VAR_NAME VAR_VALUE
    </VirtualHost>
    

    wsgi.py:

    import os
    import django
    from django.core.handlers.wsgi import WSGIHandler
    
    class WSGIEnvironment(WSGIHandler):
        def __call__(self, environ, start_response):
            os.environ["VAR_NAME"] = environ.get("VAR_NAME", "")
            return super(WSGIEnvironment, self).__call__(environ, start_response)
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
    django.setup(set_prefix=False)
    application = WSGIEnvironment()
    

    【讨论】:

    • 这在 Django 2.2 中停止为我可靠地工作。似乎存在某种微妙的竞争条件或线程问题。
    【解决方案5】:

    在将生产代码和开发代码都跟踪到版本控制中时,我遇到了同样的问题。我使用不同的 wsgi 脚本解决了这个问题,一个用于生产服务器,一个用于开发服务器。如上所述创建两个不同的设置文件here. 并在 wsgi 脚本中引用它们。比如下面是wsgi_production.py文件

    ...
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings.production")
    ...
    

    在 wsgi_development.py 文件中

    ...
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings.development")
    ...
    

    【讨论】:

      猜你喜欢
      • 2015-01-14
      • 2021-05-05
      • 1970-01-01
      • 1970-01-01
      • 2020-07-10
      • 2016-01-20
      • 1970-01-01
      • 2015-06-08
      相关资源
      最近更新 更多