【问题标题】:SynchronousOnlyOperation Error in with django 3 and django channelsdjango 3 和 django 频道中的 SynchronousOnlyOperation 错误
【发布时间】:2020-01-07 16:13:48
【问题描述】:

我有一个 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))

【问题讨论】:

    标签: django django-channels daphne


    【解决方案1】:

    使用@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, inner):
            self.inner = inner
    
        def __call__(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, ExpiredSignatureError, DecodeError):
                        traceback.print_exc()
                        pass
                    except Exception as e:
                        traceback.print_exc()
                else:
                    raise
    

    【讨论】:

      【解决方案2】:

      感谢@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))
      

      【讨论】:

        【解决方案3】:

        请参阅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 频道文档中,它说您需要在单独的方法中编写查询。因此,如果失败,请尝试这样做。

        【讨论】:

        • 试试看,然后告诉我会发生什么
        • 我认为“await”不能用于同步功能。它必须在异步中使用。异步 __call__(self, scope): ....
        • 据我在django channels code 中看到的,您应该可以在您的中间件中执行async def __call__(self, scope):...。你试过了吗?
        • async def __call__ 不直接在 SessionMiddleware 中。它在 SessionMiddlewareInstance 中。但我使用 SessionMiddleware 的代码来重写我自己的自定义身份验证中间件。谢谢@ivissani
        猜你喜欢
        • 2017-05-23
        • 2019-02-02
        • 2018-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-18
        • 1970-01-01
        • 2020-04-27
        相关资源
        最近更新 更多