Разговариваем с Rails-приложением через XMPP (Jabber)


    Статья расскажет о том, как получать сообщения от вашего Rails-приложения по протоколу XMPP (Jabber) и наоборот, управлять приложением, отправляя ему команды через XMPP.



    Немного теории


    XMPP (Extensible Messaging and Presence Protocol), ранее известный как Jabber — основанный на XML, открытый, свободный для использования протокол для мгновенного обмена сообщениями в режиме, близком к режиму реального времени. Изначально спроектированный легко расширяемым, протокол, помимо передачи текстовых сообщений, поддерживает передачу голоса, видео и файлов по сети.

    В отличие от коммерческих систем мгновенного обмена сообщениями, таких, как AIM, ICQ, WLM и Yahoo, XMPP является децентрализованной, расширяемой и открытой системой. Любой желающий может открыть свой сервер мгновенного обмена сообщениями, регистрировать на нем пользователей и взаимодействовать с другими серверами XMPP. На основе протокола XMPP уже открыто множество частных и корпоративных серверов XMPP. Среди них есть достаточно крупные проекты, такие как Facebook, Google Talk, Яндекс, ВКонтакте и LiveJournal.

    Если у вас еще нет джаббер-аккаунта (буду называть XMPP джаббером, по-старинке), его можно быстро завести на Jabber.ru или воспользоваться Google Talk. С клиентом для джаббера проблем не будет, подойдет практически любой «асечный», например, Miranda или QIP.

    Для чего это нужно?


    Я использую отправку сообщений:
    — Для поиска битых ссылок. Когда пользователь на моем сайте попадает на 404-страницу, мне приходит сообщение с адресом этой страницы и адресом страницы, с которой был этот переход.
    — Для мгновенного получения информации о заказе товара, оплате и т.д.
    — Для получения сообщений от пользователей, которые не требуют ответа. Например, при заполнении «формы пожеланий».

    Я отдаю команды Rails-приложению:
    — На перезагрузку приложения. Это гораздо быстрее, чем лезть на сервер по ssh и перезагружать рельсы длиннющей командой.
    — На добавление/удаление строки в базе данных. Мне было лень писать полноценный интерфейс, для работы с БД, которая очень редко нуждается в добавлении/удалении нескольких записей.

    Ну а вы можете использовать связку Rails+XMPP для этих и любых более смелых задач. Например, заливки файлов и другого контента на сервер, чаты с пользователями и т.д.

    Что нам потребуется?


    Потребуется нам гем XMPP4R. Устанавливаем:
    gem install xmpp4r

    Сразу замечу, что гем очень нестабильно ведет себя под windows, поэтому лучше работать с ним на линуксе.

    Далее, нам нужен джаббер-аккаунт для нашего Rails-приложения, т.е. адрес, с которого оно будет отправлять вам сообщения и принимать от вас команды. Регистрируем аккаунт.

    Отправка сообщений


    Подключение и аутентификация робота (буду так называть часть нашего Rails-приложения, ответственную за общение).
    require "xmpp4r"
    
    robot = Jabber::Client::new(Jabber::JID::new("robot@xmpp.ru"))
    robot.connect
    robot.auth("password")

    Думаю, тут все понятно, robot@xmpp.ru — адрес джаббер-аккаунта, который вы зарегистрировали для приложения, password — пароль к нему. Теперь отправляем сообщение.

    message = Jabber::Message::new("you@xmpp.ru", "Хелло, ворлд!")
    message.set_type(:chat)
        
    robot.send message

    Где you@xmpp.ru — ваш джаббер-адрес. Вот и все. Сообщение уже всплыло из вашего трея.

    Обратная связь


    Теперь самое интересное — мы будем отправлять роботу команды (через джаббер-клиент). Для этого сперва подключим робота и заставим его заявить о своем присутствии и готовности общаться. Т.е. робот «будет онлайн», зеленая ламочка в джаббер-клиенте будет гореть.
    robot = Jabber::Client::new(Jabber::JID::new("robot@xmpp.ru"))
    robot.connect
    robot.auth("password")
    robot.send(Jabber::Presence.new.set_show(nil))

    Теперь, включим цикл ожидания входящих сообщений. При этом будем проверять, что сообщения приходят с правильного адреса, т.е. роботом не пытается командовать некий злоумышленник.
    robot.add_message_callback do |message| #ожидание сообщения
      if message.from.to_s.scan("you@xmpp.ru").count > 0 #сообщение с правильного адреса
      case message.body #перебираем варианты команд для робота
      when "hello"
        message = Jabber::Message::new("you@xmpp.ru", "И тебе привет!")
        message.set_type(:chat)
        robot.send message
      when "restart"
        File.open(Rails.root.to_s + "/tmp/restart.txt", "a+")
      end
      else #сообщение с чужого адреса
        message = Jabber::Message::new("you@xmpp.ru", "Ко мне ломится незнакомец!")
        message.set_type(:chat)
        robot.send message
      end
    end
    

    Получив сообщение «hello», робот ответит нам приветом, а получив «restart» — перезагрузит Rails-приложение (если оно работает через Passenger).

    Финальный класс


    В предыдущем примере, робот получил от нас команду перезагрузить приложение и… умер. Его нужно снова оживлять, запуская соответствующий скрипт. Делать это каждый раз после перезагрузки приложения конечно же не хочется. Поэтому, предлагаю запускать робота вместе с приложением. Я написал демон, включающий класс, который упростит работу с XMPP, а также, позволит роботу жить, невзирая на перезагрузки.

    Этот класс мы оформим в отдельный руби-файл, например «xmpp_daemon.rb», и положим в папку «config/initializers», что позволит ему запускаться вместе с рельсами.
    #coding: utf-8
    
    require "xmpp4r"
    
    class Xmpp
      
      #параметры подключения
      @@my_xmpp_address =      "you@xmpp.ru"     #ваш джаббер-адрес
      @@robot_xmpp_address =   "robot@xmpp.ru"   #джаббер-адрес робота
      @@robot_xmpp_password =  "password"        #пароль от джаббер-аккаунта робота
      @@site_name =            "sitename.ru"     #имя сайта, для идентификации источника сообщений
      
      #подключение и аутентификация на xmpp-сервере
      def self.connect
        @@robot = Jabber::Client::new(Jabber::JID::new(@@robot_xmpp_address))
        @@robot.connect
        @@robot.auth(@@robot_xmpp_password)
      end
      
      #отправка сообщения
      def self.message(text)
        self.connect
        message = Jabber::Message::new(@@my_xmpp_address, "[#{@@site_name}]\n#{text}")
        message.set_type(:chat)
        @@robot.send message
      end
      
      #прием сообщений
      def self.listen
        self.connect
        @@robot.send(Jabber::Presence.new.set_show(nil))
        @@robot.add_message_callback do |message| #ожидание сообщения
          if message.from.to_s.scan(@@my_xmpp_address).count > 0 #сообщение с правильного адреса
            case message.body #перебираем варианты команд для робота
            when "hello"
              Xmpp.message "И тебе привет!"
            when "restart"
              Xmpp.message "Перезагрузка..."
              File.open(Rails.root.to_s + "/tmp/restart.txt", "a+")
            end
          else #сообщение с чужого адреса
            Xmpp.message "Ко мне ломится незнакомец!"
          end
        end
      end
    end
    
    Xmpp.listen
    

    Теперь, из любого контроллера Rails-приложения мы легко можем отправить сообщение.
    Xmpp.message "Хелло, ворлд!"
    

    Спасибо за внимание, надеюсь, это кому-то пригодится.

    МанагеруЗакгоЭнспе
    Поделиться публикацией

    Похожие публикации

    Комментарии 26

    • НЛО прилетело и опубликовало эту надпись здесь
        +4
        зачем 10-20 строк, если можно обойтись тремя?
          –3
          >гем очень нестабильно ведет себя под windows
          Иногда 10-20 строк стабильнее.
            +8
            Тут проблема в слове windows, а не в выборе технологии
              –2
              Если тесты руби отрабатывают на windows без ошибок, а кривонаписанный xmpp4r падает, то виноват windows?
              0
              Но обычно — нет
            • НЛО прилетело и опубликовало эту надпись здесь
              +4
                +1
                Интересно посмотреть на ваше решение, кстати. На чистом tcp.
                • НЛО прилетело и опубликовало эту надпись здесь
                    +2
                    а где получение входящих сообщений? и обработка фейлов?
                    • НЛО прилетело и опубликовало эту надпись здесь
                +3
                Вообще очень удачное применение. Мы юзаем для этих же целей email, но jabber конечно выигрывает в опперативности :)
                  +2
                  Мне не приходило в голову управлять сервером через джаббер бота ). А ведь хорошая идея для простых задачек.
                    0
                    Советую глянуть в сторону xmpp4r-simple для большей простоты (гем поверх xmpp4r). С ним код превращается в нечто подобное:

                    # Send a message to a friend, asking for authorization if necessary:
                    im = Jabber::Simple.new("user@example.com", "password")
                    im.deliver("friend@example.com", "Hey there friend!")

                    # Get received messages and print them out to the console:
                    im.received_messages { |msg| puts msg.body if msg.type == :chat }
                      0
                      Увы, с Rails 3 у меня этот гем работать не захотел.
                        0
                        А из-за чего он отказывался работать?
                          0
                          … xmpp4r-simple-0.8.8/lib/xmpp4r-simple.rb:441: syntax error, unexpected ':', expecting keyword_then or ',' or ';' or '\n' (SyntaxError)

                          И да, поправка, вроде не третьи рельсы виноваты, а руби 1.9.2. Гугл говорит, что на 1.8.7 все нормально.
                            0
                            Отлично, пока не переезжаю на 1.9 :)
                              +1
                              Ну и зря =) Все остальное-то нормально работает. Просто гем этот не обновлялся уж очень давно.
                                0
                                У меня видимо просто мешают ассоциации такой нумерации с linux kernel 2.3 / 2.5 :)
                      0
                      Андрей, а это у вас на продакшене такое работает?
                        +1
                        Ну да.
                          0
                          Подскажите пожалуйста, в чем может быть проблема — вставил ваш код, бот конектится, обрабатывает первое сообщение, а на остальные входящие сообщения потом не реагирует. Сам сообщения отправляет нормально. Куда можно капнуть?
                            0
                            *копнуть, позор мне ))
                              0
                              Лечится так. Стоит вписать Thread.stop в главный поток, после того, как установите все свои callback'и и прочий инициализирующий стафф.

                              Очень полезный пост на эту тему (англ.)

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое