响应发送到客户端后在 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实现