响应发送到客户端后在 Django 中执行代码

Posted

技术标签:

【中文标题】响应发送到客户端后在 Django 中执行代码【英文标题】:Execute code in Django after response has been sent to the client 【发布时间】:2011-05-17 20:14:59 【问题描述】:

在我的 Django 应用程序中,我想跟踪响应是否已成功发送到客户端。我很清楚,在 HTTP 这样的无连接协议中没有“无懈可击”的方式来确保客户端已收到(并显示)响应,因此这不是关键任务功能,但我仍然想在最晚可能的时间。响应不会是 html,因此来自客户端的任何回调(使用 javascript 或 IMG 标签等)都是不可能的。

我能找到的“最新”钩子是在中间件列表的第一个位置添加一个实现 process_response 的自定义中间件,但据我了解,这是在构建实际响应并将其发送到客户端之前执行的。响应成功发送后,Django 中是否有任何钩子/事件来执行代码?

【问题讨论】:

【参考方案1】:

我稍微修改了 Florian Ledermann 的想法......所以有人可以正常使用 httpresponse 函数,但允许他们定义一个函数并将其绑定到特定的 httpresponse。

old_response_close = HttpResponse.close
HttpResponse.func = None
def new_response_close(self):
    old_response_close(self)
    if self.func is not None:
        self.func()
HttpResponse.close = new_response_close

可以通过以下方式使用:

def myview():
    def myfunc():
        print("stuff to do")
    resp = HttpResponse(status=200)
    resp.func = myfunc
    return resp

我一直在寻找一种发送响应的方法,然后执行一些耗时的代码......但是如果我可以让后台(很可能是芹菜)任务运行,那么它将使这对我毫无用处.我将在 return 语句之前启动后台任务。它应该是异步的,所以在代码执行完成之前会返回响应。

---编辑---

我终于让 celery 与 aws sqs 一起工作了。我基本上发布了“如何”。看看我在这篇文章中的回答: Cannot start Celery Worker (Kombu.asynchronous.timer)

【讨论】:

【参考方案2】:

如果您经常需要这样做,一个有用的技巧是有一个特殊的响应类,例如:

class ResponseThen(Response):
    def __init__(self, data, then_callback, **kwargs):
        super().__init__(data, **kwargs)
        self.then_callback = then_callback

    def close(self):
        super().close()
        self.then_callback()

def some_view(request):
    # ...code to run before response is returned to client

    def do_after():
        # ...code to run *after* response is returned to client

    return ResponseThen(some_data, do_after, status=status.HTTP_200_OK)

...如果您想要一个快速/hacky 的“即发即弃”解决方案,而无需集成适当的任务队列或从您的应用中分离出单独的微服务,这将很有帮助。

【讨论】:

啊,这与我上面 9 岁且已接受的解决方案有何不同? 本质上不是:P ...但是当您在代码中需要大量“响应式”并且您需要每个人都这样做时,以这种方式编写它会更优雅完全不同的东西,但不必为它们创建单独的类-我想大多数尝试这样做的人最终都会像我一样。我更喜欢这种架构,我认为 ppl 应该看到更多的方法并选择他们喜欢的任何一种,我只是试图将 ppl 更多地推向函数式编程/更少类的代码风格:) 这太棒了!谢谢! 这似乎是一个不错的技巧,但在我的情况下它不起作用。我使用 UWSGI 来处理请求,我相信它可能会在请求关闭时结束该过程。我在回调中放置了日志记录和异常,无法获得任何反馈。 这里的 Response 类应该是什么?【参考方案3】:

我现在要使用的方法是使用 HttpResponse 的子类:

from django.template import loader
from django.http import HttpResponse

# use custom response class to override HttpResponse.close()
class LogSuccessResponse(HttpResponse):

    def close(self):
        super(LogSuccessResponse, self).close()
        # do whatever you want, this is the last codepoint in request handling
        if self.status_code == 200:
            print('HttpResponse successful: %s' % self.status_code)

# this would be the view definition
def logging_view(request):
    response = LogSuccessResponse('Hello World', mimetype='text/plain')
    return response

通过阅读 Django 代码,我非常确信 HttpResponse.close() 是将代码注入请求处理的最新点。我不确定与上面提到的方法相比,这种方法是否真的能更好地处理错误情况,所以我暂时将这个问题留待解决。

我更喜欢这种方法而不是 lazerscience 的回答中提到的其他方法的原因是它可以单独在视图中设置,不需要安装中间件。另一方面,使用 request_finished 信号不允许我访问响应对象。

【讨论】:

发送一个更简单的 json 标志有什么问题?为什么以上都是? 我不明白你所说的“json flag”是什么意思,我也看不到 JSON 以任何方式帮助我解决问题......请详细说明。 可以很好地处理成功的响应,但如果用户中断连接(通过关闭选项卡或点击“停止”按钮),则不会执行 close()。定义一个_del_方法,如果你需要在响应被处理之后做一些事情,即使连接被强行中断了。 我需要做同样的事情,但要记录 API 调用的指标。无法回调,之后我需要访问 httpresponse 或查看代码中的上下文。我这样做的原因是,保存指标有时会使 api 调用所花费的时间加倍。我唯一的方法是使用消息队列和任务服务器(rabbitmq/celery),但我仍然喜欢一种不需要在服务器上进行额外设置的纯 django 方式。【参考方案4】:

我想在谈论中间件时,您正在考虑中间件的 process_request 方法,但还有一个 process_response method 在返回 HttpResponse 对象时被调用。我想这将是您找到可以使用的钩子的最新时刻。

此外,还有一个 request_finished signal 被解雇。

【讨论】:

我知道 process_response,但由于预计此方法会返回响应,我相信此时无法发送响应。 request_finished 看起来很有希望,我现在正在研究。 我检查了request_finished,不幸的是,即使处理请求时出现异常,它也会被执行。所以这对我的目的也没有用,因为我只想跟踪成功的响应。 我猜你不会找到比 process_response 更晚的时刻,因为它是在 HttpRequest 完全构造之后调用的,然后它被处理到 Web 服务器...... 我不知道你到底想实现什么,但你也可以考虑在客户端有一些 js 发送回一些确认...... 如问题所述,响应不是 HTML,因此很遗憾,我无法使用任何客户端回调或 Javascript。

以上是关于响应发送到客户端后在 Django 中执行代码的主要内容,如果未能解决你的问题,请参考以下文章

如何将 JSON 响应发送到 Swift 3 中的另一个视图

如何使用 gen-server 将来自 rabbitmq 消费者的响应发送到 Erlang 中的生产者

前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。前端控制器既可以使用Filter实现

跨线程发送 TraceId

从 django rest 框架中的序列化程序发送自定义错误响应?

如何将数据从回调传递到Django中的另一个视图?