Відстій 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 цілком успішно застосовується для вирішення величезного кола завдань, для яких він, треба вважати, добре підходить. Хоча більшість рішень використовують тільки базовий функціонал.