diff --git a/lesson44.md b/lesson44.md index c9d5f341d..75254c96e 100644 --- a/lesson44.md +++ b/lesson44.md @@ -1,4 +1,4 @@ -# Урок 44. Сокеты. Django channels. +# Урок 44. Сокеты. Django Channels. ![](https://www.imaginarycloud.com/blog/content/images/2016/03/gifmachine-2.gif) @@ -6,45 +6,45 @@ ### Реализация чата -Допустим вы хотите реализовать на своём сайте чат. Вы знаете протокол HTTP, который подразумевает систему запрос-ответ. +Допустим, вы хотите реализовать на своём сайте чат. Вы знаете протокол HTTP, который подразумевает систему запрос-ответ. -Но что делать если вам необходимо обновить информацию у клиента, хотя он её не запрашивал (Вам пишут сообщение, но вы не -знаете когда именно оно будет написано) +Но что делать, если необходимо обновить информацию у клиента, хотя он её не запрашивал? (Вам пишут сообщение, но вы +не знаете, когда именно оно будет написано.) Какие существуют варианты решения этой проблемы? #### Множество запросов -Мы можем делать большое кол-во запросов в надежде, что уже кто-то прислал нам сообщение +Мы можем делать большое количество запросов в надежде, что кто-то уже прислал нам сообщение ![](https://djangoalevel.s3.eu-central-1.amazonaws.com/lesson44/lots_requests.png) Чем плох такой подход? -Мы отправляем огромное кол-во запросов в "пустоту", расходуя ресурсы и выполняя не нужные запросы. +Мы отправляем огромное количество запросов в "пустоту", расходуя ресурсы и выполняя ненужные запросы. -#### Длинное соединение (long polling) +#### Длинное соединение (Long Polling) -Мы можем отдавать ответ только когда сообщение пришло. +Мы можем отдавать ответ только тогда, когда сообщение пришло. ![](https://djangoalevel.s3.eu-central-1.amazonaws.com/lesson44/long_polling.png) -Как это реализовать? Например, в коде можно использовать вечный цикл, и опрос какого либо хранилища, например `redis`. +Как это реализовать? Например, в коде можно использовать вечный цикл и опрос какого-либо хранилища, например, `redis`. Если данные появились, отдавать ответ. Чем плох такой подход? -"Пустые" HTTP запросы заменяются на "пустые" запросы к хранилищу данных, что ничем особо не лучше, мы всё еще тратим -большое кол-во ресурсов. Большинство серверов и браузеров имеют ограничение на время запроса, что тоже является +"Пустые" HTTP запросы заменяются на "пустые" запросы к хранилищу данных, что ничем особо не лучше. Мы всё еще тратим +большое количество ресурсов. Большинство серверов и браузеров имеют ограничение на время запроса, что тоже является проблемой для такого подхода. #### Сокеты -Сокет это специальный вид соединения поверх HTTP, для создания постоянного соединения. +*Сокет* - это специальный вид соединения поверх HTTP для создания постоянного соединения. Как это работает? -![](https://www.pubnub.com/wp-content/uploads/2013/09/WebSockets2.png) +[/не отображается/]: # (![](https://www.pubnub.com/wp-content/uploads/2013/09/WebSockets2.png)) Клиент отправляет запрос на соединение с сокетом сервера. @@ -54,10 +54,10 @@ Сервер рассылает это сообщение другим клиентам. -В любой момент обе стороны могут разорвать соединение если это необходимо. +В любой момент обе стороны могут разорвать соединение, если это необходимо. -Запросы для сокетов проходят по протоколу WebSocket и выполняются на адреса, которые начинаются с `ws://`, а -не `http://` +Запросы для сокетов проходят по протоколу `WebSocket` и выполняются на адреса, которые начинаются с `ws://`, а +не `http://`. ![](https://djangoalevel.s3.eu-central-1.amazonaws.com/lesson44/socket.png) @@ -67,18 +67,18 @@ - Чаты -- Приложения реального времени (Например отображение курса валют, стоимости криптовалют итд.) +- Приложения реального времени (Например, отображение курса валют, стоимости криптовалют и т. д.) -- IoT приложения (IoT - Internet of Things, интернет вещей, любые смарт предметы. Смарт-чайники, телевизоры, датчики - дыма, кофе машины итд.) +- IoT приложения (**IoT** - Internet of Things, интернет вещей, любые смарт предметы. Смарт-чайники, телевизоры, датчики + дыма, кофе машины и т.д.) - Онлайн игры -Но если необходимо, то можно применять где угодно. +Но если необходимо, то можно применять, где угодно. -## Django channels +## Django Channels -Естественно для Python существует готовый пакет для поддержки этого протокола с поддержкой Django +Естественно для Python существует готовый пакет для поддержки этого протокола с поддержкой Django. [Дока](https://channels.readthedocs.io/en/stable/) @@ -88,9 +88,9 @@ ### Туториал -Давайте напишем простой чат при помощи Django +Давайте напишем простой чат при помощи Django. -Создаём виртуальное окружение, устанавливаем django и channels, создаём джанго проект +Создаём виртуальное окружение, устанавливаем Django и channels, создаём Django проект: ```django-admin startproject chatsite``` @@ -107,15 +107,15 @@ chatsite/ wsgi.py ``` -Если вы используете версию django 2.2, то у вас не будет файла `asgi.py`, а он нам будет нужен. Не переживайте, мы его +Если вы используете версию Django 2.2, то у вас не будет файла `asgi.py`, а он нам будет нужен. Не переживайте, мы его создадим. -Файлы `wsgi.py` и `asgi.py` необходимы для запуска серверов, `wsgi` - синхронных, `asgi` - асинхронных. Веб сокет это +Файлы `wsgi.py` и `asgi.py` необходимы для запуска серверов, `wsgi` - синхронных, `asgi` - асинхронных. Веб-сокет - это асинхронная технология. -Создадим приложение для чата +Создадим приложение для чата: -```python3 manage.py startapp chat``` +```python manage.py startapp chat``` Получим примерно такую структуру файлов: @@ -131,7 +131,7 @@ chat/ views.py ``` -Для простоты, предлагаю удалить всё кроме `views.py` и `__init__.py` +Для простоты предлагаю удалить всё кроме `views.py` и `__init__.py`: Полученная структура: @@ -141,7 +141,7 @@ chat/ views.py ``` -Добавляем наше приложение в `INSTALLED_APPS` в `settings.py` +Добавляем наше приложение в `INSTALLED_APPS` в `settings.py`: ``` # chatsite/settings.py @@ -156,7 +156,7 @@ INSTALLED_APPS = [ ] ``` -Создадим папку `templates`, добавим её в `settigns.py` +Создадим папку `templates`, добавим её в `settigns.py`. Создадим файл `index.html` в папке `templates`: @@ -182,7 +182,7 @@ What chat room would you like to enter?
}; document.querySelector('#room-name-submit').onclick = function (e) { - var roomName = document.querySelector('#room-name-input').value; + let roomName = document.querySelector('#room-name-input').value; window.location.pathname = '/chat/' + roomName + '/'; }; @@ -190,20 +190,19 @@ What chat room would you like to enter?
``` -Что будет на этой странице? - -Поле для ввода и кнопка войти. Это будет возможность зайти в конкретный чат, по его названию. +Что будет на этой странице? Поле для ввода и кнопка войти. Это будет возможность зайти в конкретный чат, по его +названию. Что делает JS? При заходе на страницу сразу выделяет поле для ввода имени чата. -Если на инпуте нажимается энтер на клавиатуре, то имитируем нажатие на кнопку входа. +Если на инпуте нажимается `Enter` на клавиатуре, то имитируем нажатие на кнопку входа. При нажатии на кнопку входа, берем значение из инпута и переходим на страницу `/chat/<значение импута>/` - этой страницы пока не существует. -Создадим view для этой страницы. +Создадим `view` для этой страницы. ```python from django.views.generic import TemplateView @@ -213,7 +212,7 @@ class Index(TemplateView): template_name = 'index.html' ``` -Создадим файл с урлами внутри приложения: +Создадим файл с URLs внутри приложения: ``` chat/ @@ -233,9 +232,9 @@ urlpatterns = [ ] ``` -А в основных урлах +А в основных URLs: -``` +```python # chatsite/urls.py from django.conf.urls import include from django.urls import path @@ -246,14 +245,15 @@ urlpatterns = [ ] ``` -Теперь если мы запустим сервер, то увидим в консоли, что-то такое: +Теперь, если мы запустим сервер, то увидим в консоли что-то такое: ``` Performing system checks... System check identified no issues (0 silenced). -You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. +You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): +admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. October 21, 2020 - 18:49:39 Django version 3.1.2, using settings 'mysite.settings' @@ -261,15 +261,15 @@ Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. ``` -А если зайти на страницу http://127.0.0.1:8000/chat/ то будет вот так: +А если зайти на страницу http://127.0.0.1:8000/chat/, то будет вот так: ![](https://djangoalevel.s3.eu-central-1.amazonaws.com/lesson44/chat_enter.png) -Попытка перейти на любую страницу ни к чему не приведёт, страницы комнаты пока просто нет :) +Попытка перейти на любую страницу ни к чему не приведёт, страницы комнаты пока просто нет. :) ### Настройка Channels -Для настройки, необходимо изменить файл `asgi.py`, если его нет, то создать его. +Для настройки необходимо изменить файл `asgi.py`, если его нет, то создать его. ```python # chatsite/asgi.py @@ -286,8 +286,8 @@ application = ProtocolTypeRouter({ }) ``` -Что мы сделали? Мы сказали нашему приложению, что мы планируем разные протоколы, обрабатывать по разному, в данный -момент, мы указали только протокол `http`, а значит что фактически, пока что, ничего не изменится +Что мы сделали? Мы сказали нашему приложению, что мы планируем разные протоколы обрабатывать по-разному, в данный +момент мы указали только протокол `http`, а значит, что пока что ничего не изменится. Добавляем приложение в `INSTALLED_APPS`: @@ -304,7 +304,7 @@ INSTALLED_APPS = [ ] ``` -И добавляем настройку, что бы указать, что основной сервер был `asgi.py` +И добавляем настройку, чтобы указать, что основной сервер был `asgi.py`: ```python # mysite/settings.py @@ -319,7 +319,8 @@ Performing system checks... System check identified no issues (0 silenced). -You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. +You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): +admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. October 21, 2020 - 19:08:48 Django version 3.1.2, using settings 'mysite.settings' @@ -327,11 +328,11 @@ Starting ASGI/Channels version 3.0.0 development server at http://127.0.0.1:8000 Quit the server with CONTROL-C. ``` -Обратите внимание, предпоследняя строка, теперь сервер запущен с поддержкой веб сокетов. +Обратите внимание на предпоследнюю строку, теперь сервер запущен с поддержкой веб-сокетов. ### Создаём страницу с конкретным чатом -Создадим html, `room.html` +Создадим файл `room.html`: ```html @@ -392,22 +393,22 @@ Quit the server with CONTROL-C. ```{{ room_name|json_script:"room-name" }}``` -Фильтр json_script Добавит на страницу тег скрипт с данными из переменной, если открыть комнату с названием `test` то -отрендереная страница будет выглядеть так: +Фильтр `json_script` добавит на страницу тег `script` с данными из переменной, если открыть комнату с названием `test`, +то отрендеренная страница будет выглядеть так: ![](https://djangoalevel.s3.eu-central-1.amazonaws.com/lesson44/room_script.png) -Нужно для того, что бы считать переменную через JS. +Нужно для того, чтобы считать переменную через JS. Что происходит в JS? В первой строке мы считываем из переменной имя комнаты. -И создаём соединение с веб сокетом по адресу (`ws://127.0.0.1:8000/ws/chat/<имя чата>/`), мы создадим серверную часть -дальше. Обратите внимание, используется другой протокол не `http`. При создании такого объекта, запрос на соединение +И создаём соединение с веб-сокетом по адресу (`ws://127.0.0.1:8000/ws/chat/<имя чата>/`). Мы создадим серверную часть +дальше. Обратите внимание, используется другой протокол, не `http`. При создании такого объекта запрос на соединение отправляется автоматически. -Если по этому сокету приходит сообщение, то мы добавляем его к нашему месту для текста +Если по этому сокету приходит сообщение, то мы добавляем его к нашему месту для текста: ```js chatSocket.onmessage = function (e) { @@ -416,7 +417,7 @@ chatSocket.onmessage = function (e) { }; ``` -Если соединение было разорвано, отписать в консоль ошибку +Если соединение было разорвано, отписать в консоль ошибку: ```js chatSocket.onclose = function (e) { @@ -424,7 +425,7 @@ chatSocket.onclose = function (e) { }; ``` -В случае отправки сообщения, отправить его по сокету. +В случае отправки сообщения отправить его по сокету: ```js document.querySelector('#chat-message-submit').onclick = function (e) { @@ -437,7 +438,7 @@ document.querySelector('#chat-message-submit').onclick = function (e) { }; ``` -И создать view. +И создать `view`. ```python from django.views.generic import TemplateView @@ -461,13 +462,13 @@ urlpatterns = [ ] ``` -Запускаем сервер, заходим в любую комнату, пишем любое сообщение и видим ошибку. +Запускаем сервер, заходим в любую комнату, пишем сообщение и видим ошибку. ```WebSocket connection to 'ws://127.0.0.1:8000/ws/chat/lobby/' failed: Unexpected response code: 500``` -Мы не создали бекэнд для сокета. Давайте сделаем это. +Мы не создали бэкэнд для сокета. Давайте сделаем это. -### Бекэнд сокета +### Бэкэнд сокета Создадим новый файл `chat/consumers.py` @@ -500,19 +501,19 @@ class ChatConsumer(WebsocketConsumer): })) ``` -Что это такое? Это класс для работы с веб сокетом. +Что это такое? Это класс для работы с веб-сокетом. Методы: -- connect - Что делать при запросе на соединение. +- `connect()` - что делать при запросе на соединение. -- disconnect - Что делать при разрыве соединения. +- `disconnect()` - что делать при разрыве соединения. -- receive - Что делать при приходе сообщения. +- `receive()` - что делать при приходе сообщения. -- send - Отправить сообщение всем кто подключён (включая отправителя, вообще всем). +- `send()` - отправить сообщение всем, кто подключён (включая отправителя, вообще всем). -Создаём новый файл для урлов веб сокета `routing.py`. +Создаём новый файл для URLs веб-сокета `routing.py`. ``` chat/ @@ -533,9 +534,9 @@ websocket_urlpatterns = [ ] ``` -Обратите внимание к классу был применён метод `as_asgi`, это аналогия `as_view` для обычных классов. +Обратите внимание, к классу был применён метод `as_asgi()` - это аналогия `as_view()` для обычных классов. -Укажем эту переменную в нашем `asgi.py`: +Укажем эту переменную в нашем файле `asgi.py`: ```python # chatsite/asgi.py @@ -560,7 +561,7 @@ application = ProtocolTypeRouter({ Обратите внимание, мы добавили новый протокол для обработки. -Для того, что бы сокет работал необходимы сессии, а для этого необходимо провести миграции. +Для того чтобы сокет работал, необходимы сессии, а для этого необходимо провести миграции. ```python manage.py migrate``` @@ -568,32 +569,32 @@ application = ProtocolTypeRouter({ **В данный момент работать будет только один чат!!!** -Мы не добавили возможность создавать разные сокеты, для разных страниц. Для этого необходимо разделить данные по слоям. +Мы не добавили возможность создавать разные сокеты для разных страниц. Для этого необходимо разделить данные по слоям. ### Подключаем channels -Для того что бы использовать различные не пересекающиеся чаты, мы будем использовать `group`, `group` это +Для того чтобы использовать различные не пересекающиеся чаты, мы будем использовать `group`. `group` - это набор `channel`. -Для использования необходимо какое-либо внешнее хранилище. Мы будем использовать Redis. +Для использования необходимо какое-либо внешнее хранилище. Мы будем использовать `Redis`. -Для этого необходимо установить еще один внешний модуль, для взаимодействия между нашими слоями и редисом. +Для этого необходимо установить еще один внешний модуль для взаимодействия между нашими слоями и `Redis`. ```pip install channels_redis``` ### Для пользователей Windows -На windows обычный редис не будет работать с последними версиями django-channels. +На Windows обычный `Redis` не будет работать с последними версиями `django-channels`. Необходимо установить [это](https://www.memurai.com/get-memurai) и запустить в консоли после: ```memurai``` -Это аналог Redis, который будет работать +Это аналог `Redis`, который будет работать. ### Обновление settings -Необходимо обновить настройки и указать, что мы будем использовать redis: +Необходимо обновить настройки и указать, что мы будем использовать `Redis`: ```python # chatsite/settings.py @@ -609,7 +610,7 @@ CHANNEL_LAYERS = { } ``` -Для проверки работы редиса необходимо открыть `shell`: +Для проверки работы `Redis` необходимо открыть `shell`: ```python manage.py shell``` @@ -624,12 +625,12 @@ async_to_sync(channel_layer.receive)('test_channel') {'type': 'hello'} ``` -Напоминаю, изначально вебсокеты это асинхронная технология. Для использования её синхронно, мы будем использовать -встроенный метод `async_to_sync`. +Напоминаю, изначально веб-сокеты - это асинхронная технология. Для использования её синхронно мы будем использовать +встроенный метод `async_to_sync()`. В тесте мы отправили сообщение и получили его. -Теперь можно обновить `consumers.py`: +Теперь можно обновить файл `consumers.py`: ```python # chat/consumers.py @@ -684,23 +685,23 @@ class ChatConsumer(WebsocketConsumer): Методы: -`connect` - добавили создание группы, исходя из названия чата, и так же вызвали метод `accept` +`connect()` - добавили создание группы по названию чата и вызвали метод `accept()`. -`disconnect` - удаляем группу при разрыве соединения +`disconnect()` - удаляем группу при разрыве соединения. -`receive` - При получении сообщения мы выполняем для всей группы, метода `chat_message` могли назвать абсолютно как +`receive()` - при получении сообщения мы выполняем для всей группы, метод `chat_message()` могли назвать абсолютно как угодно. -`chat_message` - отправка сообщения +`chat_message()` - отправка сообщения. -Можем проверять. Открываем одинаковые названия чата в разных браузерах и пишем по сообщению с каждого +Можем проверять. Открываем одинаковые названия чата в разных браузерах и пишем по сообщению с каждого браузера. ## Запускаем всё асинхронно -Допустим мы хотим отправить другу большой файл, но мы хотим писать сообщения пока файл загружается. В случае -использования синхронного подхода, это невозможно, при асинхронном, это будет работать. +Допустим, мы хотим отправить другу большой файл, но мы хотим писать сообщения, пока файл загружается. В случае +использования синхронного подхода это невозможно, но при асинхронном это будет работать. -Перепишем `consumers.py` +Перепишем фай`consumers.py`: ```python # chat/consumers.py @@ -753,17 +754,18 @@ class ChatConsumer(AsyncWebsocketConsumer): ``` Что мы изменили? Мы наследовались не от `WebsocketConsumer`, а от `AsyncWebsocketConsumer`, заменили все функции с -обычных на асинхронные, и вызов функций с обычного на асинхронные. +обычных на асинхронные и вызов функций с обычного на асинхронные. Всё, ваш чат полностью асинхронен. ### Тестирование -Для тестирования веб сокетов используются специфический ацептанс тесты. Разберите самостоятельно [тут](https://channels.readthedocs.io/en/stable/tutorial/part_4.html). +Для тестирования веб-сокетов используются специфические acceptance тесты. +Разберите самостоятельно [тут](https://channels.readthedocs.io/en/stable/tutorial/part_4.html). ### Практика 1. Повторите туториал из этого занятия. -2. Давайте разбирать задания на диплом! \ No newline at end of file +2. Давайте разбирать задания на диплом!