1

TL;DR

After an upgrade to Django 3.2, using the Channels deprecated method for ASGI handling works. Using the new recommended method doesn't work.


I'm upgrading from Django 3.0/Channels 2 to Django 3.2/Channels 3.

In channels 3.0 release notes, it is stated that instantiating a ProtocolRouter without the http key is deprecated, and that one should use django.core.asgi.get_asgi_application as value.

However, when I'm explicit on the http protocol using get_asgi_application, my middlewares don't work anymore: the app is complaining that I'm using sync middleware in an async context. This is the Django error popping up:

SynchronousOnlyOperation at /graphql/
You cannot call this from an async context - use a thread or sync_to_async.

...

The full traceback is here: https://pastebin.com/chRyW4VL

Here is the content of asgi.py

# my_project/asgi.py
import os

from channels.routing import ProtocolTypeRouter, URLRouter
import django
from django.core.asgi import get_asgi_application

assert os.environ.get('DJANGO_SETTINGS_MODULE') is not None, \
    'The `DJANGO_SETTINGS_MODULE` env var must be set.'

django.setup()

from core.urls import ws_routes

application = ProtocolTypeRouter({
    # If the following line is not commented out, I get the above error.
    # 'http': get_asgi_application(), 
    'websocket': URLRouter(ws_routes)
})

[EDIT] Here is a snippet of the middleware causing the issue:

# core/middleware.py

class MyMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        ...
        response = self.get_response(request)  # THIS LINE CAUSES THE ERROR
        ...
        return response

# Down the traceback, the culprit is a database call:
# core/**/another_module.py

def get_user_by_id(uid):
    return User.objects.get(id=uid) # BOOM!

"""
I know this db call is synchronous, but this is fine: 
I don't want it to be something else. I only want to 
use it in standard HTTP request/response cycle, with synchronous views, etc.
"""

I do not want to use async logic in my HTTP handlers/middleware, as I prefer the simplicity of synchronous patterns. I use Channels only to deal with websockets.

I'm looking for advice on how to follow Channels guidelines (ie, be explicit about the http protocol application).

In the meantime, I simply ignore the deprecation warning, and use the ASGI handler offered by Channels.

edthrn
  • 1,052
  • 12
  • 17
  • That's an interesting one. Are you using Daphne to serve both WebSockets and normal requests? Could you also show which middleware causes the issue? Quick look through the source code reveals that Django should adapt any middleware to the "correct" mode, so maybe issue lies in the middleware itself – Alexandr Tatarinov Jun 27 '21 at 22:58
  • Indeed, Daphne is used in both cases. I edited my original post to share the middleware causing the problem: it's a simple one that just store the current user in a contextual variable, available to the whole app. – edthrn Jun 28 '21 at 00:47
  • Could you include the traceback? If the line in question is `self.get_response`, it seems to have nothing to do with this exact middleware. Does the error disappear if you remove this middleware? – Alexandr Tatarinov Jun 28 '21 at 09:29
  • Please see my updated post with more info on the traceback. Yes, the error disappears when this middleware is removed. – edthrn Jun 28 '21 at 12:45
  • Thanks, unfortunately, I dont get it anyway. Since the error occurs on the `self.get_response` it means it's not related to this middleware, however the error dissapears if you remove it – Alexandr Tatarinov Jun 28 '21 at 15:14
  • One thing comes to mind is that the side effect of the middleware changes the behavior somewhere else down the line, however thats impossible to see without whole, complete traceback with all stack visible – Alexandr Tatarinov Jun 28 '21 at 15:16
  • No the only side effect of this middleware is to read from the database at some point. Please, see the full traceback here: https://pastebin.com/chRyW4VL – edthrn Jun 28 '21 at 15:32
  • Seems like the error happens here `File "/code/**/**/middleware.py", line 26, in request.user = SimpleLazyObject(lambda: get_user(request))`. Can you include that middelware? – Alexandr Tatarinov Jun 28 '21 at 16:25
  • This other middleware is a strict copy of django's builtin `AuthMiddleware`, but adapted to my own authentication method (based on JWT HTTP header). https://github.com/django/django/blob/main/django/contrib/auth/middleware.py#L15 The weird thing is Django's `AsgiHandler` complaining about it, while Channels `AsgiHandler` doesn't... – edthrn Jun 28 '21 at 16:33
  • try adding ` async_capable = False` to that middleware. idk what's happening to be fare) – Alexandr Tatarinov Jun 28 '21 at 17:04

1 Answers1

1

As discussed in this GitHub issue, the problem comes from Django and is described in this ticket.

Should be fixed as soon as this PR is merged and released in Django.

edthrn
  • 1,052
  • 12
  • 17