Статьи

Как использовать Faye как Push-сервер в реальном времени в Rails

Есть несколько способов добавить push-функциональность в приложение, включая Pushr и Pub-Nub , которые являются довольно элегантными и простыми решениями. Есть также несколько более продвинутых опций. В этой статье я собираюсь показать вам, как использовать Faye , систему обмена сообщениями, которая работает как на Ruby, так и на Node.js.


Мы собираемся создать простой чат-сервис. Теперь Райан Бейтс рассказал об этом на Railscast # 260 , однако в этом уроке мы будем придерживаться немного другого подхода. Сначала мы создадим службу чата, в которой пользователи заходят в общую комнату, и все могут общаться друг с другом публично. Вторая функциональность, которую мы добавим, это личные сообщения. Кроме того, мы интегрируем некоторую безопасность в нашу реализацию, используя гем private_pub Райана Бэйта .

Убедитесь, что у вас есть рабочая установка Ruby и Ruby on Rails 3.1. Кроме того, вам понадобится худой. Thin — широко используемый веб-сервер Ruby, и Фэй требует, чтобы он работал (он не работает с WEBrick, встроенным сервером Rails). Вы можете установить Thin, вот так:

1
gem install thin

У нас все должно быть готово, поэтому давайте создадим приложение:

1
rails new faye-tutorial

Теперь добавьте драгоценный камень в ваш Gemfile :

1
gem ‘faye’

и запустите bundle install чтобы установить его. Фэй должен работать на отдельном веб-сервере от самого веб-приложения; Для этого вам нужно создать конфигурационный файл Rackup. Добавьте файл faye.ru в корневой каталог проекта и убедитесь, что он выглядит так:

1
2
3
4
require ‘faye’
 
bayeux = Faye::RackAdapter.new(:mount => ‘/faye’, :timeout => 25)
bayeux.listen(9292)

Этот файл просто говорит Rackup, как запустить сервер Faye. Попробуйте, чтобы убедиться, что он работает правильно. Запустите это в вашем терминале:

1
rackup faye.ru -E production -s thin

Если вы не получили никаких ошибок, тогда вы готовы!


Чтобы создать приложение чата, нам нужны две основные вещи: пользователи и чат. Мы не собираемся проверять подлинность наших пользователей, но нам все равно нужно предоставить им способ установить их имя пользователя. Давайте создадим это. Выполните следующую команду, чтобы создать controller сессий, чтобы мы могли войти в них:

1
rails g controller sessions new create

Это создаст controller сессий и два метода: new и create . Добавьте эти маршруты в ваш файл routes.rb :

1
2
get ‘/login’ => ‘sessions#new’, :as => :login
post ‘/login’ => ‘sessions#create’, :as => :login

Эти два маршрута должны быть понятны для вас. Идите дальше и измените файл app/views/sessions/new.html.erb :

1
2
3
4
5
<%= form_tag login_path do |f|
  <%= label_tag :username %>
  <%= text_field_tag :username %>
  <%= submit_tag «Enter» %>
<% end %>

И create метод create внутри controller сессий выглядит следующим образом:

1
2
3
4
def create
  session[:username] = params[:username]
  render :text => «Welcome #{session[:username]}!»
end

Давай, попробуй! Запустите rails server в Терминале и укажите в браузере localhost: 3000 / login и введите имя пользователя. После того, как вы отправите форму, вас должно приветствовать ваше заявление.

Если вы готовы принять вызов, вы можете использовать Omniauth, чтобы добавить интеграцию в Twitter или Facebook для этого шага!


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

1
rails generate controller chats room

Это сгенерирует наш controller чатов и метод одной room . Нам нужно добавить несколько маршрутов, чтобы наш чат работал, поэтому добавьте эту строку в ваш файл routes.rb :

1
get ‘/chatroom’ => ‘chats#room’, :as => :chat

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

1
2
3
def room
  redirect_to login_path unless session[:username]
end

Это гарантирует, что пользователи установят имя пользователя, если они хотят общаться. Теперь давайте создадим саму комнату! Добавьте это в app/views/chats/room.html.erb :

01
02
03
04
05
06
07
08
09
10
<div class=»chat_container»>
  <div id=»chat_room»>
    <p class=»alert»> Welcome to the chat room <%= session[:username] %>!
  </div>
 
  <form id=»new_message_form»>
    <input type=»text» id=»message» name=»message»>
    <input type=»submit» value=»Send»>
  </form>
</div>

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

Теперь, чтобы отправлять сообщения в комнату, нам нужно добавить немного JavaScript в наше представление. Сначала добавьте библиотеку Фая в app/views/layouts/application.html.erb :

1
<%= javascript_include_tag «http://localhost:9292/faye.js» %>

Затем добавьте следующее в начало представления room.html.erb :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<script>
  $(function() {
    // Create a new client to connect to Faye
    var client = new Faye.Client(‘http://localhost:9292/faye’);
 
    // Handle form submissions and post messages to faye
    $(‘#new_message_form’).submit(function(){
      // Publish the message to the public channel
      client.publish(‘/messages/public’, {
        username: ‘<%= session[:username] %>’,
        msg: $(‘#message’).val()
      });
 
      // Clear the message box
      $(‘#message’).val(»);
 
      // Don’t actually submit the form, otherwise the page will refresh.
      return false;
    });
  });
</script>

Этот метод принимает сообщение в форме, которую мы добавили ранее (когда оно отправлено), и отправляет имя пользователя и сообщение автора в канал «/ messages / public» в объекте JSON. Каналы — это способ отправки сообщений Фэй. Пользователь подписывается на канал и получает все сообщения, отправленные на него. В нашем случае есть только один канал — публичный. Давайте разберем код немного больше:

  1. Сначала мы создаем новый клиент Faye и подключаем его к нашему серверу Faye.
  2. Далее мы обрабатываем отправку формы. Когда пользователь нажимает клавишу ввода или нажимает «Отправить», мы опубликуем вышеупомянутый объект JSON, содержащий отправителя сообщения и само сообщение, в общедоступном канале.
  3. После этого мы очищаем окно сообщения и return false , чтобы форма не отправлялась и не обновляла страницу.

Теперь это будет публиковать сообщения в чате, но наши подключенные пользователи не смогут их получать, потому что их браузеры не подписаны на канал. Это достигается за счет немного больше JavaScript. Сделайте так, чтобы блок JavaScript в app/views/chats/room.html.erb выглядел так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<script>
  $(function() {
    // Create a new client to connect to Faye
    var client = new Faye.Client(‘http://localhost:9292/faye’);
 
    // Subscribe to the public channel
    var public_subscription = client.subscribe(‘/messages/public’, function(data) {
      $(‘<p></p>’).html(data.username + «: » + data.msg).appendTo(‘#chat_room’);
    });
 
    // Handle form submission to publish messages.
    $(‘#new_message_form’).submit(function(){
      // …
      // Leave this part as it is
      // …
    });
  });
</script>

Он просто подключается к серверу Фэй и подписывается на канал «/ messages / public». Обратный звонок, который мы предоставляем, получает отправленные сообщения. data будет объектом JSON, который мы опубликовали ранее, поэтому мы просто используем его для создания

пометьте сообщение внутри и добавьте его в контейнер чата.

Теперь у вас должен быть простой чат! Запустите Faye и сервер Rails и откройте два браузера (или окно Incognito в Chrome, например). Вы можете ввести два разных имени пользователя и проверить свой чат. Сообщения должны появляться практически мгновенно в чате после их отправки.


Прямо сейчас пользователи могут общаться друг с другом, но все сообщения общедоступны. Давайте добавим несколько простых функций, где люди могут отправлять кому-то личные сообщения, упоминая имя пользователя получателя — вроде как в Twitter. Чтобы это работало, мы собираемся подписать наших пользователей на их собственные каналы, так что они единственные, кто может получать сообщения от него. Сделайте ваше app/views/chats/room.html.erb JavaScript похожим на это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<script>
  $(function() {
    // Subscribe to receive messages!
    var client = new Faye.Client(‘http://localhost:9292/faye’);
 
    // Our public subscription
    var public_subscription = client.subscribe(‘/messages/public’, function(data) {
      $(‘<p></p>’).html(data.username + «: » + data.msg).appendTo(‘#chat_room’);
    });
 
    // Our own private channel
    var private_subscription = client.subscribe(‘/messages/private/<%= session[:username] %>’, function(data) {
      $(‘<p></p>’).addClass(‘private’).html(data.username + «: » + data.msg).appendTo(‘#chat_room’);
    });
 
    // Handle form submission to publish messages.
    $(‘#new_message_form’).submit(function(){
      // Is it a private message?
      if (matches = $(‘#message’).val().match(/@(.+) (.+)/)) {
        client.publish(‘/messages/private/’ + matches[1], {
          username: ‘<%= session[:username] %>’,
          msg: matches[2]
        });
      }
      else {
        // It’s a public message
        client.publish(‘/messages/public’, {
          username: ‘<%= session[:username] %>’,
          msg: $(‘#message’).val()
        });
      }
 
      // Clear the message box
      $(‘#message’).val(»);
 
      return false;
    });
  });
</script>

Как видите, мы подписываемся на два канала Faye: один является публичным каналом, а второй — каналом, называемым «/ messages / private / USERNAME» (обратите внимание, что мы используем имя пользователя в сеансе Rails). Таким образом, когда кто-то упоминает об этом пользователе, вместо отправки сообщения по общедоступному каналу, мы отправляем его по частному каналу получателя, так что только этот человек может его прочитать. Мы также добавили несколько простых стилей, чтобы они отображались жирным шрифтом.

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

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


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

Второе предостережение не так очевидно. Проблема с нашей реализацией заключается в том, что любой может на лету манипулировать JavaScript (например, с помощью Firebug), чтобы подписаться на любой канал, который он пожелает (даже на частные каналы), и он может публиковать сообщения, притворяясь кем-то другим. Это не так легко решить, как первый недостаток, который я указал (если бы мы решили это вручную), но Райан Бейтс создал драгоценный камень, который делает эту задачу трудной, и наше приложение очень безопасным.

Драгоценный камень называется private_pub; он по существу запрещает любому пользователю публиковать на каналы с JavaScript, то есть только приложение Rails может публиковать на них. Это добавляет некоторую безопасность, поскольку злоумышленник не сможет публиковать на частных каналах. Еще одна вещь, которую решает этот драгоценный камень, это подписка. При использовании private_pub пользователь может получать сообщения только с тех каналов, на которые они подписаны, поэтому он не может добавить подписку вручную, что решает всю проблему.

Итак, давайте добавим его в наш Gemfile :

1
gem ‘private_pub’, :git => ‘git://github.com/ryanb/private_pub.git’

запустите bundle install чтобы установить гем, и запустите генератор, чтобы создать файлы конфигурации:

1
rails g private_pub:install

При запуске этой команды вы получите предупреждение о конфликте (private_pub пытается перезаписать файл faye.ru ). Просто введите «Y» и нажмите Enter, так как необходимо перезаписать этот файл. Вам также необходимо переместить файл public/private_pub.js папку app/assets/javascripts . И последнее: удалите строку, которая включает faye.js в application.html.erb , поскольку Private Pub включает ее автоматически. Убедитесь, что вы перезапустите оба сервера (rails и faye) на этом этапе.

Теперь нам нужно внести некоторые изменения. Во-первых, подписка пользователя на канал выполняется по-другому с private_pub. Отредактируйте app/views/chats/room.html.erb и добавьте следующее перед блоком JavaScript:

1
2
<%= subscribe_to «/messages/public» %>
<%= subscribe_to «/messages/private/#{session[:username]}» %>

Это private_pub способ подписки на каналы. Это позволяет пользователю получать сообщения через указанный вами канал. Теперь нам нужно изменить код JavaScript на следующий:

PrivatePub.subscribe («/ messages / public», функция (данные) {
$ (‘<p> </ p>’). html (data.username + «:» + data.msg) .appendTo (‘# chat_room’);
});

PrivatePub.subscribe («/ messages / private / <% = сессия [: имя пользователя]%>», функция (данные) {
$ (‘<p> </ p>’). addClass (‘private’). html (data.username + «:» + data.msg) .appendTo (‘# chat_room’);
});

Единственная разница здесь заключается в том, что мы используем PrivatePub для подписки на каналы вместо библиотеки Faye напрямую.

Нам также потребуется изменить способ публикации сообщений. При использовании Private Pub, только приложение Rails может публиковать сообщения. Библиотека Javascript не может публиковать сообщения самостоятельно. Это хорошо, так как мы полностью контролируем, кто публикует сообщения и на каком канале. Для этого нам нужно изменить форму, используемую для отправки сообщений, следующим образом:

1
2
3
4
<%= form_tag new_message_path, :remote => true do %>
  <%= text_field_tag :message %>
  <%= submit_tag «Send» %>
<% end %>

Это форма AJAX, поэтому она не обновляет страницу при отправке. Он также будет искать new_message_path , поэтому убедитесь, что вы добавили это в routes.rb :

1
post ‘/new_message’ => ‘chats#new_message’, :as => :new_message

Вам также понадобится создать новый метод на контроллере чатов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
def new_message
  # Check if the message is private
  if recipient = params[:message].match(/@(.+) (.+)/)
    # It is private, send it to the recipient’s private channel
    @channel = «/messages/private/#{recipient.captures.first}»
    @message = { :username => session[:username], :msg => recipient.captures.second }
  else
    # It’s public, so send it to the public channel
    @channel = «/messages/public»
    @message = { :username => session[:username], :msg => params[:message] }
  end
 
  respond_to do |f|
    f.js
  end
end

Это работает очень похоже на его аналог JavaScript. Он определяет, содержит ли сообщение упоминание, и, если это так, отправляет сообщение в частный канал получателя. Если нет, он отправляет сообщение через общедоступный канал. Теперь, на самом деле это не отправка сообщения, а просто создание двух переменных, которые нам нужно использовать в представлении, чтобы отправить одну. Private Pub не позволяет вам отправлять сообщения через controller (по крайней мере, не с версией, которую я использовал для этого урока), поэтому создайте файл app/views/chats/new_message.js.erb и добавьте следующее:

1
2
3
4
5
// Clear message input
$(‘#message’).val(»);
 
// Send the message
<% publish_to @channel, @message %>

Это выполнит первую строку кода, очистив окно сообщения, и, вызвав publish_to , Private Pub отправит @message (который будет преобразован в объект JSON при поступлении) в @channel . Просто, да?

Давай, попробуй. Он должен работать так же, как и раньше, только с новой дополнительной безопасностью!


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

Если у вас есть какие-либо вопросы, не стесняйтесь задавать их в комментариях или отправьте мне твит !