Я должен признаться, что написать эту статью я решил очень спонтанно, прочитав статью «XMPP отстой» и почувствовав некоторую близость к чувствам автора, так как также использую ХМРР в одном из наших продуктов. Тем не менее, сжав эмоции в кулак, я всё-таки решил просто изложить, почему я испытываю смешанные чувства к данному протоколу и разложить по полкам плюсы и минусы. Также расскажу, что мы выбрали для сервера и клиента. Это все, чтобы тебе, дорогой читатель, сделать правильный выбор и вырвать меньше волос в твоих будущих проектах.

Я работаю в компании Thirdlane, где мы делаем продукты для VoIP и унифицированных коммуникаций. В свое время мы выбирали протокол для коммуникаций в одном из наших продуктов. В момент выбора протокола, вопрос стоял просто: писать ли протокол самим, или использовать ХМРР. Других протоколов мы не рассматривали. После не долгих дискуссий мы выбрали ХМРР, полагаясь на то, что это стандартный открытый протокол, имеющий широкое использование, документацию, разные имплементации и расширения, которые на поверхности казались стопроцентно отвечающими нашим требованиям. Сейчас, после достаточно долгого времени роботы с ХМРР, я могу сказать, что я не уверен, что мы сделали правильный выбор. Тем не менее, я не знаю, какими были бы мои чувства, если мы бы пошли другим путем. Конечно сидя вечерами за чашкой чая, я иногда мечтаю вернуться в то время и написать свой закрытый протокол с блек джеком и node.js, но увы. Тут же вспоминаю слова Сократа:

«Женишься ты, или не женишься, ты все равно об этом пожалеешь.»

В момент выбора основные достоинства протокола нам казались следующими:

  • Это открытый протокол;
  • Существует огромное количество серверов, и клиентов для него;
  • Возможность интеграции конечного продукта с внешним миром;
  • Он используется в крутых продуктах: MS Lync, Cisco Jabber, Atlassian HipChat, etc..;
  • Огромное количество расширений в виде XEP-ов;
  • Возможность расширять протокол.

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

Клиентская сторона


Так как наш продукт ориентирован на веб-браузер, у нас в руках есть только JS, мы его любим, он классный, стильный, и ваще. Если посмотреть на JS-библиотеки для работы с XMPP, то мы увидим несколько поделок типа strophe, xmpp-ftw, JSJaC. Ни одна из них не пришлась мне по душе, в отличие от вышеупомянутой stanza.io, в которую я лично влюбился с первого взгляда. Библиотека очень хорошо продумана и очень чисто написана, поддерживает все жизненно-важные XEP-ы, с ней легко и приятно работать. Единственный ее минус — слабая документация, что частенько требовало чтения исходного кода. Но это было тоже полезно.

Серверная сторона


Тут мы также не долго парились с выбором, так как в Оpenfire на тот момент не было ни Stream Management, ни Web Socket транспорта. Ejabberd также отпал сразу, по причине Erlang. Кроме того, оба продукта, будучи Open Source, имеют тенденцию к коммерциализации, что не есть гуд. В конечном счете мы выбрали Prosody. Шустрый, легкий, написан на красивом языке Lua, отзывчивое комюнити, проект не мертв, каждый XEP — это отдельный модуль, который легко подключать\отключать, возможность легко писать свои модули, ну, в общем, няшка.

Протокол:

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

XEP-0115: Entity Capabilities

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

XEP-0280: Message Carbons

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

XEP-0308: Last Message Correction

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

XEP-0184: Message Delivery Receipts

Это просто песня. Вкупе со стрим менеджментом позволяет дать пользователю знать, что сообщение отправлено и что сервер его получил, и позволяет знать, что сообщение доставлено до адресата. Очень полезно, но, к сожалению, эта информация не попадает в историю переписки — «XEP-0313: Message Archive Management».

XEP-0313: Message Archive Management

Это печалька… Данный ХЕР служит для хранения истории и ее получения. В первую очередь вы никогда не узнаете, было ли доставлено какое либо сообщение, если закрыли клиент. Во вторых, получение истории — это указание от какой даты хочешь и какой лимит. Как по мне, так пейджинг был бы здесь уместнее. Также нельзя организовать поиск сообщений по тексту на сервере, что тоже печалит. Но при наличии рук, это, конечно же, решается всякими костылями. Тут я полностью солидарен с автором вышеуказанной статьи. Непонятно, как в протоколе это было упущено.

XEP-0045: Multi-User Chat

Это худшее, что случилось с ХМРР. Это грусть и тоска, боль и страдания, уныние и отчаяние. Я так скажу: число сатаны — это не 666, это 0045. В первую очередь, это не групповые чаты, это имитация IRC. Всё. На этом можно было бы закончить про 0045, но давайте все-таки разберемся, кто виноват и что делать? Знаете, как войти в комнату? Послать запрос, чтобы войти в комнату. Знаете, как создать комнату? Послать запрос, чтобы войти в комнату. Если её не существует, она создастся. Уникальность имени комнаты сервера решают, кто как хочет. Нравится? Велком ту хелл. Тот, кто создавал комнату (ну или кто имеет право), задает количество последних сообщений, которые будут отправлены клиенту, который войдет в комнату. Только вошел, а тебе N сообщений с группы прилетело прямо в морду. Самое ужасное, что 0045 не предусматривает хранения оффлайн участников. А пользователь считается вышедшим из комнаты, как только закроет свой клиент. Для того чтоб вернуться потом в эту комнату, вам нужно или запомнить её имя, или воспользоваться «XEP-0048: Bookmarks». Представьте себе теперь ситуацию: вас пригласили в групповой чат, вы вошли, поговорили и добавили себе эту комнату в букмарки, да еще и указали, чтоб входить в эту комнату каждый раз при логине автоматически. Знаете, что будет, если эту комнату удалили, пока вы были оффлайн? При логине вы прочитаете букмарки и сделаете «вход в комнату», а как мы знаем, что создание не отличается от подключения, то правильно! Вы создадите новую комнату. Еще? У меня для вас будет. Если комнату не указать как «persistent», она будет удалена, как только последний пользователь закроет клиент или выйдет из нее. Вот такие дела, кто виноват — ясно, теперь вопрос: что делать?

Решили мы данную проблему так: все комнаты создаются и конфигурируются, как «member-only», и persistent. В нашем клиенте мы отрисовываем в списке участников всех тех, у кого есть право входить в комнату. Как оказалось, Prosody запрещает получать такой список всем участникам по умолчанию, хотя ХЕР говорит об обратном. Обсудили это с разработчиками Prosody, надеюсь, что договорятся и испрявят. Ну а пока у нас собственное изменение в коде Prosody. Также пришлось сделать изменение, чтобы пользователь мог сам себя исключить из этого списка (да, по умолчанию тоже нельзя), что означает его выход из комнаты. Еще написали кастомный модуль для Prosody, следящий за изменением списка пользователей комнаты (room affiliation) и сообщающий об этом всем участникам. Таким нехитрым способом у нас получились конференции «почти как в скайпе». Интересно, что в XMPP сообществе сейчас обсуждается подобная реализация MUC только используя Pub/Sub, что подтвердил мне один из участников рабочей группы.

XEP-0085: Chat State Notifications

Полезная штука, всегда хорошо знать, печатает ли пользователь или нет, просто приятный ХЕР.

XEP-0198: Stream Management

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

Вывод


После всего, я все равно затрудняюсь объяснить, почему мои чувства остались негативными. Ведь мы добились, чтобы все работало красиво. Быть может по тому, что мы ожидали, что будем пользоваться протоколом, в котором есть все и не нужно будет тратить время на решение очевидных проблем. Но «втянувшись в бой», мы наткнулись на кучу подводных камней и не продуманных реализаций. Все проблемы, в конце концов, решались, но как в том анекдоте «ложка нашлась, а осадок остался». Еще, наверное, потому, что мы потратил много времени на изучение спецификаций, чтения чужого исходного кода и приведение в порядок ХЕР-ов, которые нам не подходили, да еще, таким образом, чтобы они остались обратно совместимыми с нативными клиентами – что все вместе, есть весьма не благодарное занятие. Но, как я говорил, мы добились требуемого результата. Я думаю, вам самим решать, готовы ли вы изучать сложные спецификации, напарываться на баги их реализаций в клиентах и серверах. Если вы делаете большой сервис, да еще и с интеграциями, то написание собственных расширений вам гарантировано. Посмотрите на тот же HipChat, у него куча кастомных станз. Эти хоть описаны в документации, а что под капотом у Lync или Jabber вообще никому не известно. С другой стороны, если вам нужен базовый функционал, то ХМРР — именно то, что вам надо. Огромное количество всего из коробки, работает 99% случаев.

Комментарии (28)


  1. 0xd34df00d
    30.06.2015 20:09

    Так сервер же в 0045 вам status code 201 присылает, если комната только что была создана. А ещё в презенсе на джоин можно указать, сколько именно сообщений вы хотите получить.

    А 0313 действительно местами печальный :(
    Но там можно формами искать, так что определённым костылём и допиливанием Prosody это дело решается, как вы и написали. Причём, что интересно, совершенно не выходя за рамки стандарта.


    1. Emily_Rose Автор
      30.06.2015 20:29

      На счет 201, это конечно таки да, но потом, код — который разгребает презенс сообщения, выглядит просто ужасно: наличие тех или иных элементов\кодов в станзе, может означать очень большое количество вещей, для каждой из которых нужна отдельная логика. То есть с моей точки зрения, логичней было бы — много ивентов, и много хендлеров, а сейчас один параметризированный ивент, да еще и таким образом, что логики почти нет, просто надо знать.


  1. BigD
    30.06.2015 21:26

    А где это он в Lync используется? Там же SIP.


    1. Emily_Rose Автор
      30.06.2015 21:35

      SIP используется для установки аудио\видео звонков. XMPP возможно для обмена сообщениями, и точно для федерейшн. Пруф


      1. BigD
        30.06.2015 21:38

        Спасибо за ссылку. Я пока остаюсь в уверенности, что внутри инстанса сообщения ходят по SIP/SIMPLE, а не XMPP. Хотя может что-то и поменялось…


  1. Klukonin
    30.06.2015 22:02
    +7

    Судя по описанию вы взяли готовый протокол и готовую реализацию. При небольшой доработке получили устроивший вас результат и решили поставленную задачу. И попытались приравнять к недостаткам то что вам пришлось вносить некоторые доработки и читать документацию. Серьезно? Вы считаете это недостатком? А чем тогда должны заниматься разработчики, решая задачу написания сервиса по обмену сообщениями? Брать опенсорсный протокол и его опенсорсную реализацию, прикручивать нескучный интерфейс и, не внося никаких существенных изенений, сдавать заказчику? Как-то оно это… Не совсем честно, имхо.


    1. Emily_Rose Автор
      30.06.2015 22:28
      +8

      Суть статьи, в том, что мы не уверены, что написание собственного протокола и собственной реализации, заняла бы больше времени и нервов, чем взять готовый «опенсорсный» протокол, и его реализацию и начать ею пользоваться. К реализации ХМРР в инкарнации Prosody как сервера у нас ни одной претензии нет, кроме одной махонькой, с которой согласились сами разработчики. К протоколу у нас есть некоторые замечания, или если он «опенсорсный», то типа нельзя говорить, что вот это и то, по моему мнению, сделано не так как бы хотелось? Почему я не могу этим делиться? С моей точки зрения ХМРР немного отстал от текущего положения вещей, особенно, меня печалит 0045. Что не честного в размышлении о таких вещах?


      1. Klukonin
        01.07.2015 07:23
        +2

        Да не, то что вы предлагаете идеи по улучшению протокола — это прекрасно. Надеюсь что вы донесете свою мысль до разработчиков.
        Это развитие, это хорошо.
        моя мысль заключалась в том что брать опенсорсный протокол с опенсорсной реализацией и жаловаться на то что пришлось допиливать напильником и тратить на это время… Ну это не совсем честно. Опенсорс по умолчанию подразумевает что есть некая экспертиза, выраженная в коде, и которую любой желающий может использовать свободно в своих целях. Это изначально конструктор. И каких-то блоков может не оказаться, придется их делать самому. Но это совершенно не сопоставимо с разработкой и изготовлением всех блоков самостоятельно.


        1. ad1Dima
          01.07.2015 08:05
          +2

          моя мысль заключалась в том что брать опенсорсный протокол с опенсорсной реализацией и жаловаться на то что пришлось допиливать напильником и тратить на это время…


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


      1. alekciy
        01.07.2015 16:28

        Будте спокойны. Написание собственного протокола и реализация заняла бы больше времени (тогда заняла бы). Когда что-то уже есть, то проще начать это использовать и быстро выявить недостатки/неприминимость в своем системном окружении. Когда нет ничего, нужно еще на практике наловить кучу своих граблей, наловить кучу «особенностей реализации» других систем. И это пожиратели времени суммируются и кодингом, проектированием…

        XMPP со своими плюсами и минусами есть и некоторое время еще будет. И пока ни кто не спешит написать ему полноценную замену.


    1. mifki
      01.07.2015 02:21

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


  1. kekekeks
    30.06.2015 23:02
    +1

    Я 3 (три) раза пытался использовать XMPP, оно даже доходило до продакшна, но в итоге всё равно приходилось переделывать на что-то другое, заточенное под конкретные нужды.


  1. DnAp
    30.06.2015 23:40
    +3

    Во вторых, получение истории — это указание от какой даты хочешь и какой лимит. Как по мне, так пейджинг был бы здесь уместнее.
    Еще в прошлой статье удивился, но не отписал так как не читал XEP.
    Это сделано намерено — в промежутке между получениями страниц могут появиться новые сообщения и пейджинг бы возвращал дубликаты, а если историю догрузили спустя 30 минут и 100 сообщеий, то пришлось бы загружать несколько страниц состоящих только из дубликатов или считать входящие.


    1. Emily_Rose Автор
      01.07.2015 00:44

      При первом взаимодействии с контактом (вы открыли чата с ним, или он написал вам сообщение), посылаем запрос на получение N последних сообщений, теперь у нас есть коллекция, в которой есть N или N+1 сообщений или пустой ответ, в этом случае ничего не надо делать. Теперь, как только это произошло, и ответ не был пустым — посылаем запрос на пейджинейдет историю, и как параметр запроса указываем дату самого первого(earliest) сообщения, что у нас уже есть. Ответ кешируем. Теперь можно пользоваться пейджингом без проблем и не будет никаких дубликатов. Конечно, если у вас нет машины времени. То есть в моем варианте — мы берем точку отсчета: Самое раннее сообщение, которое есть на клиенте, и продолжаем с шагом назад. В случае реализации ХМРР, нам нужно указать дату в прошлом и от нее с шагом вперед. Пейджинг, он и тут есть из коробки, только не в ту сторону: Result Set Management (XEP-0059).


      1. ad1Dima
        01.07.2015 05:14
        +1

        запрос N1

        посылаем запрос на получение N последних сообщений

        запрос N2
        посылаем запрос на пейджинейдет историю, и как параметр запроса указываем дату самого первого(earliest) сообщения

        И, я так понимаю, запрос N3
        Теперь можно пользоваться пейджингом без проблем


        Против того, что, если я правильно понял берем текущую дату и получаем N сообщений, берем дату самого старого и получаетм ещё N и так далее. Все одним и тем же запросом.

        Ну и я как-то не понял, как в вашей схеме обходится то, что описал DnAp: пока мы получали страницу с номером N нам написали ещё 2 cообщения, и 2 последних сообщения со страницы N перешли на страницу N+1?


        1. Klukonin
          01.07.2015 07:31

          Абсолютно сумасшедшая мысль. Что если хранить историю сообщений локально в SQLite и сортировать по времени?
          Можно потом формировать вывод как угодно.


          1. ad1Dima
            01.07.2015 08:03

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


            1. Klukonin
              01.07.2015 08:51

              На сколько я понял проблему, в данном случае у нас будут дубликаты сообщений. В случае с SQLite это решается парой запросов.
              По поводу синхронизации между девайсами — лучше pgp и торрентов пока не могу придумать. можно передавать Magnet ссылку средствами самого протокола. Но даже если ее перехватят, страшного ничего в этом нет. Ломать pgp то еще удовольствие.

              Не люблю описывать словами свои идеи. Лучше, конечно, схему рисовать. но это надо углубляться и смотреть ваш код.


              1. ad1Dima
                01.07.2015 09:02

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

                как напишешь, так и будет.

                По поводу синхронизации между девайсами — лучше pgp и торрентов пока не могу придумать

                просто хранить переписку на сервере, не? и после переустановки приложения она никуда не пропадёт


                1. Klukonin
                  01.07.2015 09:20

                  просто хранить переписку на сервере, не? и после переустановки приложения она никуда не пропадёт

                  Но кто на это согласится, господа? (с) Ржевский.

                  Идея синхронизации между устройствами понятна. У меня есть история сообщений на всех устройствах и если одно выходит из строя — это не критично, сообщения подтянутся потом после восстановления. Вероятность отказа всех устройств одновременно примерно равна вероятности отказа хранилища в датацентре.
                  Облачную услугу хорошо бы монетизировать. Кому надо — подпишутся на хранение защищенной копии своих сообщений на удаленном сервере. На условиях что закрытый ключ есть только у пользователя и расшифровать может только он. Но это подойдет не всем. Многие принципиально не будут отдавать свою переписку. Даже в зашифрованном виде.


                  1. ad1Dima
                    01.07.2015 09:33

                    Вероятность отказа всех устройств одновременно примерно равна вероятности отказа хранилища в датацентре.

                    Нет, она равна вероятности отказа одного устройства, поделённое на количество устройств. И если количество равно единице…

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

                    И вообще, мы несколько отклонились от оригинального комментария.


                    1. vintage
                      01.07.2015 10:40

                      Вероятность одновременного отказа всё же равна произведению вероятностей отказа каждого устройства. И она очень быстро стремится к нулю при росте числа устройств.


                      1. ad1Dima
                        01.07.2015 10:54

                        Вероятность одновременного отказа всё же равна произведению вероятностей отказа каждого устройства

                        Да, так точнее.


        1. Emily_Rose Автор
          01.07.2015 13:57

          Против того, что, если я правильно понял берем текущую дату и получаем N сообщений, берем дату самого старого и получаем ещё N и так далее. Все одним и тем же запросом.
          Это почти то, что мы сделаем на сервере, но вернём номера страниц и ссылки на них. ХМРР не сможет сделать то, что вы говорите из коробки. А Пейджинг мне кажется более удобным, так ка при поиске по тексту сообщений, можно просто вернуть номера страниц, в которых этот текст встречался.


      1. DnAp
        01.07.2015 13:47

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


  1. roman_kashitsyn
    01.07.2015 11:58
    +4

    Ждём третью статью с заголовком «Ну не отстой ли XMPP?».


    1. Revertis
      01.07.2015 14:34
      +4

      Или «XMPP отстой? Вы не видели ICQ!»


  1. irostovtsev
    08.07.2015 21:41

    Хабру было бы не плохо иметь, а-ля XEP-0308: Last Message Correction для комментов.