{"id":305266,"date":"2020-06-12T15:00:31","date_gmt":"2020-06-12T15:00:31","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=305266"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=305266","title":{"rendered":"\u0413\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0430 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 Starlette"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/506056\/\">\u0414\u043e\u0431\u0440\u043e\u0433\u043e \u0434\u043d\u044f!<\/p>\n<p>  \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0421\u043e\u0431\u043e\u043b\u0435\u0432 \u0410\u043d\u0434\u0440\u0435\u0439 \u0438 \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u0441 \u0432\u0430\u043c\u0438 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 Starlette, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0435 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.<\/p>\n<h3>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h3>\n<p>  <a href=\"https:\/\/www.starlette.io\/\" rel=\"nofollow\">Starlette<\/a> \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043e\u043b\u043e\u0434\u043e\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a, \u0438 \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u00ab\u043f\u043b\u044e\u0448\u043a\u0438\u00bb \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e. <a href=\"https:\/\/habr.com\/ru\/post\/502814\/\">\u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043b \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c JWT \u0441\u0435\u0441\u0441\u0438\u0438 \u0438 \u00abDjango\u043f\u043e\u0434\u043e\u0431\u043d\u0443\u044e\u00bb \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u0430\u043a \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.<\/p>\n<p>  <a name=\"habracut\"><\/a>  <\/p>\n<h3>\u0414\u043b\u044f \u0447\u0435\u0433\u043e \u043d\u0443\u0436\u043d\u0430 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0430 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 c\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439?<\/h3>\n<p>  \u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c \u044f \u0438 \u043c\u043e\u0439 \u0434\u0440\u0443\u0433 (\u043d\u0430\u0437\u043e\u0432\u0435\u043c \u0435\u0433\u043e UnnamedUser) \u0440\u0435\u0448\u0438\u043b\u0438 \u043f\u043e\u043e\u0431\u0449\u0430\u0442\u044c\u0441\u044f \u0432 \u0447\u0430\u0442\u0435.<\/p>\n<p>  \u041a\u043e\u0433\u0434\u0430 \u044f \u0437\u0430\u0445\u043e\u0436\u0443 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0441 ID=1, <b>\u043c\u043e\u0439 \u0431\u0440\u0430\u0443\u0437\u0435\u0440<\/b> \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0432\u043e\u0435 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c (\u0434\u043b\u044f \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f \u043d\u0430\u0437\u043e\u0432\u0435\u043c \u0435\u0433\u043e \u00ab\u043a\u0430\u043d\u0430\u043b 1\u00bb*). <\/p>\n<p>  \u041a\u043e\u0433\u0434\u0430 UnnamedUser \u0437\u0430\u0445\u043e\u0434\u0438\u0442 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0441 ID=1, <b>\u0435\u0433\u043e \u0431\u0440\u0430\u0443\u0437\u0435\u0440<\/b> \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0432\u0442\u043e\u0440\u043e\u0435 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c (\u0434\u043b\u044f \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f \u043d\u0430\u0437\u043e\u0432\u0435\u043c \u0435\u0433\u043e \u00ab\u043a\u0430\u043d\u0430\u043b 2\u00bb*). <\/p>\n<p>  <i>* \u0434\u0430\u043b\u0435\u0435 \u043f\u043e \u0442\u0435\u043a\u0441\u0442\u0443 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043d\u0430\u043b\u043e\u043c<\/i><\/p>\n<blockquote><p>\u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u00ab\u043a\u0430\u043d\u0430\u043b 1\u00bb \u0438 \u00ab\u043a\u0430\u043d\u0430\u043b 2\u00bb \u044d\u0442\u043e \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043a\u043b\u0430\u0441\u0441\u0430 <a href=\"https:\/\/github.com\/encode\/starlette\/blob\/master\/starlette\/endpoints.py\" rel=\"nofollow\">WebSocketEndpoint<\/a>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0447\u0430\u0442\u0435 \u043c\u044b \u0432\u0438\u0434\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0432\u043e\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0430 \u043d\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0434\u0440\u0443\u0433\u0438\u0445 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 (\u043a\u0430\u043a \u043e\u0436\u0438\u0434\u0430\u043b\u043e\u0441\u044c).<\/p><\/blockquote>\n<p>  \u0427\u0442\u043e\u0431\u044b \u0440\u0435\u0448\u0438\u0442\u044c \u044d\u0442\u0443 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u043d\u0430\u0448\u0438 \u043a\u0430\u043d\u0430\u043b\u044b \u0432 \u0433\u0440\u0443\u043f\u043f\u0443 (\u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443 <b>room_1<\/b>) \u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u043c\u0430\u0441\u0441\u043e\u0432\u044b\u0435 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438 \u043f\u0440\u0438 \u043d\u0430\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0438 \u043a\u0430\u043a\u043e\u0433\u043e \u043b\u0438\u0431\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f (\u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443 \u043a\u0442\u043e-\u0442\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0432 \u0447\u0430\u0442).<\/p>\n<h3>\u0413\u0434\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0433\u0440\u0443\u043f\u043f\u044b?<\/h3>\n<p>  \u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0433\u0440\u0443\u043f\u043f \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u0441\u043b\u043e\u0432\u0430\u0440\u0435\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0437\u043e\u0432\u0435\u043c <b>CHANNEL_GROUPS<\/b> \u0438 \u043e\u0431\u044a\u044f\u0432\u0438\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e:  <\/p>\n<pre><code class=\"python\">import time import uuid from simple_print.functions import sprint_f from starlette.endpoints import WebSocketEndpoint  CHANNEL_GROUPS = {} <\/code><\/pre>\n<h3>\u0423\u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u0441\u044f \u043e\u0442 WebSocketEndpoint<\/h3>\n<p>  \u0427\u0442\u043e\u0431\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u0430\u043d\u0430\u043b\u0430 \u0441\u0432\u044f\u0437\u044c \u0441 <b>CHANNEL_GROUPS<\/b> \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0431\u0430\u0437\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 <a href=\"https:\/\/github.com\/encode\/starlette\/blob\/master\/starlette\/endpoints.py\" rel=\"nofollow\">WebSocketEndpoint<\/a>.<\/p>\n<p>  \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 <b>Channel<\/b>:  <\/p>\n<pre><code class=\"python\">class Channel:     def __init__(self, websocket, expires, encoding):         self.channel_uuid = str(uuid.uuid1()) # uid         self.websocket = websocket         self.expires = expires # \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438         self.encoding = encoding # \u0442\u0438\u043f \u043a\u0430\u043d\u0430\u043b\u0430 (json, text, bytes)         self.created = time.time() # \u0432\u0440\u0435\u043c\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f       async def _send(self, payload): # \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0432 \u0433\u0440\u0443\u043f\u043f\u0443         websocket = self.websocket         if self.encoding == &quot;json&quot;:             await websocket.send_json(payload)         elif self.encoding == &quot;text&quot;:             await websocket.send_text(payload)         elif self.encoding == &quot;bytes&quot;:             await websocket.send_bytes(payload)         else:             await websocket.send(payload)         self.created = time.time()      def _is_expired(self):         return self.expires + int(self.created) &lt; time.time()      def __repr__(self):         return f&quot;{self.channel_uuid}&quot; <\/code><\/pre>\n<p>  \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 uuid (\u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430) \u043a\u0430\u043d\u0430\u043b\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0432 Python \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 <a href=\"https:\/\/docs.python.org\/3\/library\/uuid.html\" rel=\"nofollow\">UUID objects<\/a><\/p>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 <b>ChannelEndpoint<\/b> \u043e\u0442 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u043d\u0430\u0448\u0438\u0445 endpoints:  <\/p>\n<pre><code class=\"python\">class ChannelEndpoint(WebSocketEndpoint):     def __init__(self, *args, **kwargs):         super().__init__(*args, **kwargs)         self.expires = 60 * 60 * 24           self.encoding = &quot;json&quot;         self.groups = CHANNEL_GROUPS # \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0432\u044f\u0437\u044c \u0441 CHANNEL_GROUPS       async def on_connect(self, websocket, **kwargs):         await super().on_connect(websocket, **kwargs)         self.channel = Channel(websocket=websocket, expires=self.expires, encoding=self.encoding)      async def on_disconnect(self, websocket, close_code):         await super().on_disconnect(websocket, close_code)         await self._remove(self.channel)      async def _remove(self, channel):         for group in self.groups:             if channel in self.groups[group]:                 del self.groups[group][channel]      async def _validate_name(self, name):         if name.isidentifier():             return True         raise TypeError(&quot;Group names must be valid python identifier only alphanumerics and underscores are accepted&quot;)      async def _clean_expired(self):         for group in self.groups:             for channel in self.groups.get(group, {}):                 if channel._is_expired():                     del self.groups[group][channel]      async def get_or_create(self, group): # \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0433\u0440\u0443\u043f\u043f\u0443         assert await self._validate_name(group), &quot;Invalid group name&quot;         self.groups.setdefault(group, {})         self.groups[group][self.channel] = &quot;&quot;         self.group = group      async def group_send(self, payload): # \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0433\u0440\u0443\u043f\u043f\u0443         await self._clean_expired()         for channel in self.groups.get(self.group, {}):             await channel._send(payload) <\/code><\/pre>\n<p>  \u041f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u044b \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 WebSocketEndpoint <i>on_connect<\/i>, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044f \u043a \u043d\u0435\u043c\u0443 \u043e\u0431\u044a\u0435\u043a\u0442 <b>Channel<\/b>.<\/p>\n<p>  \u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e\u043a\u0438\u0434\u0430\u0435\u0442 \u043a\u0430\u043d\u0430\u043b, \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 <b>_remove<\/b>, \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043a\u0430\u043d\u0430\u043b\u0430 \u0438\u0437 <b>CHANNEL_GROUPS<\/b>.<\/p>\n<p>  \u0412 endpoints \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043e\u0442 <b>ChannelEndpoint<\/b> \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0435 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b:<\/p>\n<ul>\n<li><b>get_or_create(self, group)<\/b> \u2014 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0433\u0440\u0443\u043f\u043f\u044b <\/li>\n<li><b>group_send(self, payload)<\/b> \u2014 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043a\u0430\u043d\u0430\u043b\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0445\u043e\u0434\u044f\u0442 \u0432 \u0434\u0430\u043d\u043d\u0443\u044e \u0433\u0440\u0443\u043f\u043f\u0443.<\/li>\n<\/ul>\n<h3>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438<\/h3>\n<p>  <\/p>\n<pre><code class=\"python\">routes = [     Route(&quot;\/chat\/&quot;, endpoint=ChatView)     Route(&quot;\/chat\/ws&quot;, endpoint=ChatChannel) ]  html = &quot;&quot;&quot; &lt;!DOCTYPE html&gt; &lt;html&gt;     &lt;head&gt;         &lt;title&gt;ws&lt;\/title&gt;     &lt;\/head&gt;     &lt;body&gt;         &lt;h1&gt;ChannelEndpoint&lt;\/h1&gt;         &lt;form action=&quot;&quot; onsubmit=&quot;sendMessage(event)&quot;&gt;             &lt;label&gt;group_id: &lt;\/label&gt;&lt;input type=&quot;text&quot; id=&quot;groupId&quot; autocomplete=&quot;off&quot; value=&quot;2&quot;&gt;&lt;br\/&gt;             &lt;label&gt;username: &lt;\/label&gt;&lt;input type=&quot;text&quot; id=&quot;username&quot; autocomplete=&quot;off&quot; value=&quot;test_user2&quot;&gt;&lt;br\/&gt;                    &lt;label&gt;message: &lt;\/label&gt;&lt;input type=&quot;text&quot; id=&quot;messageText&quot; autocomplete=&quot;off&quot; value=&quot;test_message2&quot;&gt;&lt;br\/&gt;             &lt;button&gt;Send&lt;\/button&gt;         &lt;\/form&gt;         &lt;ul id='messages'&gt;         &lt;\/ul&gt;         &lt;script&gt;             var ws = new WebSocket(&quot;ws:\/\/localhost\/chat\/chat\/ws&quot;);             ws.onmessage = function(event) {                 console.log('Message received %s', event.data)                 var messages = document.getElementById('messages');                 var message = document.createElement('li');                 var data = JSON.parse(event.data);                 message.innerHTML = `&lt;strong&gt;${data.username} :&lt;\/strong&gt; ${data.message}`;                 messages.appendChild(message);             };             function sendMessage(event) {                 var username = document.getElementById(&quot;username&quot;);                 var group_id = document.getElementById(&quot;groupId&quot;);                 var input = document.getElementById(&quot;messageText&quot;);                 var data = {                     &quot;group_id&quot;: group_id.value,                      &quot;username&quot;: username.value,                     &quot;message&quot;: input.value,                 };                 console.log('Message send %s', data)                 ws.send(JSON.stringify(data));                 event.preventDefault();             }         &lt;\/script&gt;     &lt;\/body&gt; &lt;\/html&gt; &quot;&quot;&quot;  class ChatView(HTTPEndpoint):     async def get(self, request):         return HTMLResponse(html)  class ChatChannel(ChannelEndpoint): # \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u0441\u044f \u043e\u0442 ChannelEndpoint     async def on_receive(self, websocket, data):         group_id = data[&quot;group_id&quot;]         message = data[&quot;message&quot;]         username = data[&quot;username&quot;]         if message.strip():             group = f&quot;group_{group_id}&quot;              await self.get_or_create(group)  # \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0433\u0440\u0443\u043f\u043f\u0443 (\u0438 \u0432\u0441\u0435 \u0435\u0435 \u043a\u0430\u043d\u0430\u043b\u044b) \u0438\u0437 \u0441\u043b\u043e\u0432\u0430\u0440\u044f CHANNEL_GROUPS              payload = {                 &quot;username&quot;: username,                 &quot;message&quot;: message,             }              await self.group_send(payload) # \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u043c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430\u043c \u0433\u0440\u0443\u043f\u043f\u044b <\/code><\/pre>\n<h3>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/h3>\n<p>  \u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u0438 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0433\u0440\u0443\u043f\u043f\u044b \u0438\u0437 \u043b\u044e\u0431\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u0430 \u043a\u043e\u0434\u0430, \u00ab\u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u00bb \u0433\u0440\u0443\u043f\u043f, \u0430 \u0442\u0430\u043a\u0436\u0435 \u00ab\u043e\u0447\u0438\u0441\u0442\u043a\u0430\u00bb.<\/p>\n<p>  \u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0433\u0440\u0443\u043f\u043f\u0443 \u0438\u0437 \u043b\u044e\u0431\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u0430 \u043a\u043e\u0434\u0430:  <\/p>\n<pre><code class=\"python\">from channel_box import group_send await group_send('my_chat_1', {&quot;username&quot;: &quot;New User&quot;, &quot;message&quot;: &quot;Hello world&quot;}) <\/code><\/pre>\n<p>  \u00ab\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u00bb \u0433\u0440\u0443\u043f\u043f:  <\/p>\n<pre><code class=\"python\">from channel_box import groups_show groups_show() <\/code><\/pre>\n<p>  \u041e\u0447\u0438\u0441\u0442\u043a\u0430:  <\/p>\n<pre><code class=\"python\">from channel_box import groups_flush groups_flush() <\/code><\/pre>\n<h3>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430<\/h3>\n<p>  \u041c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 pip:  <\/p>\n<pre><code class=\"python\">pip install channel-box <\/code><\/pre>\n<p>  \u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0435\u0448\u0435\u043d\u0438\u044f:<br \/>  <a href=\"https:\/\/github.com\/Sobolev5\/channel-box\" rel=\"nofollow\">github.com\/Sobolev5\/channel-box<\/a><\/p>\n<p>  \u041f\u0440\u0438\u043c\u0435\u0440 \u0440\u0430\u0431\u043e\u0442\u044b:<br \/>  <a href=\"http:\/\/backend.starlette-vue.site\/chat\/chat1\/\" rel=\"nofollow\">backend.starlette-vue.site\/chat\/chat1<\/a><br \/>  <a href=\"http:\/\/backend.starlette-vue.site\/chat\/chat2\/\" rel=\"nofollow\">backend.starlette-vue.site\/chat\/chat2<\/a><\/p>\n<p>  \u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0440\u0430\u0431\u043e\u0442\u044b:<br \/>  <a href=\"https:\/\/github.com\/Sobolev5\/starlette-vue-backend\/tree\/master\/apps\/chat\" rel=\"nofollow\">github.com\/Sobolev5\/starlette-vue-backend\/tree\/master\/apps\/chat<\/a><\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/506056\/\"> https:\/\/habr.com\/ru\/post\/506056\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/506056\/\">\u0414\u043e\u0431\u0440\u043e\u0433\u043e \u0434\u043d\u044f!<\/p>\n<p>  \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0421\u043e\u0431\u043e\u043b\u0435\u0432 \u0410\u043d\u0434\u0440\u0435\u0439 \u0438 \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u0441 \u0432\u0430\u043c\u0438 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 Starlette, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0435 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.<\/p>\n<h3>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h3>\n<p>  <a href=\"https:\/\/www.starlette.io\/\" rel=\"nofollow\">Starlette<\/a> \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043e\u043b\u043e\u0434\u043e\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a, \u0438 \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u00ab\u043f\u043b\u044e\u0448\u043a\u0438\u00bb \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e. <a href=\"https:\/\/habr.com\/ru\/post\/502814\/\">\u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043b \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c JWT \u0441\u0435\u0441\u0441\u0438\u0438 \u0438 \u00abDjango\u043f\u043e\u0434\u043e\u0431\u043d\u0443\u044e\u00bb \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u0430\u043a \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-305266","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/305266","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=305266"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/305266\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=305266"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=305266"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=305266"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}