-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathminiapp.py
157 lines (120 loc) · 4.81 KB
/
miniapp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import asyncio
from pathlib import Path
from typing import Union
import aiohttp_jinja2
import jinja2
from aiohttp import web
from aiohttp_sse import EventSourceResponse, sse_response
from aiohttplimiter import Limiter, default_keyfunc
from services.gamecontroller import (GameController, GameEvent, GameEventType,
GameWordStatus)
limiter = Limiter(keyfunc=default_keyfunc)
@limiter.limit("1/second")
async def miniapp_handler(request: web.Request) -> web.Response:
return await aiohttp_jinja2.render_template_async(
"paint.html", request, context=None
)
@limiter.limit("1/second")
async def update_canvas_handler(request: web.Request) -> web.Response:
if request.content_type != "multipart/form-data":
return web.Response(status=401, text="Incorrect content type")
params = await request.post()
if "_auth" not in params or "image" not in params or "gameId" not in params:
return web.Response(status=401, text="Some keys are missing")
controller: GameController = request.app["controller"]
return (
web.Response(text="OK")
if await controller.update_state(
init_data=params["_auth"], game_id=params["gameId"], image=params["image"]
)
else web.Response(text="error", status=401)
)
@limiter.limit("1/second")
async def get_word_handler(request: web.Request) -> web.Response:
params = request.rel_url.query
if "_auth" not in params or "gameId" not in params:
return web.Response(status=401, text="Some keys are missing")
controller: GameController = request.app["controller"]
word_result = await controller.get_word(
init_data=params["_auth"], game_id=params["gameId"]
)
match word_result.status:
case GameWordStatus.Ok:
return web.Response(text=word_result.word)
case _:
return web.Response(text=word_result.status, status=401)
class EventSourceResponsePatched(EventSourceResponse):
"""Patched EventSourceResponse:
`_ping()` - Cancellation on ConnectionResetError
`is_connected()` - Check connection is prepared and ping task is not done
"""
async def _ping(self):
"""https://github.com/aio-libs/aiohttp-sse/pull/370"""
# periodically send ping to the browser. Any message that
# starts with ":" colon ignored by a browser and could be used
# as ping message.
while True:
await asyncio.sleep(self._ping_interval)
try:
await self.write(": ping{0}{0}".format(self._sep).encode("utf-8"))
except ConnectionResetError:
self._ping_task.cancel()
def is_connected(self) -> bool:
"""Check connection is prepared and ping task is not done.
https://github.com/aio-libs/aiohttp-sse/pull/401
"""
return self.prepared and not self._ping_task.done()
@limiter.limit("1/second")
async def game_events_handler(request: web.Request) -> web.Response:
params = request.rel_url.query
if "_auth" not in params or "gameId" not in params:
return web.Response(status=204)
controller: GameController = request.app["controller"]
_auth = params["_auth"]
game_id = params["gameId"]
queue = await controller.sub(init_data=_auth, game_id=game_id)
resp: EventSourceResponsePatched
async with sse_response(request, response_cls=EventSourceResponsePatched) as resp:
resp.ping_interval = 5
async def stop_event_on_disconnect() -> None:
await resp.wait()
await queue.put(GameEventType.Disconnect)
asyncio.create_task(stop_event_on_disconnect())
try:
while resp.is_connected():
event: Union[GameEvent, GameEventType]
event = await queue.get()
queue.task_done()
if event == GameEventType.Disconnect:
break
await resp.send(data=event.data, event=event.type)
if event.type == GameEventType.Error:
await resp.send("reset", event="reset")
break
except ConnectionResetError:
pass
finally:
await controller.unsub(
game_id=game_id,
session_queue=queue
)
return resp
app = web.Application()
aiohttp_jinja2.setup(
app,
enable_async=True,
loader=jinja2.FileSystemLoader(
Path(__file__).parent.resolve() / "templates"),
)
app.add_routes(
[
web.get("", miniapp_handler),
web.post("/update", update_canvas_handler),
web.get("/word", get_word_handler),
web.get("/events", game_events_handler),
web.static(
"/static", Path(__file__).parent.resolve() / "static", name="static"
),
]
)
app["static_root_url"] = "/web/app/static"