I am trying to use Django Channels to implement long-polling for a React frontend web app and Django REST backend. I believe that much of what I have is working to some degree, but some thing(s) must be incorrectly configured or coded to produce unexpected results.
UPDATE: It seems that the problem lies in the axios.get(...) request. When swapping out that request with a fetch(...), that fetch receives a response every single time, whereas the Axios call gets a response every other time on average. Unsure of the solution to this at this time.
In short, the problem that I am receiving is that when the Django Channels Consumer sends back a response, it does not immediately go back to the frontend (per console.log(...)s in the code); rather, it seems the Consumer must send another response and then the previous or both responses appear in the frontend.
I am trying to implement using AsyncHttpConsumer because Websockets are not possible in our use-case.
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
Then, in the Views.py file during an update of an Analysis, I call the below line to communicate to the Consumer group and call the previously defined function, analysis_update.
async_to_sync(layers.group_send)(f"analysis_{idAnalysis}", {"type": "analysis.update", "id": idAnalysis})
The frontend ReactJS code loops by basically doing the below and checking the response.
await axios.get(ApiUrl.analysisSubscribe(analysisId), {
timeout: 60000,
})