41

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?

Florian Ledermann
  • 3,187
  • 1
  • 29
  • 33

5 Answers5

41

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.

Florian Ledermann
  • 3,187
  • 1
  • 29
  • 33
  • what's wrong with sending a simpler json flag? why all of the above? – Srikar Appalaraju Nov 30 '10 at 13:36
  • Neither do I get what you mean by "json flag" nor can I see in any way JSON helping me with my problem... please elaborate. – Florian Ledermann Nov 30 '10 at 13:39
  • 6
    Works well to handle a successful response, but close() is not executed if the user interrupts the connection ( by closing the tab or hitting the "stop" button ). Define a \__del\__ method if you need to do something after the response has been handled, even if the connection has been forcefully interrupted. – vincent Jan 13 '11 at 10:10
  • 1
    I need to do the same thing but with logging metrics of an API call. There is no way to callback and I need access to the httpresponse or view context in the code afterwards. My reason for this is that saving metrics can sometimes double the time an api call takes. My only way to do this is using a message queue and task server (rabbitmq/celery) but I'd still love a pure-django way of doing this that doesn't require extra setup on the server. – rennat Jul 22 '11 at 23:58
  • Hey any idea how I could use that in conjunction with DRF? Can I somehow overwrite that globally? – Snake_py Jul 25 '22 at 19:28
33

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.

NeuronQ
  • 7,527
  • 9
  • 42
  • 60
  • Ahm, how is this different from my 9-year-old & accepted solution above? – Florian Ledermann Mar 13 '19 at 15:07
  • 14
    essentially it's not :P ...but writing it this way is just more elegant when you need lots of "response then"-s in your code and you need *each of them to do something completely different* but without having to create separate classes for them - I imagine most people trying to do this would end up in this case, as I did. I like the architecture of this more and I think ppl should see more ways of doing it and pick whichever they like, I'm just trying to push ppl more towards a functional-programming/less-classes code style :) – NeuronQ Mar 13 '19 at 16:04
  • This is a hella thing! Thank you! –  Oct 16 '19 at 12:20
  • 1
    This seems like a nice trick however it doesn't work in my case. I use UWSGI to handle requests and I believe it may end the process when the request is closed. I placed logging and exceptions in the callback and can't get any feedback. – Rémi Héneault Jan 22 '21 at 12:49
  • What is the Response class here supposed to be? – Marcos Pereira Dec 14 '21 at 17:21
3

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.

Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148
  • I was aware of process_response, but as this method is expected to return a response I believe the response could not have been sent out at this point. request_finished looks promising, I'm looking into that now. – Florian Ledermann Nov 30 '10 at 12:15
  • I checked request_finished and unfortunately it is executed even if there is an exception when the request is handled. So this is useless for my purposes too, because I only want to track successful responses. – Florian Ledermann Nov 30 '10 at 12:30
  • 1
    I guess you won't find a later moment than process_response, because that is called after the HttpRequest is fully constucted and and after that it is handled over to the web server... – Bernhard Vallant Nov 30 '10 at 12:38
  • 1
    I don't know what you are exactly trying to achieve, but you could also take in consideration having some js on the client side that is sending back some confirmation... – Bernhard Vallant Nov 30 '10 at 12:41
  • 1
    As stated in the question, the response is not HTML so I cannot use any client-side callbacks or Javascript, unfortunately. – Florian Ledermann Nov 30 '10 at 12:56
  • I am currently looking into overriding HttpResponse.close() which, judging by the source of the django handlers, would be the latest point that code is executed in the response chain. Not sure if it makes a real difference to the above mentioned methods though, although it has the advantage of being able to be returned by the view alone, not needing middleware classes to be installed or handlers to be registered. – Florian Ledermann Nov 30 '10 at 12:58
2

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)
F814 Del
  • 21
  • 1
1

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)

Shmack
  • 1,933
  • 2
  • 18
  • 23