XMPP отстой

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

— Паша, нам нужно сделать чат.
— Да всё просто, у меня тут знакомые использовали XMPP для чата в своём приложении.

Какие у нас были требования? Да ничего особенного, простой обмен сообщениями между пользователями, без групповых разговоров. Платформы: веб (с поддержкой работы через вебсокеты), Android, iOS. Создание пользователей должно автоматически производится только нашим серверным приложением. Конечно неплохо было бы иметь отметки о том прочитано сообщение или нет (предполагается, что приложение может быть использовано с разных девайсов), и иметь возможность просмотреть лог чата. В общем стандартный функционал для мгновенного обмена сообщениями в 2015 году. Бонусные баллы начисляются если сервер умеет горизонтально масштабироваться.
Хм, звучит заманчиво. Гуглим сравнение с другими стандартами, открываем ссылку на статью в википедии. Первая мысль: ого, круто, тут есть всё что нам нужно.

Почему же мы выбрали именно XMPP?

  • XMPP — это открытый протокол, разрабатываемый XSF (XMPP Standards Foundation), независимой некоммерческой организацией. Эти ребята должно быть довольно умны и предусмотрели всё, что нужно протоколу обмена сообщениями (ага, конечно).
  • Поскольку протокол открытый должно быть довольно много реализаций сервера, и хотя бы одна из них будет достаточно хороша чтобы её использовать.
  • По этой же причине для каждого из языков программирования наверняка найдётся 1–2 библиотеки.
  • Первая буква X в аббревиатуре означает extensible, расширяемый. Даже если что-то нам не понравится в протоколе мы всегда сможем расширить своими методами.

Протокол


Ребята разрабатывавшие протокол действительно многое предусмотрели. Например, взглянем на дизайн архивирования сообщений. Индивидуальные настройки архивирования для сессий. Описание что делать в случае если один из пользователей хочет чтобы его сообщения не сохранялись на сервере, а второй хочет этого. При этом нормального механизма получения архива сообщений по страницам нет. Под нормальным я подразумеваю выставление offset и limit. Здесь вы можете установить только параметр max, который выставит максимальное количество сообщений и параметр start, который означает время, начиная с которого вы будете получать сообщения.

Или вот например: протокол не определяет что должно быть сделано с offline сообщениями (сообщения, посланные пользователю не в сети). Поэтому большинство серверов просто вышлют их пользователю при следующем логине. Помните требование про то, что пользователь может пользоваться приложением с нескольких устройств? Так вот, если вы запустите приложение на смартфоне, а потом войдёте в веб-приложение, то все offline сообщения придут на ваш смартфон и… всё. То есть в веб-приложении вы об этом никак не сможете узнать. Стоит отметить что некоторые сервера ведут себя по-другому, то есть реализуют XEP-0013.

Ах да, протокол для IM (instant messaging) в 2015 году не имеет спецификации, которая позволяла бы получить список непрочитанных сообщений и отметить прочитанными какие-то из них. Совсем.

Реализации


В самом начале я ещё не знал с какими проблемами из предыдущего пункта мне придётся столкнуться и поэтому в качестве реализации сервера был выбран MongooseIM. Масштабируемый, умеет запрещать разрешать регистрацию только с определённых ip, поддерживает вебсокеты. Для веб front-end была выбрана библиотека JSJaC, потому что по сравнению с strophe.js предоставлял более удобное API. Простая страничка из примеров JSJaC подключилась к серверу с пол-пинка и радости моей не было предела. Казалось бы, работа закончена. Ну почти.

И вот тогда я столкнулся с последней озвученной проблемой из предыдущего пункта. Поскольку протокол не подразумевает возможность получить непрочитанные сообщения, да и вообще не говорит о прочитанности/непрочитанности сообщений, эту часть функционала придётся дописывать самому. Будучи не сильным в программировании на erlang-е я принялся искать другие подходящие реализации сервера. Искал я реализации на Java или JavaScript.

Openfire — одна из самых популярных реализаций XMPP на Java. К плюсам можно отнести простоту настройки: всё происходит через веб-интерфейс. Дальше только минусы: требовательность к ресурсам, отсутствие плагина для работы через вебсокеты.

Единственным достойным упоминания из проектов на JavaScript оказался xmpp-ftw. Проект реализует много расширений из XMPP. Однако от его использования пришлось отказаться, поскольку мы бы не смогли использовать существующие клиентские библиотеки. Возможно всё было бы не так хорошо и с ним.

Tigase поначалу казался спасением. Быстрый, масштабируемый сервер на Java, с возможностью написать плагин и довольно просто его подключить. И отсутствием документации. Вместо неё рекомендовалось читать исходный код. Но это ничего, я и так чаще всего так и делаю. Я написал плагин, который помечал новые сообщения непрочитанными. Чтобы регистрировать пользователей пришлось напрямую писать их в базу, поскольку API для администрирования так и не удалось нормально использовать. К счастью Tigase имеет драйвер для подключения к Mongodb. Получение непрочитанных сообщений сделано отдельным методом API приложения (костыль, можно было сделать и плагином внутри Tigase, но отняло бы намного больше времени, потому что для общения с СУБД внутри Tigase используется довольно низкоуровневое API). Подключив всё я проверил — пока сообщение не помечено прочитанным (кстати, сообщения не имеют id в xmpp, поэтому помечать было решено прочитанной всю переписку с конкретным пользователем) оно возвращается в списке непрочитанных. Всё работает. Осталось проверить только случай с offline сообщением. Да, оно не помечалось непрочитанным, потому что плагин который обрабатывает offline сообщения срабатывает раньше, чем архивирование. На форуме Tigase разработчик ответил что нужное мне поведение реализовано в коммерческом проекте Tigase Unified Archive. Гугление по его названию ни к чему не привело, разработчик на форуме сказал что проект пока нестабилен и нет релизной версии. Покопавшись в исходниках сервера нашёл что можно получить нужное поведение выставив всем сообщениям тип chat.

Теперь пройдёмся по реализациям клиентов. Начнём с JavaScript реализаций. Пробовали мы только JSJaC, который в итоге поменяли на strophe.js. Первый имеет более приятное API, но он имеет только базовую функциональность, не поддерживая работу с расширениями, например с архивом. В то же время strophe вместо этого предлагает довольно удобный xml-билдер. Ну что ж, абстрагироваться от внутренностей XMPP не получилось. Кстати, вебсокет-коннектор в JSJaC работает только в Webkit.

Из Java клиентов попробован на данный момент только Smack. В случае посылки некорректного пакета скорее всего будет выброшен NoResponseException, и это всё что вы узнаете.

Вывод


Может быть, моя проблема в том, что я надеялся что кто-то решил часть моих проблем за меня и сделал это хорошо. Знай я всё это сначала — предложил бы написать собственный чат-сервер. Серьёзно. Дело не в том что протокол плох или не подходит для использования для реализации IM с современными требованиями, хотя я до сих пор так считаю. Просто я бы предпочёл иметь свой собственный код и заложить нужные возможности для расширения, чем перелопачивать столько чужого кода и спецификаций. XMPP вполне успешно применяется для решения огромного круга задач, для которых он, надо полагать, хорошо подходит. Хотя большинство решений используют только базовый функционал.

© Habrahabr.ru