Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SocketIO Возвращается (Снова) #110

Merged
merged 8 commits into from
Jul 16, 2022
Merged

Conversation

niqzart
Copy link
Member

@niqzart niqzart commented Jul 16, 2022

Фронтендерам, ищущим инфу про ack-и, это тут, хотя можно и весь пост почитать

SocketIO снова снова с бэком

Нововведения

  • можно использовать те же модели, что добавлены в Передел Разработки Базы Данных и Моделей #94
  • нужно создавать EventController (похоже на Namespace для _rst-файлов)
  • сами события нужно объявлять внутри класса, наследующего EventSpace
  • клиентские события создаются через добавление функциям декораторов
  • сервереные события создаются через конструктор соответствующего класса

Фичи

  • автоматическая документация (локально доступна только в JSON-формате: http://localhost:5000/asyncapi.json)
  • поддержка декораторов, к которым можно было привыкнуть через Namespace (jwt_authorizer, database_searcher, doc_abort, etc)
  • минимизация повторяющегося кода (например, название события берётся из названия самой переменной)
  • всё импортируется из миркосервиса common (не соглашаться, если IDE предлагает другие пути!)

Моменты по коду

# все декораторы вызываются от объявленного вверху _sio-файла controller-а:
controller: EventController = EventController()

# одним из методов controller-а нужн декоритровать EventSpace:
@controller.route()
class MyEventSpace(EventSpace):
    # все куски кода в этом посте (кроме раздела "Прочее") 
    # складываются внутрь какого-то класса, наследующего EventSpace
    pass  # <сюда>

Виды событий

Серверные события (ServerEvent) [SUB, subscribe]

Используются редко, нужны для добавления в документацию и emit-а их через другие события или даже ReST-методы. Создаются всё ещё внутри класса, наследующего EventSpace, но теперь в формате:

<event_name> = ServerEvent(...)

Клиентские события (ClientEvent) [PUB, publish]

Такие события нужны для обработки входящих от клиента emit-ов. Они создаются через применение декораторов (см. ниже) к методам, объявленным внутри сабкласса к EventSpace-у.

Дуплексные события (DuplexEvent) [SUB & PUB]

Многие события являются одновременно серверными и клиентскими. Тогда они называются duplex-ными. Соответственно DuplexEvent умеет всё то, что умеет ClientEvent, и всё то, что умеет SeverEvent. Если серверное и клиентское события должны иметь одно и тоже название, то нужно объявить всего одно изначально клиентское событие и сделать его дуплексным через декораторы (см. ниже).

Декораторы

.argument_parser

Этот декоратор нужен всем методам класса, наследуемого от EventSpace, чтобы они стали ClientEvent-ами (клиентские события). Всего один необязательный агрумент: модель, которая и будет обрабатывать приходящий JSON. Модель может быть как одна из описанных в #94, так и стандартная pydantic-модель. По-умолчанию применяется пустая модель.

.mark_duplex

Помечает клиентское событие как дуплекное, чтобы его можно было emit-ить. Все аргументы опциональны:

  • server_model это модель, которая будет обрабатывать данные, уходящие с эмитом этого события. По-умолчанию будет использоваться та же модель, что используется для клиентского события
  • use_event позволяет получить keyword-аргумент event в функции обработчике, чтобы событие могло эмитить само себя. False по-умолчанию
  • остальные аргументы объяснены в документации pydantic

.marshal_ack

Добавляет acknowledgement с моделью к событию. Используется, если из handler-а возвращается что-то, что нужно обработыть моделью, будь то словарь, объект-модель или ORM-объект (чаще последний). По сути декоратор делает тоже самое, что и .marshal_with, откуда и название.

  • ack_model это модель, которая будет обрабатывать данные, она обязательна. Если модели нет, нужно использовать декоратор .force_ack
  • аргумент force_wrap сделан ради библиотечной натуры __lib__, в нашем проекте он всегда будет True, что установлено по-умолчанию
  • остальные аргументы объяснены в документации pydantic

.force_ack

Добавляет acknowledgement без модели к событию. Единственный аргумент, force_wrap, сделан ради библиотечной натуры __lib__, в нашем проекте он всегда будет True, что установлено по-умолчанию

Порядок декораторов

Новые декораторы описаны в том же порядке, в котором должны применяться в коде, сверху вниз. При использовании с другими декораторами, сначала идёт превращение функции в event (.argument_parser и .mark_duplex), затем другие декораторы в стандартном порядке (описан в #27) и только потом один из декораторов обработки ack-а (.marshal_ack или .force_ack)

Прочее

Привязка готового события

Делается, как обычно, внизу файла api.py (protected=True практически всегда нужен):

socketio.add_namespace(<адрес namespace-а>, <название EventController-а>, protected=True)

EventController-ов можно перечислять несколько через запятую:

socketio.add_namespace("/", ec1, ec2, ec3, protected=True)

Отправка серверных событий

Делается через метод ServerEvent.emit (или его прокси DuplexEvent.emit) с аргументами:

  • _room (str | None): строка-название комнаты, на которую нужно отправить событие. Комната user-<id> для всех сокетов этого пользователя создаётся в protected-namespace-ах автоматически
  • _include_self (bool, True по-умолчанию): включать ли отправителя текущего клиентского события в рассылку
  • _namespace (str | None): переопределяет пространство имён, в котором будет отправлено событие. По-умолчанию заполнено текущим namespace-ом. Менять не рекомендуется
  • _data (словарь, объект модели или ORM-объект): те данных, которые должна конвертировать модель. Опциоально, при отсутствии будут использоваться kwargs
  • **kwargs: keyword-аргументы, которые используются при создании возвращаемой модели. Используются только если в _data лежит None

Для удобства также существует метод .emit_convert, в котором чуть проще задавать параметры:

  • все параметры из .emit, но без лидирующих _
  • include_self теперь False по умолчанию
  • user_id аналогичен заданию room=f"user-{user_id}", заменяет room если задан

Приклепление к SocketIO

Контроллер нужно импортировать в __init__-(е/ах) и в файле api.py, назвать можно <...>_events. Чтобы он присоединился к инстанции SockeIO, нужно создать namespace или добавить контроллер к существующему namespace-у:

socketio.add_namespace("/", communities_meta_events, invitation_events, protected=True)
# "/": название namespace-а (всегда начинается с /)
# communities_meta_events, invitation_events: добавленные контроллеры
# protected=True: нужно, если будет использоваться jwt_authorizer или комната user-<id>

@niqzart
Copy link
Member Author

niqzart commented Jul 16, 2022

Кто такие эти ваши ack-и?!

Специально для фронтендеров

Матчасть

Ack (сокращ. от acknowledgement) — концепт из SocketIO, позволяющий после emit-а события получить подтвеждение его успешной (или не очень) обработки. По сути небольшой островок request-response общения в SocketIO

Особенности реализации в проекте

Все приходящие на сервер события проекта будут возвращать ack. Он представляет из себя один объект, содержажий:

  • code (челое число, обязателен): статус-код операции. Для похожести на ReST-часть, взяты HTTP-коды
  • message (строка, опциональна): приложенное текстовое сообщение. Используется для описания ошибок и простых ack-ов-уведомлений (например, если возвращать нечего и всё хорошо, придёт "Success")
  • data (объект, опциоанльный): приложенные данные. Используется только если есть что возвращать (например, id только что созданного сообщества). Обычно будет в том же формате, что и данные у одноименного SUB-события

Получается, что отправка события начинает напоминать POST-запрос: отправляем что-то, а в ответ приходит 200 или проблема, которую выводим пользователю. Думаю, это удобнее, чем перед отправкой начинать ждать какого-то ответного события и отдельно обрабатывать событие error

Применение

Работает просто:

socket.emit(
  "new-community",  // эмитим событие new-community
  { name: "Сообщество" },   // передаём какую-то инфу
  ({code, message, data}) => {  
    // когда сервер всё обработает, вернётся ack и тригернет этот callback
    console.log(code, message);  // 200 "Success"
    console.log(data);  // {id: 4, name: "Сообщество" }
  }
);

Теперь для создания события не нужно начитать слушать create-community в диалоге создания, а при получении закрывать диалог и редиректить пользователя: все эти действия складываются в emit события new-community

Глобальнее

Все системы с этим нововведениям сводятся к примерно-одинаковому потоку событий:

  1. Есть GET (или POST для пагинации) запрос для получения изначальных данных
  2. Нужно слушать всё те же события, что пользователь может вызвать с этой страницы
  3. Когда пользователь вызывает событие, оно приходит всем слушателям (другим вкладкам или другим пользователям), но не вызвавшему сокету
  4. Вызвавшему приходит ack об успехе или провале действия, на что сайт реагирует

Что с доками?

В доках у PUB-событий теперь будет по две модели: одна показывает входящий формат, вторая — формат ack-а. Скорее всего в таком порядке они в доках и будут, но в любом случае можно понять по названию модели. В дальнейшем там же могут появится и описания возможных ошибок

@niqzart niqzart merged commit 335859b into prod Jul 16, 2022
@niqzart niqzart deleted the feat/new-flask-siox branch July 16, 2022 23:42
@niqzart niqzart changed the title Feat/new flask siox SocketIO Возвращается (Снова) Jul 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant