Django 3.0 + Channels + ASGI + TokenAuthMiddleware (注:这是一个提问标题,无需回答)

7
我升级到Django 3.0后,在使用Websockets + TokenAuthMiddleware时出现了以下错误:
SynchronousOnlyOperation
You cannot call this from an async context - use a thread or sync_to_async.
2个回答

18

问题在于您无法从异步上下文访问同步代码。这是适用于Django 3.0的TokenAuthMiddleware

# myproject.myapi.utils.py
from channels.auth import AuthMiddlewareStack
from channels.db import database_sync_to_async
from django.contrib.auth.models import AnonymousUser

from rest_framework.authtoken.models import Token


@database_sync_to_async
def get_user(headers):
    try:
        token_name, token_key = headers[b'authorization'].decode().split()
        if token_name == 'Token':
            token = Token.objects.get(key=token_key)
            return token.user
    except Token.DoesNotExist:
        return AnonymousUser()


class TokenAuthMiddleware:

    def __init__(self, inner):
        self.inner = inner

    def __call__(self, scope):
        return TokenAuthMiddlewareInstance(scope, self)


class TokenAuthMiddlewareInstance:
    """
    Yeah, this is black magic:
    https://github.com/django/channels/issues/1399
    """
    def __init__(self, scope, middleware):
        self.middleware = middleware
        self.scope = dict(scope)
        self.inner = self.middleware.inner

    async def __call__(self, receive, send):
        headers = dict(self.scope['headers'])
        if b'authorization' in headers:
            self.scope['user'] = await get_user(headers)
        inner = self.inner(self.scope)
        return await inner(receive, send)


TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))

就这样使用:

# myproject/routing.py
from myapi.utils import TokenAuthMiddlewareStack
from myapi.websockets import WSAPIConsumer

application = ProtocolTypeRouter({
    "websocket": TokenAuthMiddlewareStack(
        URLRouter([
            path("api/v1/ws", WSAPIConsumer),
        ]),
    ),

})
application = SentryAsgiMiddleware(application)

1
请将此标记为答案。运行得非常完美!如果我之前发现了这个,我就不会浪费几个小时了。 - Kannan Ravindran
1
它不再工作了: TypeError: __call__()缺少2个必需的位置参数:'receive'和'send' - tapion
这个功能运行得非常完美。我只是在使用daphne进行部署时遇到了一些问题,因为它需要django.settings。只需提醒自己可能需要在utils.py文件的顶部(在导入rest_framework、jwt等之前)添加os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<yourapp>.settings") / django.setup(),以便它们可以访问您的设置变量。 - Felipe de Abreu Prazeres

3

正如@tapion所,这个解决方案已经不再适用于channels 3.x

新的解决方案可以稍微进行调整:

class TokenAuthMiddleware:
    def __init__(self, inner):
        self.inner = inner

    async def __call__(self, scope, receive, send):
        headers = dict(scope['headers'])
        if b'authorization' in headers:
            scope['user'] = await get_user(headers)
        return await self.inner(scope, receive, send)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接