Как выполнить код в Django после отправки ответа клиенту (на PythonAnywhere)?

Я ищу способ выполнить код в Django после того, как ответ был отправлен клиенту. Я знаю, что обычный способ - реализовать очередь задач (например, Celery). Однако служба PaaS, которую я использую (PythonAnywhere), не поддерживает очереди задач по состоянию на май 2019 года. Она также кажется слишком сложной для нескольких простых случаев использования. Я нашел следующее решение на SO: Выполнить код в Django после отправки ответа клиенту. Принятый ответ отлично работает при локальном запуске. Однако в рабочей среде PythonAnywhere он по-прежнему блокирует отправку ответа клиенту. Что вызывает это?

Вот моя реализация:

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

Я ожидаю, что ответ будет отправлен клиенту немедленно, но на самом деле загрузка страницы занимает целых 20 секунд. (На моем ноутбуке код работает отлично. Ответ отправляется немедленно, а операторы печати выполняются через 20 секунд.)


person pandichef    schedule 18.05.2019    source источник
comment
Согласно исходному коду класса HttpResponseBase , метод close() выдает сигнал request_finished. Пробовали ли вы слушать этот сигнал и действовать в соответствии с ним?   -  person nik_m    schedule 18.05.2019
comment
Нет. request_finished здесь не имеет значения. Насколько я понимаю, исходное решение SO использует close() для отправки ответа клиенту до выполнения функции обратного вызова. (На самом деле я хочу, чтобы request_finished выполнил сигнал после ответа клиенту. К сожалению, я проверил это, и это не так, как это реализовано в исходном коде.)   -  person pandichef    schedule 18.05.2019
comment
Это именно цель этого сигнала. Будет отправлено, когда Django завершит доставку HTTP-ответа клиенту. Я только что реализовал решение с сигналами и отлично работает. Ты пробовал это?   -  person nik_m    schedule 18.05.2019
comment
Попробуйте это: def my_request_finished_function(sender, environ, **kwargs): sleep(60). Запрос просто зависнет на 60 секунд. Напротив, я хочу, чтобы ответ был отправлен, а затем был запущен дополнительный код.   -  person pandichef    schedule 18.05.2019
comment
Вот ответ, который я получил от сотрудников PA для всех, кто интересуется: Я предполагаю, что метод закрытия работает по-разному в зависимости от того, работает ли он с отладочным веб-сервером, включенным в сервер выполнения Django, или работает за реальным веб-сервером, как мы используем на PythonAnywhere. Таким образом, на сервере отладки закрытие происходит вне запроса, тогда как на PythonAnywhere (или, возможно, на любом реальном веб-сервере) закрытие происходит как часть запроса, поэтому ваш 20-секундный сон задерживает возврат ответа. Я не думаю, что этот метод будет работать на PythonAnywhere.   -  person pandichef    schedule 20.05.2019