added queue socket connect and validator consumer

This commit is contained in:
Alexander Karpov 2022-06-21 20:26:32 +03:00
parent d26dbee256
commit 8fcbeaf8d8
9 changed files with 116 additions and 50 deletions

View File

@ -1,29 +1,18 @@
"""
ASGI config for chess_backend project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
"""
import os import os
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import OriginValidator
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
import room.routing import room.routing
from room.middleware import HeaderAuthMiddleware
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chess_backend.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chess_backend.settings")
application = ProtocolTypeRouter( application = ProtocolTypeRouter(
{ {
"http": get_asgi_application(), "http": get_asgi_application(),
"websocket": OriginValidator( "websocket": HeaderAuthMiddleware(
AuthMiddlewareStack(URLRouter(room.routing.websocket_urlpatterns)), URLRouter(room.routing.websocket_urlpatterns)
["*"],
), ),
} }
) )

View File

@ -21,10 +21,12 @@ INSTALLED_APPS = [
"channels", "channels",
# Apps # Apps
"game", "game",
"room"
] ]
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",

View File

@ -1,6 +0,0 @@
from django.contrib import admin
# Register your models here.
from game.models import HeroImageSet
admin.site.register(HeroImageSet)

View File

@ -174,6 +174,9 @@ class Deck(models.Model):
# added for better DRF view # added for better DRF view
return self.get_heroes() return self.get_heroes()
def score(self):
return sum([x.attack + x.health + x.speed for x in self.get_heroes()])
class Meta: class Meta:
db_table = "deck" db_table = "deck"
verbose_name = "deck" verbose_name = "deck"

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,30 +1,110 @@
import json import json
from asgiref.sync import sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer from channels.generic.websocket import AsyncWebsocketConsumer
from game.models import Deck
from room.models import PlayerInQueue
class QueConsumer(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(args, kwargs)
self.room_group_name = None
class QueueConsumer(AsyncWebsocketConsumer):
async def connect(self): async def connect(self):
self.room_group_name = "que" self.room_group_name = "queue"
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept() await self.accept()
await self.check_origin()
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
async def disconnect(self, close_code): async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.room_group_name, self.channel_name) await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
# Receive message from WebSocket # Receive message from WebSocket
async def receive(self, text_data): async def receive(self, text_data):
print(text_data) data = None
# Send message to room group
await self.channel_layer.group_send( try:
self.room_group_name, data = json.loads(text_data)
{"type": "chat_message", "message": text_data}, except ValueError:
) await self.send(
text_data=json.dumps(
{"type": "ERROR", "message": "data is not JSON serializable"}
)
)
if data:
# TODO move to external function/class
if "type" not in data:
await self.send(
text_data=json.dumps(
{"type": "ERROR", "message": "incorrect data typing"}
)
)
else:
if data["type"] == "connect":
if "deck_id" not in data:
await self.send(
text_data=json.dumps(
{"type": "ERROR", "message": "deck id is not provided"}
)
)
else:
deck = None
# validate deck and check user originality
try:
deck_id = int(data["deck_id"])
deck = await self.check_user_deck(deck_id)
except ValueError:
await self.send(
text_data=json.dumps(
{"type": "ERROR", "message": "deck id is incorrect"}
)
)
if deck:
await self.queue_connector(deck)
else:
await self.send(
text_data=json.dumps(
{
"type": "ERROR",
"message": "such deck doesn't exist",
}
)
)
@sync_to_async
def check_user_deck(self, deck_id: int):
try:
deck = Deck.objects.get(id=deck_id)
if deck.player.id != self.scope["player"]:
return False
return deck
except Deck.DoesNotExist:
return False
@sync_to_async
def queue_connector(self, deck):
try:
queue = PlayerInQueue.objects.get(
player_id=self.scope["player"]
).score = deck.score()
except PlayerInQueue.DoesNotExist:
queue = PlayerInQueue.objects.create(
player_id=self.scope["player"], score=deck.score()
)
self.scope["queue"] = queue
async def chat_message(self, event):
pass
async def check_origin(self):
if not self.scope["player"]:
await self.send(
text_data=json.dumps(
{"type": "ERROR", "message": "token is incorrect or expired"}
)
)
await self.close()
class RoomConsumer(AsyncWebsocketConsumer): class RoomConsumer(AsyncWebsocketConsumer):

View File

@ -6,17 +6,21 @@ from game.services.jwt import read_jwt
@database_sync_to_async @database_sync_to_async
def get_player(jwt): def get_player(headers):
# WARNING headers type is bytes
if b"authorization" not in headers or not headers[b"authorization"]:
return False
jwt = headers[b"authorization"].decode()
payload = read_jwt(jwt) payload = read_jwt(jwt)
if not payload:
raise PermissionDenied if not payload or "id" not in payload:
try: return False
return Player.objects.get(id=payload)
except User.DoesNotExist: return payload["id"]
return AnonymousUser()
class QueryAuthMiddleware: class HeaderAuthMiddleware:
"""Custom middleware to read user auth token from string.""" """Custom middleware to read user auth token from string."""
def __init__(self, app): def __init__(self, app):
@ -24,9 +28,6 @@ class QueryAuthMiddleware:
self.app = app self.app = app
async def __call__(self, scope, receive, send): async def __call__(self, scope, receive, send):
# Look up user from query string (you should also do things like scope["player"] = await get_player(dict(scope["headers"]))
# checking if it is a valid user ID, or if scope["user"] is already
# populated).
scope["user"] = await get_user(int(scope["query_string"]))
return await self.app(scope, receive, send) return await self.app(scope, receive, send)

View File

@ -4,7 +4,7 @@ from django.db import models
from game.models import Player from game.models import Player
class PlayerInQue(models.Model): class PlayerInQueue(models.Model):
# TODO use redis for storing # TODO use redis for storing
player = models.ForeignKey(Player, unique=True, on_delete=models.CASCADE) player = models.ForeignKey(Player, unique=True, on_delete=models.CASCADE)
score = models.IntegerField() score = models.IntegerField()

View File

@ -3,6 +3,6 @@ from django.urls import path
from . import consumers from . import consumers
websocket_urlpatterns = [ websocket_urlpatterns = [
path("room", consumers.QueConsumer.as_asgi()), path("room/", consumers.QueueConsumer.as_asgi()),
path("room/<str:room_name>/", consumers.RoomConsumer.as_asgi()), path("room/<str:room_name>/", consumers.RoomConsumer.as_asgi()),
] ]