django 3和django通道中的SynchronousOnlyOperation错误

我有一个django 2应用,我使用django通道进行套接字连接。

我只是将Django更新到版本3。现在,当我尝试建立套接字连接时,daphne显示此错误。我对Django 2没有任何问题。

[Failure instance: Traceback: <class 'django.core.exceptions.SynchronousOnlyOperation'>: You cannot call this from an async context - use a thread or sync_to_async.
/home/ubuntu/pl_env/lib/python3.6/site-packages/autobahn/websocket/protocol.py:2844:processHandshake
/home/ubuntu/pl_env/lib/python3.6/site-packages/txaio/tx.py:429:as_future
/home/ubuntu/pl_env/lib/python3.6/site-packages/twisted/internet/defer.py:151:maybeDeferred
/home/ubuntu/pl_env/lib/python3.6/site-packages/daphne/ws_protocol.py:83:onConnect
--- <exception caught here> ---
/home/ubuntu/pl_env/lib/python3.6/site-packages/twisted/internet/defer.py:151:maybeDeferred
/home/ubuntu/pl_env/lib/python3.6/site-packages/daphne/server.py:201:create_application
/home/ubuntu/pl_env/lib/python3.6/site-packages/channels/routing.py:54:__call__
/home/ubuntu/pl_env/lib/python3.6/site-packages/channels/security/websocket.py:37:__call__
/home/ubuntu/petroline_django/orders/token_auth.py:25:__call__
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/manager.py:82:manager_method
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/query.py:411:get
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/query.py:258:__len__
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/query.py:1261:_fetch_all
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/query.py:57:__iter__
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/sql/compiler.py:1142:execute_sql
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/utils/asyncio.py:24:inner

它说问题出在token_auth.py的第25行。此行是token = Token.objects.get(key=token_key)

这是我处理令牌认证的token_auth.py。

from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework.authtoken.models import Token


class TokenAuthMiddleware:
    """
    Token authorization middleware for Django Channels 2
    see:
    https://channels.readthedocs.io/en/latest/topics/authentication.html#custom-authentication
    """

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

    def __call__(self,scope):
        headers = dict(scope['headers'])
        if b'authorization' in headers:
            try:
                token_name,token_key = headers[b'authorization'].decode().split()
                if token_name == 'Token':
                    # Close old database connections to prevent usage of timed out connections
                    close_old_connections()
                    token = Token.objects.get(key=token_key)
                    scope['user'] = token.user
            except Token.DoesnotExist:
                scope['user'] = AnonymousUser()
        return self.inner(scope)

TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
hdswphr 回答:django 3和django通道中的SynchronousOnlyOperation错误

由于@ivissani的回答,我用了一些SessionMiddleware代码修复了TokenAuthMiddleware。

我为Django频道打开了一个issue,用于更新文档。

@database_sync_to_async
def get_user(token_key):
    try:
        return Token.objects.get(key=token_key).user
    except Token.DoesNotExist:
        return AnonymousUser()


class TokenAuthMiddleware:
    """
    Token authorization middleware for Django Channels 2
    see:
    https://channels.readthedocs.io/en/latest/topics/authentication.html#custom-authentication
    """

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

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


class TokenAuthMiddlewareInstance:
    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:
            token_name,token_key = headers[b'authorization'].decode().split()
            if token_name == 'Token':
                self.scope['user'] = await get_user(token_key)
        inner = self.inner(self.scope)
        return await inner(receive,send) 


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

使用@database_sync_to_async装饰器修复:

(请参见https://github.com/MathieuB1/KOREK-backend/commit/ff6a4b542cda583a1d5abbf200a5d57ef328cae0#diff-95e545fb374a9ed7e8af8c31087a3f29

import jwt,re
import traceback
from channels.auth import AuthMiddlewareStack
from channels.db import database_sync_to_async
from django.contrib.auth.models import AnonymousUser
from django.conf import LazySettings
from jwt import InvalidSignatureError,ExpiredSignatureError,DecodeError
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session

settings = LazySettings()

from django.db import close_old_connections

@database_sync_to_async
def close_connections():
    close_old_connections()

@database_sync_to_async
def get_user(user_jwt):
    try:
        return User.objects.get(id=user_jwt)
    except User.DoesNotExist:
        return AnonymousUser()


class TokenAuthMiddleware:
    """
    Token authorization middleware for Django Channels 2
    """
    def __init__(self,scope):
        # Close old database connections to prevent usage of timed out connections
        close_connections()

        # Login with JWT
        try:
            if scope['subprotocols'][0] != 'None':

                token = scope['subprotocols'][0]

                try:
                    user_jwt = jwt.decode(
                        token,settings.SECRET_KEY,)
                    scope['user'] = get_user(user_jwt['user_id'])
                    return self.inner(scope)

                except (InvalidSignatureError,KeyError,DecodeError):
                    traceback.print_exc()
                    pass
                except Exception as e:
                    traceback.print_exc()
            else:
                raise
,

请参阅this part of the documentation。在那里说明了,如果您尝试从异步上下文中使用ORM(似乎是这种情况),则Django 3将引发此类异常。

Django Channels documentation explains的解决方案是按如下方式使用sync_to_async

from channels.db import database_sync_to_async


class TokenAuthMiddleware:
    # more code here
    async def __call__(self,scope):
        # and some more code here
        token = await database_sync_to_async(Token.objects.get(key=token_key))()

尽管请记住,我一生中没有使用过它,所以它可能会失败。

请注意,在Django渠道文档中,它指出您需要使用单独的方法编写查询。因此,如果失败,请尝试这样做。

本文链接:https://www.f2er.com/2809479.html

大家都在问