Django приложение, использующее Graphql и пакет Channels, бросает исключение внутри приложения: объект 'NoneType' не имеет атрибута 'replace'
У меня есть приложение Django, которое использует graphene для реализации GraphQL, у меня все настроено и работает, но теперь у меня есть ошибка в консоли, которая внезапно появилась, и хотя она ничего не нарушает, по крайней мере, насколько я могу судить, она продолжает появляться в консоли, и я хотел бы ее исправить.
Я совсем новичок в Django, поэтому не могу понять, откуда это происходит. Похоже, что это происходит из пакета channels.
Это ошибка в полном объеме, которая возникает сразу после запуска сервера и затем снова после выполнения каждого запроса.
Django version 3.2.3, using settings 'shuddhi.settings'
Starting ASGI/Channels version 3.0.3 development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
WebSocket HANDSHAKING /graphql/ [172.28.0.1:60078]
Exception inside application: 'NoneType' object has no attribute 'replace'
Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/channels/staticfiles.py", line 44, in __call__
return await self.application(scope, receive, send)
File "/usr/local/lib/python3.8/site-packages/channels/routing.py", line 71, in __call__
return await application(scope, receive, send)
File "/usr/local/lib/python3.8/site-packages/channels/security/websocket.py", line 35, in __call__
if self.valid_origin(parsed_origin):
File "/usr/local/lib/python3.8/site-packages/channels/security/websocket.py", line 54, in valid_origin
return self.validate_origin(parsed_origin)
File "/usr/local/lib/python3.8/site-packages/channels/security/websocket.py", line 73, in validate_origin
return any(
File "/usr/local/lib/python3.8/site-packages/channels/security/websocket.py", line 74, in <genexpr>
pattern == "*" or self.match_allowed_origin(parsed_origin, pattern)
File "/usr/local/lib/python3.8/site-packages/channels/security/websocket.py", line 98, in match_allowed_origin
parsed_pattern = urlparse(pattern.lower(), scheme=None)
File "/usr/local/lib/python3.8/urllib/parse.py", line 376, in urlparse
splitresult = urlsplit(url, scheme, allow_fragments)
File "/usr/local/lib/python3.8/urllib/parse.py", line 433, in urlsplit
scheme = _remove_unsafe_bytes_from_url(scheme)
File "/usr/local/lib/python3.8/urllib/parse.py", line 422, in _remove_unsafe_bytes_from_url
url = url.replace(b, "")
AttributeError: 'NoneType' object has no attribute 'replace'
Это мой файл Settings.py:-
urls.py в папке main_project:-
from django.contrib import admin
from django.urls import include, path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('', include('app_name.urls')),
path('admin/', admin.site.urls),
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]
if settings.DEBUG:
urlpatterns += (
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
)
urls.py в приложении:-
from . import views
from django.urls import path
from .views import *
urlpatterns = [
path('', views.index, name='index'),
]
Файл Router.py, в котором я указал вещи, необходимые для подписки:-
from base64 import decode
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
from django.urls import path
from .schema import MyGraphqlWsConsumer
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth import get_user_model
from channels.db import database_sync_to_async
from channels.middleware import BaseMiddleware
import jwt
from .settings import SECRET_KEY
# from user.models import Token
@database_sync_to_async
def get_user(token_key):
try:
decodedPayload = jwt.decode(
token_key, key=SECRET_KEY, algorithms=['HS256'])
user_id = decodedPayload.get('sub')
User = get_user_model()
user = User.objects.get(pk=user_id)
return user
except Exception as e:
return AnonymousUser()
# This is to enable authentication via websockets
# Source - https://stackoverflow.com/a/65437244/7981162
class TokenAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
query = dict((x.split("=")
for x in scope["query_string"].decode().split("&")))
token_key = query.get("token")
print('token from subscription request =>', token_key)
scope["user"] = await get_user(token_key)
print('user subscribing ', scope["user"])
scope["session"] = scope["user"] if scope["user"] else None
return await super().__call__(scope, receive, send)
application = ProtocolTypeRouter(
{
"http": get_asgi_application(),
"websocket": AllowedHostsOriginValidator(TokenAuthMiddleware(
URLRouter(
[path("graphql/", MyGraphqlWsConsumer.as_asgi())]
)
)),
}
)
Не уверен, что здесь нужно что-то еще, но это все, что я могу придумать. Было бы здорово узнать, как устранить неполадки и избавиться от этого исключения.
Обновление:-
Пока я продолжаю устранять неполадки, я обнаружил, что начал видеть это после того, как переместил переменную ALLOWED_HOSTS в файл env, и если я устанавливаю ALLOWED_HOSTS = ['*'] в файле settings.py, ошибка исчезает. И она появляется только тогда, когда происходит подписка из пользовательского интерфейса. Я определенно хочу, чтобы ALLOWED_HOSTS получала значение из переменной окружения, потому что оно будет разным для prod и dev и его нужно будет устанавливать из переменных окружения
Сейчас файл .env имеет следующее - DJANGO_ALLOWED_HOSTS=0.0.0.0,localhost
TlDR - Обновление пакета каналов до версии 3.0.4.
Детали: -
Я сузил проблему до того, что она вызвана тем, что ALLOWED_HOSTS имеет что-нибудь кроме ['*']. Таким образом, если у меня есть определенный список разрешенных доменов, например ['localhost', '0.0.0.0']
, это приводит к исключению и фактически препятствует работе подписки.
Виной тому является использование 'AllowedHostsOriginValidator' пакета channels, который почему-то ломается при использовании Python 3.8. Проблема документирована здесь
В версию 3.0.4 пакета каналов добавлено исправление. Просто обновите пакет, и он будет работать нормально.