Ale*_*cer 7 django django-channels daphne
我有一个 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
Run Code Online (Sandbox Code Playgroud)
它说问题出在 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))
Run Code Online (Sandbox Code Playgroud)
感谢@ivissani 的回答,我用一些SessionMiddleware代码修复了我的 TokenAuthMiddleware 。
我已经打开了一个问题,有关更新文档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))
Run Code Online (Sandbox Code Playgroud)
小智 5
通过使用 @database_sync_to_async 装饰器修复:
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
Run Code Online (Sandbox Code Playgroud)
请参阅文档的这一部分。其中解释说,如果您尝试在异步上下文中使用 ORM(似乎是这种情况),Django 3 将引发此类异常。
正如Django Channels 文档所解释的,解决方案将使用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))()
Run Code Online (Sandbox Code Playgroud)
尽管请记住,我一生中没有使用过这个,所以它可能会失败。
请注意,在 Django 通道文档中,它说您需要在单独的方法中编写查询。因此,如果这失败了,请尝试这样做。