Я должен признаться, что написать эту статью я решил очень спонтанно, прочитав статью «XMPP отстой» и почувствовав некоторую близость к чувствам автора, так как также использую ХМРР в одном из наших продуктов. Тем не менее, сжав эмоции в кулак, я всё-таки решил просто изложить, почему я испытываю смешанные чувства к данному протоколу и разложить по полкам плюсы и минусы. Также расскажу, что мы выбрали для сервера и клиента. Это все, чтобы тебе, дорогой читатель, сделать правильный выбор и вырвать меньше волос в твоих будущих проектах.
Я работаю в компании Thirdlane, где мы делаем продукты для VoIP и унифицированных коммуникаций. В свое время мы выбирали протокол для коммуникаций в одном из наших продуктов. В момент выбора протокола, вопрос стоял просто: писать ли протокол самим, или использовать ХМРР. Других протоколов мы не рассматривали. После не долгих дискуссий мы выбрали ХМРР, полагаясь на то, что это стандартный открытый протокол, имеющий широкое использование, документацию, разные имплементации и расширения, которые на поверхности казались стопроцентно отвечающими нашим требованиям. Сейчас, после достаточно долгого времени роботы с ХМРР, я могу сказать, что я не уверен, что мы сделали правильный выбор. Тем не менее, я не знаю, какими были бы мои чувства, если мы бы пошли другим путем. Конечно сидя вечерами за чашкой чая, я иногда мечтаю вернуться в то время и написать свой закрытый протокол с блек джеком и node.js, но увы. Тут же вспоминаю слова Сократа:
«Женишься ты, или не женишься, ты все равно об этом пожалеешь.»
В момент выбора основные достоинства протокола нам казались следующими:
Использование 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% случаев.
Я работаю в компании 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% случаев.