«phpClub» — архив тем ("тредов"), посвящённых изучению PHP и веб-технологий.
Аноним 2019/07/11 16:58:20  №1432619 1
Двачуны, ума не могу приложить почему команда echo в цикле не выводит ничего на екран. За пределами цикла работает норм, а в середине ноль на масу. Чо такое???
Ответы: >>1432623 >>1440220
someApprentice 2019/07/14 01:22:03  №1434558 2
image.png (120, 1920x1080)
1080x1920
https://github.com/someApprentice/Crypter

Переделал схему наследования БД на Single Table Inheritance.
Перешел с Node.js на PHP.
Реализовал простой текстовый чат (без шифрования) и конфернеции между двумя пользователями.

Сообщения/конференции сохраняются в IndexeDB и извлекаются реактивно (https://rxdb.info/).

Проверять не обязательно, думаю я могу всё сделать самостоятельно. Но если есть какие-то интересные мысли или замечания и советы, всегда рад. Скорее всего у меня повсюду не вынесен код.

У меня есть вопросы:

1. Когда отправляется первое сообщение пользователю, то открывается сначала форма для отправки сообщения, а потом делается редирект в только что созданную конференцию, после отправки сообщения.pic-1 Это сделалось потому что нельзя изначально открыть конференцию, которая ещё не существует.

Было бы лучше если бы приватная конференция имела такой же id как и пользователь. И пользователь сам по себе как бы представлялися как приватная конференция. Тогда можно будет открывать диалог между двумя пользователями без создания отдельной конференция для них.

А ссылки на сообщения как раз помогут в том, чтобы чужие сообщения не отображались кому не надо.

Вот схема:

Conference:
uuid
type
name //может быть как имя пользователя так и имя конференции
...

Conference_Reference:
user
conference
updated
unread

Message:
...
conference


Message_Reference:
user
message

То есть пользователь получает только свои ссылки на сообщения, и чужие сообщения, из одной и той же приватной конференции, представляющая пользователя, он не получит.
И сами приватные конференции создаются при регистрации

Вопрос прост и короток: Ничего страшного если uuid приватных конференций будут такие же как у пользователя?


2. Вопрос связанный с плохим знанием Питона или более специфичный для используемой мною платформы WAMP.

Платформа которую я использую может использовать компоненты, которые запускаются вместе с ней и могут быть использованы для обработки того или иного действия. Эти компоненты определяются в конфиге ( https://github.com/someApprentice/Crypter/blob/master/wamp/.crossbar/config.json#L101-L120 ).

И я сталкнулся с такой проблемой, что каждый из этих компонентов вынужден для себя добавлять путь к модулям, и этот путь добавляется снова и снова с каждым модулем.

https://github.com/someApprentice/Crypter/blob/master/wamp/AuthenticatorSession.py#L4
https://github.com/someApprentice/Crypter/blob/master/wamp/AuthorizerSession.py#L5
https://github.com/someApprentice/Crypter/blob/master/wamp/MessengerSession.py#L4

И я слышал, что такой подход добавления модулей не очень хороший...

И заострю внимание на том, что эти компоненты запускаются самой платформой при её запуске в консоле или точнее сказать как процесс.

Вопрос в том - правильно ли добавляю пути к модулям и как это исправить если нет? И не является ли этот вопрос скорее специфичным для этой платформы и лучше бы ему быть заданным на её форуме?

3. Должно быть правильно шифровать любой контент который передается в сообщении будь то голосовое сообщение, изображение или любой другой файл. Библиотека шифрования может шифровать Uint8Array ( https://github.com/openpgpjs/openpgpjs#encrypt-and-decrypt-uint8array-data-with-a-password ).

Можно ли перекодировать исходный файл в Uint8Array а затем обратно в файл, для его сохранения на диск/хранилище?

Я поискал ответы в интернете и нашел такие ответы

https://stackoverflow.com/questions/37134433/convert-input-file-to-byte-array
https://stackoverflow.com/questions/25354313/saving-a-uint8array-to-a-binary-file

которые предлагают такой подход

var reader = FileReader(file);

reader.onloadend = (e) => {
let a = new Uint8Array(evt.target.result);

//encrypt

let blob = new Blob([a], { type: mimeType });

...
}

Но я не могу понять, Blob и File принимают в конструктор array of ArrayBuffer... Uint8Array является ArrayBuffer'ом?

В документации сказано, что нельзя создать ArrayBuffer сам по себе, вместо этого, нужно создать TypedArray, которым является Uint8Array.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

И Uint8Array тоже принимает в конструктор ArrayBuffer

Значит, можно обойтись без FileReader'а и написать напрямую

var buffer = async file.arrayBuffer()

var a = Uint8Array(buffer);

//encrypt

file = new File([encrypted], { type: ... });

Это верно?
Ответы: >>1440220 >>1440221
525 - 657 Аноним 2019/07/21 21:04:51  №1440220 3
>>1432619

Ты не привел пример кода, трудно подсказать. Обычно путают условие в for и пишут вместо условия продолжения цикла условие остановки (противоположное условие).

>>1434558

Вопросы по схеме БД:

- зачем нужна таблица participant, если есть conference_reference? Я не понимаю, чем таблица participant отличается от conference_reference.
- не стоит ли сделать тип ENUM для message.type, чтобы ограничить список допустимых значений?
- не стоит ли сделать message_attachment.type типом ENUM?
- что за URL указывается в поле message.content? URL на стороннем сервере? На своем? Если на своем, не логичнее ли вместо URL указать внешних ключ на таблицу файлов, либо какой-то идентификатор файла, из которого строится URL? Идентификатор удобен тем, что позволяет в будущем менять вид URL файла.
- возможно, стоит в conference_reference убрать id и использовать в качестве ключа пару (conference_uuid, user_uuid). Например, я вижу, что message_reference уже использует пару (conference, user).

И еще, ничего, что тут приватный ключ лежит? https://github.com/someApprentice/Crypter/blob/master/wamp/.crossbar/key.priv

И еще, не отдаем ли мы тут детали ошибки на сервере пользователю: https://github.com/someApprentice/Crypter/blob/master/wamp/AuthenticatorSession.py#L55

Тут, в тестах, возможно стоило сделать вспомогательную функцию для генерации токенов, так как в них легко опечататься и долго гадать, потому тест не работает: https://github.com/someApprentice/Crypter/blob/master/wamp/tests/authenticator_test.py

Еще, я у тебя вижу комментарии:
> TypeError: exceptions must be old-style classes or derived from BaseException, not <class 'module'>
скорее всего ты неправильно сделал импорт и указываешь не объект, представляющий класс, а объект импортированного модуля. То есть тебе надо разобраться с модулями и импортами.

Я бы советовал добавить в тесты проверку, что нельзя подписаться на чужие сообщения или получить их.

Также, немного неудобно, что приходится поддерживать 2 набора сущностей: в Питоне и в PHP.

Что касается исключений при авторизации, мне кажется, их правильнее ловить в обработчике авторизации, а не тут: https://github.com/someApprentice/Crypter/blob/master/api/src/EventListener/ExceptionListener.php Также, не стоит отдавать подробности исключения клиенту, они могут содержать важные внутренние данные.

Тут (https://github.com/someApprentice/Crypter/blob/master/api/tests/Controller/AuthControllerTest.php ) есть такой код:

$client->request(
$method = 'POST',
$uri = '/api/auth/login',

Так писать не стоит, это не keyword arguments из Питона и имена переменных не учитываются никак.

В тестах авторизации ( https://github.com/someApprentice/Crypter/blob/master/api/tests/Controller/AuthControllerTest.php ) ты проверяешь что тебе выдана кука с определенным именем. Ты полагаешься на знание внутреннего устройства механизма авторизации. И наличие куки не гарантирует авторизации. Правильнее запрашивать какую-то страницу для проверки доступа к ней. Можно даже сделать специальный тестовый метод /test/whoami для этого. Также, не надо копипастить огромные полотна кода, можно было сделать вспомогательную функцию для отправки запросов.

Соответственно тесты будут вида canLoginWithValidPassword, cannotLoginWithWrongPassword.

Далее, это ненадежный способ проверки, ведь речь тут о безопасности:

> https://github.com/someApprentice/Crypter/blob/master/wamp/AuthorizerSession.py#L40
> if 'private.message.to.' in uri:

Не лучше ли использовать str.startswith() ?

> regex = re.compile('^private\.message\.to\.([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12})$')
Микрооптимизация: регулярку можно скомпилировать один раз в начале скрипта и использовать скомпилированную версию. Также, для регулярок в питоне есть специальный литерал r'....'.

Также, я тут подумал, что для огромных конференций обновление через websocket может потребовать отдельную схему. У тебя, как я понял, при отправке сообщения в групповой чат на 1000 пользователей будет отправлено 1000 уведомлений private.message.to... и conference.updated.for.... Это будет нагружать вебсокет-демон. Логичнее для огромных чатов просто сделать канал updates.{conference_uuid} и слать туда одно уведомление. А клиент пусть отдельным запросом выясняет, что поменялось. Можно конечно слать и само сообщение, но надо убедиться, что кикнутые из чата, но не отписавшиеся от веб-сокет канала пользователи не смогут его получить.

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

Также, я хочу напомнить, что вебсокет - это ненадежный канал, он имеет право терять сообщения (пользователь отсоединился и пропустил сообщение) и потому стоит проектировать его как вспомогательный канал для быстрого уведомления об обновлениях, но не как единственный или основной канал доставки данных.

> Когда отправляется первое сообщение пользователю, то открывается сначала форма для отправки сообщения, а потом делается редирект в только что созданную конференцию ...
> Было бы лучше если бы приватная конференция имела такой же id как и пользователь. И пользователь сам по себе как бы представлялися как приватная конференция. Тогда можно будет открывать диалог между двумя пользователями без создания отдельной конференция для них.

Этот вопрос можно решить без изменений в БД, на уровне интерфейса. Мы можем, например, сделать URL вида /talk/{userId} и при его открытии смотреть: есть приватный диалог с этим пользователем или нет. Если есть - подгружать данные (либо редиректить на URL с id конференции), если нет - показать пустую страницу. То есть тебе не надо создавать что-то в БД только потому, что пользователь нажал на имя контакта.

Для этого нам надо иметь либо список всех конференций пользователя локально, либо метод API, который примет пару id пользователей и вернет id конференции между ними. Если мы используем API, то появляется задержка на время поиска конференции. Однако, мы можем разрешить пользователю писать и отправлять сообщение, не дожидаясь поиска конференции и подгрузки истории. Для этого надо доработать API приема сообщений так, чтобы в него можно было передавать либо id конференции, либо пару id пользователей. Это немного усложнит код. При желании, можно доработать аналогично и другие API, принимающие id конференции.

Твой вариант подразумевает, что если у пользователей A и B есть контакт C, то у них будет общая conference, но четыре разных conference_reference (A + C, C + A, B + C, C + B). Это не очень удачно, на мой взгляд. Не очень понятно, зачем тогда вообще нужна сущность conference и не проще ли из conference_reference, соответствующего диалогу, ссылаться напрямую на пользователя. И это будет создавать путаницу.

Если хочется, чтобы id конференции совпадал с пользовательским, тогда логичнее было бы в качестве id конференции брать отсортированную пару id пользователей. То есть конференция пользователей A и B может иметь идентификатор A_B. А в таблице можно использовать составной ключ:

TABLE conference (
lower_user_id UUID REFERENCES users (id),
higher_user_id UUID REFERENCES users (id),
PRIMARY KEY (lower_user_id, higher_user_id)
)

Но тут есть минус: нам придется все внешние ключи на конференцию делать парой uuid, что неудобно. Плюс, непонятно, какие uuid записывать в случае группового чата: первых двух присоединившихся пользователей? Что, если один или оба пользователя выйдут из чата?

На мой взгляд, эти проблемы можно попробовать решить, используя для конференции свой id, но указывая id участвующих юзеров для приватных диалогов. Это позволяет ссылаться на конференцию по id, а также искать приватный диалог по id участников:

TABLE conference (
id UUID PRIMARY KEY,
-- Заполняются только для приватных диалогов из 2 человек
lower_dialog_user UUID DEFAULT NULL REFERENCES users (id),
higher_dialog_user UUID DEFAULT NULL REFERENCES users (id),
UNIQUE KEY(lower_dialog_user, higher_dialog_user)
)

Есть ли какие-то недостатки у такого подхода?
Ответы: >>1447890 >>1447897
Аноним 2019/07/21 21:05:52  №1440221 4
>>1434558

> И я сталкнулся с такой проблемой, что каждый из этих компонентов вынужден для себя добавлять путь к модулям, и этот путь добавляется снова и снова с каждым модулем.

Есть возможность в конфиге указать дополнения к pythonpath: https://crossbar.io/docs/Native-Worker-Options/ . Не подойдет ли это тут? Единственное, я не советую привязываться к текущей директории, чтобы ничего не ломалось, когда текущая директория меняется. А привязываться к расположению файла. Либо использовать вспомогательный bash-скрипт, который задаст правильную текущую директорию перед запуском сервера.

Также, ты не очень удачно назвал директорию src, надо было назвать как-то вроде wampserver.

Если ты добавил корень всего в pythonpath, то далее ты просто пишешь что-то вроде: import database from src

Я советую почитать какую-нибудь статью про модули и пакеты, хотя бы эту: https://realpython.com/absolute-vs-relative-python-imports/

Там еще есть relative imports, но мне кажется, тут можно обойтись без них. То есть разберись с модуляим и пакетами в python и тем, как они ищутся. Добавь корневую директорию в pythonpath, и импортируй стандартным способом.

> Можно ли перекодировать исходный файл в Uint8Array а затем обратно в файл, для его сохранения на диск/хранилище?

Я не очень понимаю, зачем ты хочешь это сделать, но думаю, что можно. Забивать хранилище огромными файлами, затрудняя поиск информации в нем, не очень логично, а зачем их хранить на диске в зашифрованном виде, вообще непонятно.

>В документации сказано, что нельзя создать ArrayBuffer сам по себе, вместо этого, нужно создать TypedArray, которым является Uint8Array.

Если мы посмотрим определения, то увидим, что:

- ArrayBuffer - просто представляет набор байт в памяти фиксированной длины. Он не позволяет с ними что-то делать. Чтобы что-то делать с данными, нужно создать представление для доступа к ним: DataView или TypedArray
- DataView - позволяет читать/записывать числа разных размеров в ArrayBuffer
- TypedArray (Unit8Array и другие) - позволяет работать с данными в буфере, как будто это массив чисел определенного типа. Это именно представление для буфера, даже если ты не передаешь буфер или передаешь в конструктор не-буфер, он создает его сам. Как я понял, за TypedArray всегда прячется какой-то буфер, даже если ты его явно не создавал.

Blob - это абстрактная штука, из которой можно читать данные, оптимизированная под огромный объем данных (A Blob object represents a file-like object of immutable, raw data.). Он отличается от ArrayBuffer следующими особенностями:

1) он может собираться из нескольких отдельных буферов, позволяя избежать выделения памяти и копирования данных в одну общую область памяти. Вместо копирования он просто хранит ссылки на исходные буферы. И когда ты делаешь blob.slice(), он просто создает новый блоб со ссылками на куски буферов без выделения памяти и копирования каких-то данных. ArrayBuffer же представляет собой одну последовательную область памяти.

2) он может представлять данные, которые пока не загружены в память, или которых так много, что они в нее не поместятся, или которые потребуют много времени для чтения, и читать их кусками или потоком (методы stream(), slice()). Я подозреваю, что когда ты делаешь slice(), он реально ничего не читает в память и не меняет, а просто создает пометку "взять байты с X по Y из низлежащего блоба".

3) он может содержать информацию о MIME-типе и виде символов конца строки

4) он иммутабелен

5) для него можно создать URL (внутри браузера, он имеет вид blob://12345678) и пользователь может скачать блоб как файл по этому URL.

File это расширение Blob с доп. атрибутами и он представляет существующий файл на диске.

Ты читаешь файл в память целиком. Это работает только для маленьких файлов (порядка 1 Мб). Большие файлы надо уметь обрабатывать по кускам по следующим причинам:

- не тратить зря память
- быстрее начать передачу, не дожидаясь полной загрузки с диска в память
- отправка на сервер огромного файла может прерваться, потому его логичнее резать на куски и загружать кусками, а при ошибке пересылать заново только один кусок

Соответственно, твой подход работает только для маленьких файлов. Зачем ты хочешь их сохранять после шифрования, я не понимаю. Если ты пересылаешь файл контакту, то ты читаешь его целиком или частично, шифруешь, шлешь на сервер. Если ты принимаешь файл, то расшифровываешь и пишешь на диск. Если принимаешь файл кусочками, то расшифровываешь каждый кусочек, пишешь во временный файл, в конце переименовываешь.

Не забывай после окончания работы с файлом закрывать все и освобождать, чтобы данные не висели в памяти.

Если тебе пока не хочется возиться с кусочками, то стоит поставить ограничение по размеру на несколько мегабайт. Если ты хочешь сделать качественную передачу файлов, придется учиться резать их на кусочки по несколько десятков или сотен килобайт.

> Но я не могу понять, Blob и File принимают в конструктор array of ArrayBuffer...

Они принимают массив буферов, а не один буфер, чтобы можно было собрать большой файл из кусочков, разбросанных по разным местам памяти, без копирования их в общую область памяти. И я подозреваю, он позволяет даже собирать блоб из кусочков данных в памяти и тех, что не сейчас в памяти.

> Uint8Array является ArrayBuffer'ом?

Нет, это представление для записи/чтения данных из буфера по одному числу типа uint8 за раз.

Зачем нужен FileReader, когда есть blob.stream(), я не очень понимаю, я думаю, он просто исторически появился раньше (в FF3.6), чем Blob.stream() (это новая экспериментальная технология).
Ответы: >>1447897
someApprentice 2019/08/04 10:55:46  №1447890 5
messenger.mp4 (4146, 1280x720)
720x1280
>>1440220
>- зачем нужна таблица participant, если есть conference_reference? Я не понимаю, чем таблица participant отличается от conference_reference.
Это денормализация чтобы получить конференцию по собеседнику, в случае приватных конференций. Таблица participant нужна скорее для публичных конференций где количество собеседников может быть больше двух. Поскольку изначально предполагалось что приватные конференции будут представлены как конференции из двух человек, это используется и для приватных конференций.

Это подход обсуждался нами ранее:
https://phpclub.tech/pr/chain/1394587/#1389598
>>Когда пользователь впервые отправляет приватное сообщение другому пользователю, создаётся конференция, пара получателей и "копии" диалога для каждого пользователя. Но прежде чем это сделать, нужно проверить, действительно ли сообщение отправляется впервые. Для этого нужно проверить, что конференции между этими пользователями не существует. То есть нужно найти двух Получателей с одинаковым Диалогом и чтобы этот Диалог был приватным. Нужно сделать это (желательно) за один запрос.
>Такие запросы с джойнами будут плохо работать на больших нагрузках. Они же почти не оптимизируются индексами никак и требуют перебор строк. Возможно, тут придется сделать денормализацию, например, добавить в Dialog либо в Participant дополнительные поля.

>- не стоит ли сделать тип ENUM для message.type, чтобы ограничить список допустимых значений?
>- не стоит ли сделать message_attachment.type типом ENUM?
Да, я не знал про этот тип. Исправлю.

>- что за URL указывается в поле message.content? URL на стороннем сервере? На своем? Если на своем, не логичнее ли вместо URL указать внешних ключ на таблицу файлов, либо какой-то идентификатор файла, из которого строится URL? Идентификатор удобен тем, что позволяет в будущем менять вид URL файла.
Исправлю.

>- возможно, стоит в conference_reference убрать id и использовать в качестве ключа пару (conference_uuid, user_uuid). Например, я вижу, что message_reference уже использует пару (conference, user).
Да, id не как не используются для этих сущностный и не нужны. Вроде есть такой шаблон проектирования, когда создаются вспомогательные таблицы, которые ссылаются на основные, как бы соединяя их, и там нету поля id. Как он называется? Чтобы быть уверенным что я делаю.

>И еще, ничего, что тут приватный ключ лежит? https://github.com/someApprentice/Crypter/blob/master/wamp/.crossbar/key.priv
Удалю.

>И еще, не отдаем ли мы тут детали ошибки на сервере пользователю: https://github.com/someApprentice/Crypter/blob/master/wamp/AuthenticatorSession.py#L55
На ту строчку которую вы указали выдаётся ошибка о том что не предоставлен Bearer token. Об этом нужно выдать ошибку пользователю. А вот ниже там действительно обрабатывается любая ошибка, но это только потому что я не знаю как обработать отдельные ошибки https://github.com/someApprentice/Crypter/blob/master/wamp/AuthenticatorSession.py#L77-L78

>Тут, в тестах, возможно стоило сделать вспомогательную функцию для генерации токенов, так как в них легко опечататься и долго гадать, потому тест не работает: https://github.com/someApprentice/Crypter/blob/master/wamp/tests/authenticator_test.py
Тогда пришлось бы генерировать токен так как он генерируется в функции регистрации/логина. Не будет ли это положением теста на знание о том как генерируется токен?

>Также, немного неудобно, что приходится поддерживать 2 набора сущностей: в Питоне и в PHP.
Я думал об этой проблеме и пришел к выводу, что это не важно что у разных языков одинаковый набор сущностей - конечным результатом должен быть рабочий код, а не количество дублирующихся сущностей. Даже если это важно, то нет способа это решить, поэтому проблема сама не имеет смысла.

>Что касается исключений при авторизации, мне кажется, их правильнее ловить в обработчике авторизации, а не тут: https://github.com/someApprentice/Crypter/blob/master/api/src/EventListener/ExceptionListener.php
Делать блок try/catch?

>Также, не стоит отдавать подробности исключения клиенту, они могут содержать важные внутренние данные.
Забыл изменить перед коммитом. Мне нужно сделать рефакторинг обработчика, чтобы в девелопменте они как раз выдавались.

>Тут (https://github.com/someApprentice/Crypter/blob/master/api/tests/Controller/AuthControllerTest.php ) есть такой код:
>
>$client->request(
>$method = 'POST',
>$uri = '/api/auth/login',
>
>Так писать не стоит, это не keyword arguments из Питона и имена переменных не учитываются никак.
А как понимать что за аргумент был передан? Иногда нужно передать пустое значение, чтобы отправить следующий аргумент, и потом когда читаешь код не понимаешь что там должно быть. Нужно тогда заранее определять переменные и передавать их?

>В тестах авторизации ( https://github.com/someApprentice/Crypter/blob/master/api/tests/Controller/AuthControllerTest.php ) ты проверяешь что тебе выдана кука с определенным именем. Ты полагаешься на знание внутреннего устройства механизма авторизации. И наличие куки не гарантирует авторизации. Правильнее запрашивать какую-то страницу для проверки доступа к ней. Можно даже сделать специальный тестовый метод /test/whoami для этого.
Разве не нужно проверить что выдаются кукисы? Они ведь нужно для правильной работы приложения.

Проверка авторизации выполняется тестом проверки доступа к разлогиниванию https://github.com/someApprentice/Crypter/blob/master/api/tests/Controller/AuthControllerTest.php#L232-L242

>Также, не надо копипастить огромные полотна кода, можно было сделать вспомогательную функцию для отправки запросов.
Я как раз хотел спросить где хранить вспомогательные для тестов функции делать класс tests/Utils.php?

>Соответственно тесты будут вида canLoginWithValidPassword, cannotLoginWithWrongPassword.
Нужно разбить одну функцию на две?

>Далее, это ненадежный способ проверки, ведь речь тут о безопасности:
>
>> https://github.com/someApprentice/Crypter/blob/master/wamp/AuthorizerSession.py#L40
>> if 'private.message.to.' in uri:
>
>Не лучше ли использовать str.startswith() ?
Я не знал про эту функцию. Почему тот метод не надёжный?

>> regex = re.compile('^private\.message\.to\.([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12})$')
>Микрооптимизация: регулярку можно скомпилировать один раз в начале скрипта и использовать скомпилированную версию.
Зачем компилировать регулярку в начале скрипта? Нужны разные компиляции регулировок в зависимости от uri по которому нужна авторизация. Их все нужно делать в начале скрипта?

>Также, я тут подумал, что для огромных конференций обновление через websocket может потребовать отдельную схему. У тебя, как я понял, при отправке сообщения в групповой чат на 1000 пользователей будет отправлено 1000 уведомлений private.message.to... и conference.updated.for.... Это будет нагружать вебсокет-демон. Логичнее для огромных чатов просто сделать канал updates.{conference_uuid} и слать туда одно уведомление. А клиент пусть отдельным запросом выясняет, что поменялось. Можно конечно слать и само сообщение, но надо убедиться, что кикнутые из чата, но не отписавшиеся от веб-сокет канала пользователи не смогут его получить.
М да, для группового чата так и нужно сделать. Вообще отправка 1000 уведомлений сделано, потому что, представим, пользователь очень популярен и каждый день получает 100 а может и 1000 новых сообщений от уникальных пользователей, и в итоге ему на всех нужно подписываться, что перенагрузит клиентское приложение. Гораздо лучше перенести эту нагрузку на серверную часть.

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

Только мне кажется что менять ключ не обязательно, потому что пользователь всё равно не сможет ни получить ни отправить сообщения, потому что API/WAMP его не авторизует для этого.
Ответы: >>1448031
someApprentice 2019/08/04 11:20:31  №1447897 6
>>1440220
>Также, я хочу напомнить, что вебсокет - это ненадежный канал, он имеет право терять сообщения (пользователь отсоединился и пропустил сообщение) и потому стоит проектировать его как вспомогательный канал для быстрого уведомления об обновлениях, но не как единственный или основной канал доставки данных.
>пользователь отсоединился и пропустил сообщение
Да, об этой проблеме я уже осведомлён. Например, пользователь отсоединился на долгое время (несколько месяцев или год) и какой-то его собеседник или многие собеседники решили отредактировать или прочесть большое количество сообщений. И когда пользователь вновь откроет клиент, нужно обновить все эти сообщения. Очевидно, что нельзя подхватывать всё это большое количество сообщений сразу, и нужно подгружать их sequential, т.е. стримить.

Это касается только обновленных сообщений. Старые сообщения и новые сообщения в офлайне подхватываются с помощью API.

В чем ещё вебсокет может быть ненадёжен - я не знаю.

>> Когда отправляется первое сообщение пользователю, то открывается сначала форма для отправки сообщения, а потом делается редирект в только что созданную конференцию ...
>> Было бы лучше если бы приватная конференция имела такой же id как и пользователь. И пользователь сам по себе как бы представлялися как приватная конференция. Тогда можно будет открывать диалог между двумя пользователями без создания отдельной конференция для них.
>
>Этот вопрос можно решить без изменений в БД, на уровне интерфейса. Мы можем, например, сделать URL вида /talk/{userId} и при его открытии смотреть: есть приватный диалог с этим пользователем или нет. Если есть - подгружать данные (либо редиректить на URL с id конференции), если нет - показать пустую страницу. То есть тебе не надо создавать что-то в БД только потому, что пользователь нажал на имя контакта.
>
>Есть ли какие-то недостатки у такого подхода?
Да, это неудобно когда что-то редиректиться или выдается пустая страница. Гораздо привычней и соответственно удобней когда сразу открывается диалог и уже в него можно писать.

Я возможно неправильно изложил проблему, проблема была не составлении URL и не в получении записи конференции, а в том что при открытии диалога конференции впервые её просто не существует. И я решил сделать приватные конференции предопределенными, и в качестве их идентификатора/первичного ключа использовать дублированный uuid пользователя, так как предполагал, что переходя по URL с uuid пользователя будет удобно получить эту конференцию. Но после того как я прочитал ваш ответ, я понял одну критическую проблему. То что у двух пользователей будет две конференции с их uuid а сообщения должны находится в какой-то одной. Спасибо за это замечание.

Эту проблему действительно нужно решать на уровне интерфейса.

Нужно отдельный алгоритм для обработки приватных конференций и отдельный для публичных, а не пытаться всё привести к одному как пытался сделать я.

Всё очень просто:

Для приватной конференции мы используем URL 'u-{user_uuid}' и делаем метод API getConferenceByParticipant($user_uuid), и если её ещё не существует, то мы просто не запрашиваем сообщения, а после отправки сообщения и её создания нас уведомит об этом WAMP и мы её реактивно подхватим.
Для публичной конференции всё остаётся без изменений только открываем мы её по URL 'c-{conference_uuid}'.

По сути, это будет тоже самое что и ваша идея, т.к. conference_reference содержит идентификаторы и пользователя, и собеседника и самой конференции.


>>1440221
>> И я сталкнулся с такой проблемой, что каждый из этих компонентов вынужден для себя добавлять путь к модулям, и этот путь добавляется снова и снова с каждым модулем.
>
>Есть возможность в конфиге указать дополнения к pythonpath: https://crossbar.io/docs/Native-Worker-Options/ . Не подойдет ли это тут?
Наверно подойдёт. Я не знал про это опцию.

>Единственное, я не советую привязываться к текущей директории, чтобы ничего не ломалось, когда текущая директория меняется. А привязываться к расположению файла. Либо использовать вспомогательный bash-скрипт, который задаст правильную текущую директорию перед запуском сервера.
Текущая директория это какая? Я бы указал относительный путь к ./wamp/src

>> Можно ли перекодировать исходный файл в Uint8Array а затем обратно в файл, для его сохранения на диск/хранилище?
>
>Я не очень понимаю, зачем ты хочешь это сделать
Чтобы зашифровать голосовое/видео сообщение и сохранить его на сервер. Или не только сам контент сообщения, но и приложения к нему, например фотографию. Любая информация должна быть зашифрована.

>Забивать хранилище огромными файлами, затрудняя поиск информации в нем, не очень логично, а зачем их хранить на диске в зашифрованном виде, вообще непонятно.
Зашифрованные файлы весят больше?

Я поискал в интернете и вроде не очень.
https://security.stackexchange.com/questions/76568/does-encrypting-a-file-make-it-larger
https://security.stackexchange.com/questions/8245/gpg-file-size-with-multiple-recipients

Ответы: >>1448031
someApprentice 2019/08/04 15:29:09  №1448031 7
image.png (267, 500x337)
337x500
Я опять нечаянно запостил в закрытый тред >>1447890 >>1447897