Ruby
Rails
Node.js
Elixir
Phoenix

WebSocket and Web Application Framework

More than 3 years have passed since last update.

WebSocket はネットワークの双方向通信の規格で、リアルタイム通信の用途で利用されています。

Web Application FrameworkPhoenixWebSocket を標準でサポートしています。

RailsAction CableWebSocket の利用がサポートされるようになります。

そこで Express を合わせて3つの FrameworkWebSocket のサンプルを作成しました。

( bash コマンドの brewMac OS X で利用できます。他OSは適宜のコマンドを利用してください。 )



Node.js on Express

logo-express

Express は JavaScript が言語の Web Application Framework です。

Socket.IO という WebSocket のライブラリが有名です。


Setup



  • Node.js をインストールすると npm のコマンドが利用できます。


  • npm コマンドで ExpressGenerator をインストールします。

$ 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 = ioSocket を設定します。


    • 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

express

サンプルコード

これだけで簡単なチャットアプリケーションができます。



Elixir on Phoenix

logo-phoenix

PhoenixElixir が言語の 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 コマンドで MessageGenerator を実行します。

$ mix phoenix.new phoenix_channel_example

$ cd phoenix_channel_example
$ mix ecto.create
$ mix phoenix.gen.html Message messages content:text



  • RouterMessage を追加します。(よく忘れます。)

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

phoenix

サンプルコード

手順は増えましたが、高度なチャットアプリケーションが簡単にできます。



Ruby on Rails

logo-rails

RailsRuby が言語の Web Application Framework です。

WebSocketActionCable の機能で利用することができます。

Ruby はもちろん、外部DSLが簡単に実装できます。


Setup



  • Ruby をインストールすると gem のコマンドが利用できます。

  • 今回は gem コマンドで Rails をインストールしません。


  • gem コマンドで Bundler をインストールします。

$ brew install ruby

$ gem install bundler


  • 最新バージョンの Rails をインストールします。


  • rails コマンドで Web Application を作成します。


  • --edge オプションを指定すると GemfileGitHub を参照します。

$ 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



  • ActionCableRedis を利用します。

$ brew install redis



  • Rails で利用するライブラリを Gemfile に記述します。

Gemfile:

gem 'actioncable', github: 'rails/actioncable'

gem 'puma'
gem 'foreman', require: false



  • rails generate コマンドで MessageGenerator を実行します。

$ 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 /messagesserver.broadcast でメッセージが送信されます。

app/controllers/messages_controller.rb:

def create

ActionCable.server.broadcast 'messages', message: params[:message][:content]
head :ok
end



  • ActionCableRack の設定を記述します。

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



  • RailsActionCable の実行コマンドを記述します。

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_forremote: 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

rails

サンプルコード

かなり手順は増えましたが、このチャットアプリケーションはスケールアウトに対応しています。


Tips


ActionCable with ActiveJob

Node.jsElixir は非同期処理のメカニズムがあり、多くのリクエストを処理するのに向いています。

Ruby で非同期処理を実装するには EventMachine のライブラリを利用するのですが、さらに Rails では ActiveJob のライブラリを利用したり、 RedisPub/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