WebSocket はネットワークの双方向通信の規格で、リアルタイム通信の用途で利用されています。
Web Application Framework の Phoenix は WebSocket を標準でサポートしています。
Rails も Action Cable で WebSocket の利用がサポートされるようになります。
そこで Express を合わせて3つの Framework で WebSocket のサンプルを作成しました。
( bash コマンドの brew
は Mac OS X で利用できます。他OSは適宜のコマンドを利用してください。 )
Node.js on Express
Express は JavaScript が言語の Web Application Framework です。
Socket.IO という WebSocket のライブラリが有名です。
Setup
-
Node.js をインストールすると
npm
のコマンドが利用できます。 -
npm
コマンドで Express の Generator をインストールします。
$ brew install node
$ npm install express-generator -g
-
express
コマンドで Web Application を作成します。 -
npm
コマンドで Socket.IO のパッケージをインストールします。
$ express express-socketio-example
$ cd express-socketio-example && npm install
$ npm install socket.io --save
Making
Socket.IO の実装はこちらが参考になります。
Server
サーバーサイドに WebSocket の接続処理を記述します。
-
Express の実行ファイルの最後に Socket.IO の設定を追加します。
-
io.on
は接続の処理です。 -
io.emit
でメッセージが送信されます。 -
socket.on
でメッセージが受信されます。
-
bin/www
:
var io = require('socket.io')(server);
io.on('connection', function(socket){
socket.on('message from client', function(message){
io.emit('message from server', message);
});
});
Client
フロントエンドにも WebSocket の接続処理を記述します。
-
View のファイルに Socket.IO の設定を追加します。
-
socket = io
で Socket を設定します。 -
socket.emit
でメッセージが送信されます。 -
socket.on
でメッセージが受信されます。
-
- DOM の操作は JQuery を利用します。
- メッセージは
<div id="messages"></div>
に出力します。
views/index.jade
:
extends layout
block content
form
input#content
button Send
#messages
script(src='/socket.io/socket.io.js')
script(src='http://code.jquery.com/jquery-1.11.3.min.js')
script().
var socket = io();
$('form').submit(function(){
socket.emit('message from client', $('#content').val());
$('#content').val('');
return false;
});
socket.on('message from server', function(message){
$('#messages').append($('<p>').text(message));
});
Start
- アプリケーションを起動します。
$ DEBUG=express-socketio-example:* npm start
これだけで簡単なチャットアプリケーションができます。
Elixir on Phoenix
Phoenix は Elixir が言語の Web Application Framework です。
WebSocket が標準の機能で利用することができます。
Elixir は Erlang VM で実行されます。外部DSLが簡単に実装できます。
Setup
-
Elixir をインストールすると
mix
のコマンドが利用できます。 -
mix
コマンドで Phoenix をインストールします。 -
mix archive.install
はここから [URL] を取得します。
$ brew install elixir
$ mix local.hex
$ mix archive.install https://[URL]/phoenix_new-x.x.x.ez
- デフォルトのデータベースに PostgreSQL が利用されます。
$ brew install postgresql
$ createuser -d -P postgres
$ postgres -D /usr/local/var/postgres
-
mix phoenix.new
コマンドで Web Application を作成します。 -
mix phoenix.gen.html
コマンドで Message の Generator を実行します。
$ mix phoenix.new phoenix_channel_example
$ cd phoenix_channel_example
$ mix ecto.create
$ mix phoenix.gen.html Message messages content:text
- Router に Message を追加します。(よく忘れます。)
web/router.ex
:
resources "/messages", MessageController
Making
Channels の実装はこちらが参考になります。
Server
- Channels のルーティングを設定します。
web/channels/user_socket.ex
:
channel "rooms:*", PhoenixChannelExample.RoomChannel
-
Channels の設定します。
-
join
でチャンネルを設定します。 -
handle_out
でメッセージが送信されます。 -
handle_in
でメッセージが受信されます。
-
web/channels/room_channel.ex
:
defmodule PhoenixChannelExample.RoomChannel do
use Phoenix.Channel
def join("rooms:lobby", auth_msg, socket) do
{:ok, socket}
end
def join("rooms:" <> _private_room_id, _auth_msg, socket) do
{:error, %{reason: "unauthorized"}}
end
def handle_in("new_msg", %{"body" => body}, socket) do
broadcast! socket, "new_msg", %{body: body}
{:noreply, socket}
end
def handle_out("new_msg", payload, socket) do
push socket, "new_msg", payload
{:noreply, socket}
end
end
Client
-
Channels の設定します。
-
chan = socket.channel
でチャンネルを設定します。 -
chan.push
でメッセージが送信されます。 -
chan.on
でメッセージが受信されます。
-
web/static/js/app.js
:
import {Socket} from "deps/phoenix/web/static/js/phoenix"
let message_content = $("#message_content")
let messagesContainer = $("#messages")
let socket = new Socket("/socket")
socket.connect()
let chan = socket.channel("rooms:lobby", {})
message_content.on("keypress", event => {
if(event.keyCode === 13){
chan.push("new_msg", {body: message_content.val()})
message_content.val("")
return false
}
})
chan.on("new_msg", payload => {
messagesContainer.append(`<br/>${payload.body}`)
})
chan.join().receive("ok", chan => {
console.log("Welcome to Phoenix Chat!")
})
- DOM の操作は JQuery を利用します。
web/templates/layout/app.html.eex
:
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
- メッセージは
<div id="messages"></div>
に出力します。
web/templates/message/new.html.eex
:
<div id="messages"></div>
Run
- アプリケーションを起動します。
$ mix ecto.migrate
$ mix phoenix.server
手順は増えましたが、高度なチャットアプリケーションが簡単にできます。
Ruby on Rails
Rails は Ruby が言語の Web Application Framework です。
WebSocket は ActionCable の機能で利用することができます。
Ruby はもちろん、外部DSLが簡単に実装できます。
Setup
-
Ruby をインストールすると
gem
のコマンドが利用できます。 - 今回は
gem
コマンドで Rails をインストールしません。 -
gem
コマンドで Bundler をインストールします。
$ brew install ruby
$ gem install bundler
- 最新バージョンの Rails をインストールします。
-
rails
コマンドで Web Application を作成します。 -
--edge
オプションを指定すると Gemfile が GitHub を参照します。
$ git clone https://github.com/rails/rails.git
$ cd rails
$ bundle install
$ cd ..
$ rails/railties/exe/rails new rails-actioncable-example --edge
$ cd rails-actioncable-example
- ActionCable で Redis を利用します。
$ brew install redis
-
Rails で利用するライブラリを
Gemfile
に記述します。
Gemfile
:
gem 'actioncable', github: 'rails/actioncable'
gem 'puma'
gem 'foreman', require: false
-
rails generate
コマンドで Message の Generator を実行します。
$ bundle
$ bundle exec rails generate scaffold Message content:text
Making
ActionCable の実装はこちらが参考になります。
Server
- 接続のスーパークラスを記述します。(ジェネレーターまだかなぁ)
app/channels/application_cable/connection.rb
:
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end
- チャンネルのスーパークラスを記述します。(ジェネレーターまだかなぁ)
app/channels/application_cable/channel.rb
:
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
- チャンネルのSubscribe(購読)を設定します。
app/channels/messages_channel.rb
:
class MessagesChannel < ApplicationCable::Channel
def subscribed
stream_from "messages"
end
end
-
POST /messages
のserver.broadcast
でメッセージが送信されます。
app/controllers/messages_controller.rb
:
def create
ActionCable.server.broadcast 'messages', message: params[:message][:content]
head :ok
end
- ActionCable の Rack の設定を記述します。
config/racks/cable.ru
:
require ::File.expand_path('../../environment', __FILE__)
Rails.application.eager_load!
require "action_cable/process/logging"
run ActionCable.server
- ActionCable 設定を yml 記述します。
config/redis/cable.yml
:
production: &base
:url: redis://localhost:6379
:host: localhost
:port: 6379
:timeout: 1
:inline: true
development: *base
test: *base
- Rails と ActionCable の実行コマンドを記述します。
Procfile
:
web: bundle exec rails server
cable: bundle exec puma -p 28080 config/racks/cable.ru
Client
-
App.cable = Cable.createConsumer
でチャンネルを設定ます。
app/assets/javascripts/channels/index.coffee
:
#= require cable
#= require_self
#= require_tree .
@App = {}
App.cable = Cable.createConsumer "ws://localhost:28080"
-
received
でメッセージが受信されます。 - DOM の操作は JQuery を利用します。
app/assets/javascripts/messages.coffee
:
App.messages = App.cable.subscriptions.create 'MessagesChannel',
received: (data) ->
$('#messages').append($('<p>').text(data.message))
-
form_for
のremote: true
でページリロードを抑制します。 -
Submit
でメッセージが送信されます。
app/views/messages/_form.html.erb
:
<%= form_for(message, remote: true) do |f| %>
- メッセージは
<div id="messages"></div>
に出力します。
app/views/messages/new.html.erb
:
<div id='messages'></div>
Run
- アプリケーションを起動します。
$ rake db:migrate
$ bundle exec foreman start
かなり手順は増えましたが、このチャットアプリケーションはスケールアウトに対応しています。
Tips
ActionCable with ActiveJob
Node.js や Elixir は非同期処理のメカニズムがあり、多くのリクエストを処理するのに向いています。
Ruby で非同期処理を実装するには EventMachine のライブラリを利用するのですが、さらに Rails では ActiveJob のライブラリを利用したり、 Redis の Pub/Sub
を利用して非同期処理を実現しています。
- まず、 sidekiq のライブラリを
Gemfile
に記述します。
Gemfile
:
gem 'sidekiq'
- 次に、メッセージを送信する処理を
Job
に記述します。
app/jobs/message_job.rb
class MessageJob < ApplicationJob
queue_as :default
def perform(*args)
ActionCable.server.broadcast 'messages', message: args[0]
end
end
- さきほどの
POST /messages
に非同期の設定を記述します。
app/controllers/messages_controller.rb
:
def create
MessageJob.perform_later(params[:message][:content])
head :ok
end
Enjoy WebSocket