SynchronousOnlyOperation Ошибка в каналах django 3 и django - PullRequest
1 голос
/ 07 января 2020

У меня было приложение django 2, и я использовал django каналы для сокетного соединения.

Я просто обновил django до версии 3. И теперь Дафни показывает эту ошибку, когда я пытаюсь сделать сокетное соединение. у меня не было никаких проблем с 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. Пи, которая обрабатывает аутентификацию токена.

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))

Ответы [ 3 ]

2 голосов
/ 07 января 2020

См. эту часть документации . Там объясняется, что Django 3 вызовет такое исключение, если вы попытаетесь использовать ORM из контекста asyn c (что, похоже, имеет место).

Как Django В документации по каналам объясняется, что решение будет заключаться в использовании 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 говорится, что вам нужно написать запрос в отдельном методе. Поэтому, если это не удается, попробуйте сделать это.

1 голос
/ 08 января 2020

Благодаря ответам @ivissani я исправил свое ПО TokenAuthMiddle с некоторыми кодами SessionMiddleware .

Я открыл вопрос для django каналов об обновлении документов.

@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))
0 голосов
/ 06 марта 2020

Исправлено с помощью @ database_sync_to_asyn c decorator:

(см. 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...