0

I'm looking for a way to execute code in Django after the response has been sent to the client. I know the usual way is to implement a task queue (e.g., Celery). However, the PaaS service I'm using (PythonAnywhere) doesn't support task queues as of May 2019. It also seems overly complex for a few simple use cases. I found the following solution on SO: Execute code in Django after response has been sent to the client. The accepted answer works great when run locally. However, in production on PythonAnywhere, it still blocks the response from being sent to the client. What is causing that?

Here's my implementation:

from time import sleep
from datetime import datetime
from django.http import HttpResponse

class HttpResponseThen(HttpResponse): 
    """
    WARNING: THIS IS STILL BLOCKING THE PAGE LOAD ON PA
    Implements HttpResponse with a callback i.e.,
    The callback function runs after the http response.
    """
    def __init__(self, data, then_callback=lambda: 'hello world', **kwargs):
        super().__init__(data, **kwargs)
        self.then_callback = then_callback

    def close(self):
        super().close()
        return_value = self.then_callback()
        print(f"Callback return value: {return_value}")

def my_callback_function():
    sleep(20)
    print('This should print 20 seconds AFTER the page loads.')
    print('On PA, the page actually takes 20 seconds to load')

def test_view(request):
    return HttpResponseThen("Timestamp: "+str(datetime.now()), 
        then_callback=my_callback_function)  # This is still blocking on PA

I'm expecting the response to be sent to the client immediately, but it actually takes a full 20 seconds for the page to load. (On my laptop, the code works great. The response is sent immediately and the print statements execute 20 seconds later.)

pandichef
  • 706
  • 9
  • 11
  • 1
    According to the [source code of HttpResponseBase class](https://github.com/django/django/blob/master/django/http/response.py#L251), the `close()` method emits the signal [`request_finished`](https://docs.djangoproject.com/en/dev/ref/signals/#request-finished). Have you tried to listen on that signal and act on it? – nik_m May 18 '19 at 17:04
  • 1
    No. request_finished isn't relevant here. My understanding is that the original SO solution uses `close()` to fire off the response to the client before the callback function executes. (I actually wish request_finished executed the signal _after_ the response to the client. Unfortunately, I checked it and that's not how it's implemented in the source code.) – pandichef May 18 '19 at 18:24
  • 1
    That's exactly the purpose of this signal. To be sent when Django finishes delivering an HTTP response to the client. I have just implemented a solution with signals and works brilliant. Have you tried it? – nik_m May 18 '19 at 18:52
  • Try this: `def my_request_finished_function(sender, environ, **kwargs): sleep(60)`. The request will just hang for 60 seconds. In contrast, I want the response to be sent and then have some additional code run. – pandichef May 18 '19 at 19:30
  • Here's the response I got from PA staff for anyone interested: "My guess is that the close method works differently depending on whether it's running with the debugging webserver that is included in Django's runserver or whether it's running behind a real webserver like we use on PythonAnywhere. So in the debug server, the close happens outside of the request whereas on PythonAnywhere (or probably any real web server), the close happens as part of the request so your 20 second sleep is holding up the return of the response. I don't think that's a method that's going to work on PythonAnywhere." – pandichef May 19 '19 at 22:14

0 Answers0