【问题标题】:Create object on startup and share across requests在启动时创建对象并跨请求共享
【发布时间】:2021-03-04 08:09:34
【问题描述】:

我想将我的 Django 应用程序用作基于会话的在线服务的中继,并在所有用户之间共享此会话。为此,我配置了一个python-requests Session 对象。

我想在 Django 启动时初始化这个 Session 并让它永远活着。我的想法是通过允许特定请求的视图访问会话对象,让对我的 Django 应用程序的所有请求共享会话对象。

Flask 中进行设置(用于实验目的)相当简单:

from flask import Flask, render_template, request, session
from requests import Session
app = Flask(__name__)

session = Session()
session.post()        # Setup Session by logging in

@app.route("/")
def use_session():
    reply = session.get()   # Get resource from web service
    return jsonify(reply)

这里session会在启动时创建,use_session()可以访问。

不过,我很难在 Django 中进行同样的设置。创建会话的首选位置是哪里?

【问题讨论】:

  • 你在 Django 方面尝试过什么?您可以将相同的逻辑放在 Django 的 views.py 中(虽然这不是保持会话的好方法)
  • 什么是“基于会话的在线服务”?您需要包含逻辑以确定它是否已将您注销,以便您可以重新进行身份验证,并确保在身份验证过程发生时没有其他请求。

标签: python django python-requests


【解决方案1】:

相当于你在 Django 中的 Flask 代码是将相同的逻辑放在 views.py 文件中:

# yourapp/views.py
from django.http import HttpResponse
from requests import Session

session = Session()
session.post('https://httpbin.org/post')  # Setup Session by logging in


def use_session(request):
    reply = session.get('https://example.com')  # Get resource from web service
    return HttpResponse(reply.content, status=reply.status_code)

# yourproject/urls.py
from django.urls import path
from yourapp.views import use_session


urlpatterns = [
    path('', use_session)
]

该对象将在您启动服务器时创建。

这种方法的一个问题是,在实际部署中,您通常会运行应用程序的多个副本(不管是 Flask 还是 Django 或任何其他框架)以避免线程阻塞,在相同的服务器或有多个服务器,每个副本最终都会有自己的会话,这可能工作正常,但可能会出现问题。

在这种情况下,您可以在多个进程之间共享一个 Python 对象1,方法是使用 pickle 模块对其进行序列化并将字节数据存储在数据库中。存储数据的好地方是 Redis 或 Memcached,但也可以使用 Django 的 ORM:

# yourapp/models.py
from django.db import models


class Session(models.Model):
    pickled = models.BinaryField()

# yourapp/views.py
import pickle

from django.http import HttpResponse
from requests import Session

from . import models


def use_session(request):
    # Load the pickled session from the database
    session_db_obj = models.Session.objects.first()

    if session_db_obj:
        # Un-pickle the session
        session = pickle.loads(session_db_obj.pickled)
    else:
        # Create a new session
        session = Session()
        session.post('https://httpbin.org/post')  # Setup Session by logging in

        # Create the database object, pickle the session and save it
        session_db_obj = models.Session()
        session_db_obj.pickled = pickle.dumps(session)
        session_db_obj.save()

    reply = session.get('https://example.com')  # Get resource from web service
    return HttpResponse(reply.content, status=reply.status_code)

1:不是任何物体都可以可靠地腌制和解封,小心!

【讨论】:

  • 感谢您的回答并指出多个实例的风险。在我的应用程序中,这实际上很好。我也想知道 Workers 是否可以成为一个解决方案,但我对他们的原则还不够熟悉。
  • 那些 API 请求将阻塞服务器线程,当您的网络感觉有点慢时获得足够的请求,您将看到 502 错误。解决此问题的一种方法是确实将这些呼叫转移给工作人员。您需要以某种方式将 API 响应推送回用户,这并非易事。最近版本的 Django 的另一个选择是使用异步视图和异步版本的请求 - grequests,但这也可能会变得复杂,因为现在你需要了解 asyncio 并且 Django 还不完全支持异步
【解决方案2】:

设置它的最佳位置可能是settings.py,因为它是在应用程序初始化之前调用的,您可以从那里轻松导入代码。

话虽如此,您可能希望研究连接池或在会话顶部有一个包装器,以便在失败时重新创建它。网络并不像看起来那么可靠,如果您打算让会话长时间运行,则会增加会话在某个时间点停止的可能性。

【讨论】:

  • requests.Session 已经在做连接池(通过urllib3)并且会重新创建一个 TCP 连接就好了
  • 感谢您的回答并指出需要一些重新连接功能。我认为这可以通过一段时间后重新连接来实现。关于将逻辑放在 settings.py 中。这不是一个奇怪的选择吗?我从未在该文件中看到过逻辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-17
  • 1970-01-01
  • 2017-04-08
  • 2017-03-08
  • 2020-10-11
  • 1970-01-01
相关资源
最近更新 更多