Как правильно отправить ответ на React Frontend из Django Channels?

Я пытаюсь использовать Django Channels для реализации long-polling для React frontend веб-приложения и Django REST backend. Я считаю, что многое из того, что у меня есть, работает в той или иной степени, но некоторые вещи должны быть неправильно настроены или закодированы для получения неожиданных результатов.

Вкратце, проблема, которую я получаю, заключается в том, что когда Django Channels Consumer отправляет ответ, он не сразу возвращается во фронтенд (согласно console.log(...) в коде); скорее, кажется, что Consumer должен отправить другой ответ, а затем предыдущий или оба ответа появляются во фронтенде.

Я пытаюсь реализовать с помощью AsyncHttpConsumer, потому что Websockets невозможны в нашем сценарии использования.

Asgi.py

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
django_asgi_app = get_asgi_application()

application = ProtocolTypeRouter(
    {
        "http": URLRouter(
            longpoll_urlpatterns + [re_path(r"", django_asgi_app)]
        ),
    }
)

Routing.py

longpoll_urlpatterns = [
    # Tried using re_path but did not work
    path("analysisSubscription/<int:analysisId>/", consumers.AnalysisConsumer.as_asgi()),
]

Consumers.py

class AnalysisConsumer(AsyncHttpConsumer):
    async def handle(self, body):
        print("In Handle")
        print(self.scope)
        self.analysisId = self.scope["url_route"]["kwargs"]["analysisId"]
        self.analysis_group_name = f"analysis_{self.analysisId}"

        # Register with the appropriate channel
        await self.channel_layer.group_add(self.analysis_group_name, self.channel_name)
        
        await self.send_headers(headers=[
            (b"Content-type", b"application/json"),
            (b"Access-Control-Allow-Origin", b"*"),
            (b"Access-Control-Allow-Headers", b"Origin, X-Requested-With, Content-Type, Accept, Authorization"),])
                
        #The server won't send the headers unless we start sending the body
        await self.send_body(b"", more_body=True)

        print("Registered consumer for ID: ", self.analysisId, " and group: ", self.analysis_group_name)

       # await self.channel_layer.group_send(self.analysis_group_name, {"type": "analysis.update", "text": self.analysisId})

    async def http_request(self, message):
        print("In Request")
        print(message)
        if "body" in message:
            self.body.append(message["body"])
        if not message.get("more_body"):
            try:
                await self.handle(b"".join(self.body))
            except:
                print("Stopping")
                # If something goes wrong, disconnect
                # In the parent this ALWAYS disconnects and thus long-polling breaks
                await self.disconnect()
                raise StopConsumer()

    async def disconnect(self):
        print("Disconnecting!")
        await self.channel_layer.group_discard(self.analysis_group_name, self.channel_name)
    
    async def analysis_update(self, event):
        print(event)
        print("Inside Analysis Consumer")
        analysisId = event['id']
        analysisData = ""

        try:
            analysisData = await self.getAnalysis(analysisId)
            analysisData = json.dumps(analysisData)
        except Exception as ex:
            print("Failed to retrieve Analysis object: {ex}")
            return

        print("Retrieved analysis:\n\t", analysisData)
        await self.send_body(analysisData.encode('utf-8'))
        print("Sent the response")

        await asyncio.sleep(1)

        await self.http_disconnect(None)

    @database_sync_to_async
    def getAnalysis(self, id):
        return AnalysisSerializer(Analysis.objects.filter(id=id)[0]).data

Затем в файле Views.py во время обновления анализа я вызываю следующую строку для связи с группой потребителей и вызова ранее определенной функции, analysis_update.

async_to_sync(layers.group_send)(f"analysis_{idAnalysis}", {"type": "analysis.update", "id": idAnalysis})

Код фронтенда ReactJS зацикливается, в основном выполняя следующие действия и проверяя response.

await axios.get(ApiUrl.analysisSubscribe(analysisId), {
            timeout: 60000,
        })

TL;DR; GET запросы посылают запрос метода OPTIONS перед GET для получения информации.

После этого вопроса я пошел по альтернативному пути использования fetch против Axios для запросов. При отладке другой проблемы с задачей мы столкнулись с тем, что запрос fetch на самом деле отправлял запрос метода OPTIONS до того, как был отправлен запрос GET.

Выяснилось, может ли это происходить и с Axios, но, похоже, что да (и это было подтверждено в ходе тестирования). Дополнительную информацию можно найти в разделе Аксиос меняет метод запроса на 'OPTIONS' вместо 'GET'.

Вернуться на верх