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

发布于 2024-10-04 18:27:27 字数 299 浏览 4 评论 0原文

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

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

In my Django application I want to keep track of whether a response has been sent to the client successfully. I am well aware that there is no "watertight" way in a connectionless protocol like HTTP to ensure the client has received (and displayed) a response, so this will not be mission-critical functionality, but still I want to do this at the latest possible time. The response will not be HTML so any callbacks from the client (using Javascript or IMG tags etc.) are not possible.

The "latest" hook I can find would be adding a custom middleware implementing process_response at the first position of the middleware list, but to my understanding this is executed before the actual response is constructed and sent to the client. Are there any hooks/events in Django to execute code after the response has been sent successfully?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

梦开始←不甜 2024-10-11 18:27:27

我现在要使用的方法使用 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 信号不允许我访问响应对象。

The method I am going for at the moment uses a subclass of 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

By reading the Django code I am very much convinced that HttpResponse.close() is the latest point to inject code into the request handling. I am not sure if there really are error cases that are handled better by this method compared to the ones mentioned above, so I am leaving the question open for now.

The reasons I prefer this approach to the others mentioned in lazerscience's answer are that it can be set up in the view alone and does not require middleware to be installed. Using the request_finished signal, on the other hand, wouldn't allow me to access the response object.

高跟鞋的旋律 2024-10-11 18:27:27

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

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)

...如果您想要一个快速/黑客的“即发即忘”解决方案,而无需费心集成适当的任务队列或拆分任务队列,则可以提供帮助将微服务与您的应用程序分开。

If you need to do this a lot, a useful trick is to have a special response class like:

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)

...helps if you want a quick/hacky "fire and forget" solution without bothering to integrate a proper task queue or split off a separate microservice from your app.

深爱不及久伴 2024-10-11 18:27:27

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

此外,还有一个 request_finished信号被发射。

I suppose when talking about middleware you are thinking about the middleware's process_request method, but there's also a process_response method that is called when the HttpResponse object is returned. I guess that will be the latest moment where you can find a hook that you can use.

Furthermore there's also a request_finished signal being fired.

老子叫无熙 2024-10-11 18:27:27

我发现了一个肮脏的技巧,通过访问 HttpResponse 中的受保护成员来做到这一点。

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

    response = HttpResponse()
    response._resource_closers.append(do_after)
    return response

它适用于 Django 3.0.6 ,检查 HttpResponse 原型中的“close”函数。

def close(self):
    for closer in self._resource_closers:
        try:
            closer()
        except Exception:
            pass
    # Free resources that were still referenced.
    self._resource_closers.clear()
    self.closed = True
    signals.request_finished.send(sender=self._handler_class)

I found a filthy trick to do this by accessing a protected member in HttpResponse.

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

    response = HttpResponse()
    response._resource_closers.append(do_after)
    return response

It works in Django 3.0.6 , check the "close" function in the prototype of HttpResponse.

def close(self):
    for closer in self._resource_closers:
        try:
            closer()
        except Exception:
            pass
    # Free resources that were still referenced.
    self._resource_closers.clear()
    self.closed = True
    signals.request_finished.send(sender=self._handler_class)
风吹雨成花 2024-10-11 18:27:27

我稍微修改了 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

我正在寻找一种发送响应的方法,然后执行一些耗时的代码...但是如果我可以运行一个后台(很可能是芹菜)任务,那么它将呈现这个对我来说没用。我将在返回语句之前启动后台任务。它应该是异步的,因此响应将在代码执行完成之前返回。

---编辑---

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

I modified Florian Ledermann's idea a little bit... So someone can just use the httpresponse function normally, but allows for them to define a function and bind it to that specific 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

It can be used via:

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

I was looking for a way to send a response, then execute some time consuming code after... but if I can get a background (most likely a celery) task to run, then it will have rendered this useless to me. I will just kick off the background task before the return statement. It should be asynchronous, so the response will be returned before the code is finished executing.

---EDIT---

I finally got celery to work with aws sqs. I basically posted a "how to". Check out my answer on this post:
Cannot start Celery Worker (Kombu.asynchronous.timer)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文