真・入門編に関して
Web技術はハイパーテキストの流通に関わる多数のプロトコル,言語,サーバソフトウェア,データベース,仮想マシン,ブラウザ等の集合として定義されます。また、同様の機能を実現するブラウザ,ライブラリ,フレームワークが乱立して開発競争が起きている分野ゆえ、非常に変化が激しく、纏まった情報も少ない状態です。従って、Webアプリケーション開発の入門に取り掛かる前の既存技術のサーベイにおいて、膨大な時間が要求されてしまいます。また、機能を実装する方法が見つかったとしても、よりシンプルな方法がないか不安に感じる事もあるでしょう。
真・入門編では、実運用環境に向けた詳細な実装を徹底的に削ぎ落としたシンプルな実装を示します。少なくとも、どのWebアプリケーションにおいても、抽象的にはこういう動作をしている、ということが分かれば幸いです。まず動く物を作って、概要を掴みたい人に本シリーズをお薦めします。
まえがき
激しく流動するWebという世界
Webは1つの社会です。Webの世界ではサービスのトレンドの変化や技術の仕様変更が頻繁に起きています。従って、数カ月前に使えていた技法が現在では全く使えなくなっていた等ということは普通に有り得ます。筆者の場合、2014年に書かれた、JavaScriptのみ(MongoDB,Express,Node.jsというMEANスタックのA抜き,YEOMANも無しの独自フレームワーク構成)によるSPAの開発を説明したオライリー本の手法が、2年後の2016年においてはMEANスタックやYEOMANの確立と普及により、非常に非効率的な開発手法になっていて、全く使えなくなっていた事がありました。セキュリティもイタチごっこの様相を呈しています。従って、本格的にWebアプリケーションを開発してデプロイする場合、常にサービスのトレンドや技術の動向に関するサーベイを行う姿勢が求められます。
Webアプリケーションを制作するために必要な技術である、Webデザイン,Webプログラミング,データベース,サーバ,ネットワークの各々は専門職が確立され、分担作業が行われている程度に深い分野でもあります。実用するのであれば、不特定多数のユーザが不定期に接続しに来ることになるため、UI/UXに気を遣い、トランザクションやシステムのダウンタイムに関する厳しい要求も満たさなければなりません。従って、Webアプリケーション制作の自由度を上げたいのであれば、デザインやIT全般を理解するつもりで、時間を掛けて各分野の書籍を深くお読みになられることをお勧めします。
書籍購入やWebサイト巡りだけでなく、各地で開催されるオープンな勉強会に足を運んで勉強仲間を作る等も有効です。
http://html5j.org/ html5j
html5jという組織は、自身の定義を「HTML5などのWebプラットフォーム技術を使った「ものづくり」に関わるすべての人々を応援する、非営利・中立のコミュニティ」としており、年に一度、東京都内において、大規模な勉強会であるHTML5 Conferenceを開催しています。それ以外にも、部活動と称して小規模な勉強会を行っているようです。日本全国のWeb技術者が参加するhtml5jメーリングリストも利用できます。
Ruby on Rails (Webフレームワーク)に対する誤解に関して
Ruby on Railsを初めとするWebフレームワークは、少なくともWeb技術の初心者に対しては優しくはないです。決して低水準なWeb技術(HTTP,HTML,JavaScript,CSS等)に関する勉強からの逃げ道などではありません。良く「学問に王道なし」と言いますが、Web技術にも王道はありません。自分の発想に基づいて自由に開発を行いたければ、複数個のWebアプリケーション制作を通して、1つ1つの技術をマスターして積み上げて行く他はないと考えられます。
「Railsはscaffoldというコマンドが使えて、コマンド一発でWebアプリケーションの雛形が作れるから工数が大幅に短縮できる」と言う言説があります。しかし、実際的にはscaffoldはあまり使われていません。最大の理由としては、scaffoldしたとしても、自動生成により非常に読みにくいコードで記述された雛形を、様々な状況に向けて大幅に改変しなければならないため、それなら自力で雛形から作ったほうが遥かに楽であることが挙げられます。
Railsは開発者の作業効率向上のために、開発者に対してWebの低水準な処理を隠蔽します。しかし、その処理結果を予測しながらコーディングするには、やはり低水準なWeb技術であるHTTP,HTML,CSS,JavaScript,jQuery,SQLと、それらの上位に構築されたラッパー言語Ruby,eRuby(ERB),SCSS,CoffeeScriptによる、MVC(Model-View-Controller)モデルに基づいた、低水準なWeb技術の各々の統合方法を理解する必要があります。従って、低水準なWeb技術を一通り理解した上で、それらの技術を抽象化して操作することを要求されるため、それなりの学習コストが必要であると捉えるべきです。これは、Webフレームワーク全般に対しても言えることでしょう。
概要
本記事では、リアルタイムWebアプリケーション制作の入門として、Ruby on Rails 5.0.0.1 (以下、Rails5)上で、WebSocketを用いたリアルタイムWebチャットを作ります。機能の実現においてはRails5の標準的な機能のみを用いて、本当に必要最小限な実装のみを示します。
本記事で作成しているリアルタイムWebチャットのソースコードは、
にて公開しています。
この記事を読み終えた後に、ユースケースを考えて多種多様な機能を追加する事で、実用に適したリアルタイムWebチャットに発展させることも可能です。
前提とする知識
本記事を理解する上で必要とされる知識は下記の通りです。
- HTML
- JavaScript
- CoffeeSctipt
- jQuery
- Ruby
- eRuby(ERB)
- MVC(Model-View-Controller)の関係性
- Rails5のディレクトリの意味
本記事内においては、各技術に関する深堀りは必要ありません。満遍なく概要を掴んでいれば十分ですし、理解が不十分であれば、調べながら理解して行っても大丈夫です。最低限動くリアルタイムWebチャットを作って、リアルタイムWebアプリケーションにおける各技術の統合の仕方を学ぶことが大事です。簡単のため、データベースは一切使いません。CSSによるHTMLの装飾も行いません。
開発環境
本章ではリアルタイムWebチャット作成において使用する開発環境を整備します。バージョンの違いによる問題発生に関しては保障しません。
使用するソフトウェア
- Ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
- Ruby on Rails 5.0.0.1
作業手順
RailsはgemというRubyの追加ライブラリとして提供されています。まず、Rubyをお手元のOSのパッケージ管理システム等からインストールして下さい。
Ubuntuであれば、
apt install ruby
でインストールされます。
Rubyのインストールが終わったら、Railsのgemをインストールします。下記のコマンドを入力してRailsをインストールして下さい。
gem install rails
インストールが正常に行われたか確認するために、
rails -v
で正常にバージョン番号が表示されるか確認して下さい。
設計
本章ではコーディングに先立って、リアルタイムWebチャットという機能を実現するWebアプリケーションの設計を行います。
画面イメージ
本節では、リアルタイムWebチャットにおいてユーザーに対して提示する画面を設計します。今後はこの画面のことをシェルと呼びます。
あるユーザーが、Nameフォームに名前、Messageフォームにメッセージを入力してSubmitボタンを押すとします。そしてその時に、全ユーザーの画面のReceived Messageの下方に、入力された名前とメッセージが表示されるとします。これを繰り返すと、全ユーザーの画面のReceived Messageの下方にはタイムラインが形成されるでしょう。
それでは、User1とUser2の2人が会話している時の画面の例を少しだけ想像してみましょう。
User1の画面の例
User2の画面の例
この後の章において、このような画面の挙動を実現して行きます。
Webアプリケーションアーキテクチャの定義
使用する有名な技術/設計手法に関する前置き
- チャットにおいて送受信されるメッセージの管理に、Pub/Subアーキテクチャを採用します。動作としては下記の通りです。
- メッセージ送信者はチャネルに対してメッセージを投稿します。
- チャネルからメッセージ受信者に対してメッセージが送信されます。
上記の動作により、メッセージ送信者がチャネルのみを意識するだけで済み、他のメッセージ受信者を意識してメッセージを送信する必要がなくなり、設計/実装共に非常に楽になります。また、チャネル毎に隔離されてメッセージが送受信されます。チャネルを複数個に増やすことで、チャットルームを複数設けることも出来ます。メッセージ送受信の流れが非常に簡潔に纏まり、サーバ-クライアントモデルにおいては、Pub/Subアーキテクチャは優れた設計手法となっていると言えます。
今回の想定では下記の図のようになります。
1つのブラウザがPublisherとして1つのチャネルに対してメッセージを送ると、全てのブラウザがSubscriberとしてそのメッセージを受信します。複数のチャネルを設けることも出来ますが、今回は簡単のため行いません。
リアルタイムWebの実現において、Rails5から新たに実装されたActiveCableを使います。これは、WebSocketをラッピングして、利用しやすくまとめたクラスとなります。ブラウザ-チャネル間を繋ぐようにして用います。
リアルタイムWebチャットの本質的な動作を集中して学ぶため、データベースは使いません。
内部構造
本節では、リアルタイムWebチャットを実現するWebアプリケーションの内部構造を定義します。
概要図
ブラウザはルータにWebサイトのルートディレクトリを問い合わせます。すると、ルータがコントローラのindexアクションを呼び、コントローラは対応するビューにシェルを生成させて、シェルをブラウザに返します。シェルは実行された瞬間にチャネルとの接続を開始し、他のブラウザとメッセージの送受信を可能な状態にします。ブラウザ-チャネル間をRails5の新機能であるActiveCableで接続します。これにより、リアルタイムWebに関する実装が非常に簡潔になります。
今回の想定では下記の図のようになります。
動作
クライアントからリアルタイムWebチャットに対してブラウザでアクセスすると、クライアント上で動作するブラウザにリアルタイムWebチャットのシェルが表示されます。
ユーザーがフォームに名前とメッセージを入力し、Submitボタンを押します。
ブラウザがサーバに向けて名前とメッセージを送信します。
名前とメッセージを受信したサーバが、現在リアルタイムWebチャットにアクセス中の全てのクライアントに対し、名前とメッセージをブロードキャストします。
現在リアルタイムWebチャットにアクセス中の全てのクライアントにおいて、名前とメッセージが、ブロードキャストにより届けられた順番でブラウザに表示されます(シェル内に表示されているReceived Messageの下方に向かってタイムラインが伸びる形式)。
実装
本章では実際にRails5上でリアルタイムWebチャットのコードを書いて行きます。
Webアプリケーションのスケルトンの生成
最初に、Webアプリケーションのスケルトンを生成します。
本来、Webアプリケーション名は他のWebアプリケーションの名前と衝突しない限り任意ですが、
rails new minimum_websocket_chat
と入力して下さい。
Gemfileの編集
まず、サーバーサイドJavaScript実行環境としてnodeをインストールして下さい。
Ubuntuの場合、
apt install node
でインストールできます。
次に、Webアプリケーション内のルートディレクトリに置かれているGemfileを下記のように編集して下さい。
source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use Puma as the app server
gem 'puma', '~> 3.0'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
gem 'node'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platform: :mri
end
group :development do
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
gem 'web-console'
gem 'listen', '~> 3.0.5'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
ここでは、RailsからサーバーサイドJavaScript実行環境を利用するための追加ライブラリとして、gem 'node'という記述を追加しています。
最後に、Gemfileの編集を作成中のWebアプリケーションに対して適用するために、
bundle install
を実行して下さい。
コントローラの定義
下記のコマンドを入力して、indexアクションを持つRoomsコントローラを生成して下さい。
rails generate controller Rooms index
その結果として、/app/controllers/rooms_controller.rbとして、下記のRubyコードが自動生成されます。
class RoomsController < ApplicationController
def index
end
end
ルータの定義
前節で行ったRoomsコントローラの自動生成により、/config/routes.rbにルーティングに関する設定が自動的に追加されています。それを下記のように書き換えて下さい。
Rails.application.routes.draw do
root 'rooms#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
上記の設定は、Webサイトのルートディレクトリにアクセスした場合に、Roomsコントローラのindexアクションを呼び出すと言う設定です。
ビューの定義
前節で行ったRoomsコントローラの自動生成により、空の/app/views/index.html.erbが生成されています。
そこに下記のコードを追加して下さい。
<h1>Minimum WebSocket Chat</h1>
<form>
<label>Name:</label>
<input type="text" id="name_form" value="">
<br>
<label>Message:</label>
<input type="text" id="message_form" value="">
<br>
<input type="button" data-behavior="room_speaker" value="Submit">
</form>
<h5>Received Messages</h5>
<div id="messages"></div>
Submitボタンとなるinput要素のdata-behavior属性に、room_speakerを指定しています。これにより、イベントハンドリング時に用いる属性をCSSレイアウトに関わる属性と完全に分離することが出来ます。
id属性にmessagesを指定したdiv要素に、受信したメッセージが受信順に追記される形式で表示されます。
表示は先の画面イメージと同様です。
チャネルの定義
下記のコマンドを入力してRoomチャネルを生成して下さい。
rails generate channel Room speak
その結果として、自動生成された/app/channels/room_channel.rbのコードを下記のように書き換えます。
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak(data)
ActionCable.server.broadcast 'room_channel', data
end
end
speakメソッドのdata引数にサーバがActiveCable経由で受信したメッセージがハッシュで渡されます。
そのハッシュを、ActionCable.server.broadcastというメソッドにそのまま渡し、全てのクライアントに向けてブロードキャストしています。
ActionCableのクライアント側の端におけるデータ送受信処理の定義
前節のチャネルの自動生成により、ActionCableのクライアント側の端におけるデータ送受信処理を定義するCoffeeScriptファイルが/app/assets/javascripts/channels/room.coffeeとして自動生成されています。
そのファイルを下記のように書き換えて下さい。
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this channel
if data["name"] isnt "" and data["message"] isnt "" then $('#messages').append('<p>'+data["name"]+': '+data["message"]+'</p>')
speak: (name, message) ->
@perform 'speak', name: name, message: message
$(document).on 'click', '[data-behavior~=room_speaker]', (event) ->
nameForm = $('#name_form')
messageForm = $('#message_form')
App.room.speak nameForm.val(), messageForm.val()
messageForm.val('')
クライアント側におけるメッセージ送信時の挙動
クライアント側のメッセージ送信時の挙動に関して説明します。
$(document).on 'click', '[data-behavior~=room_speaker]', (event) ->
nameForm = $('#name_form')
messageForm = $('#message_form')
App.room.speak nameForm.val(), messageForm.val()
messageForm.val('')
data-behavior属性にroom_speakerを指定したinput要素(Submitボタン)がクリックされると、クリックイベントが発火します。そのクリックイベントをリッスンし、App.room.speakというメソッドに名前フォームの値とメッセージフォームの値を渡して呼び出します。その後、メッセージフォームを空欄にします。
App.room.speakは、
speak: (name, message) ->
@perform 'speak', name: name, message: message
に対応しており、ここで送信処理を実行しています。ここがCoffeeScriptのわかりにくい所の1つです。
クライアント側におけるメッセージ受信時の挙動
クライアント側のメッセージ受信時の挙動に関して説明します。
received: (data) ->
# Called when there's incoming data on the websocket for this channel
if data["name"] isnt "" and data["message"] isnt "" then $('#messages').append('<p>'+data["name"]+': '+data["message"]+'</p>')
data引数に、クライアントがActiveCable経由で受信したメッセージがハッシュで渡されます。そのハッシュの全要素が空でない場合に、id属性にmessagesを指定したdiv要素の子要素として、ハッシュの中身を文字列展開して見やすく整形したp要素を付加します。
これで実装は完了です。
実行結果
本章では、前章で実装したリアルタイムWebチャットの実行結果を確認します。下記の画面と同様の挙動が現れた場合、実装は成功です。
以降の作業を行う前に、コンソールから下記のコマンドを入力してHTTPサーバを起動して下さい。
rails server
2人の会話
2つのブラウザ画面を開き、各々から http://localhost:3000 に対してアクセスした後、片方のNameフォームにuser1、もう片方のNameフォームにuser2と入力します。そして、user1→user2→user1→user2→...と言う順番で、交互にメッセージを入力してSubmitボタンを押します。
user1の画面の例
user2の画面の例
3人の会話
3つのブラウザ画面を開き、各々から http://localhost:3000 に対してアクセスした後、1つのNameフォームにuser1、もう1つのNameフォームにuser2、最後の1つのNameフォームにuser3と入力します。そして、user1→user2→user3→user1→user2→user3→...と言う順番で、交互にメッセージを入力してSubmitボタンを押します。
user1の画面の例
user2の画面の例
user3の画面の例
あとがき
Rails5から搭載され始めたActiveCableを利用することで、非常に簡潔にWebSocket通信を用いたリアルタイムWebアプリケーションが実装できました。サーバー側で受信したメッセージをログとして保存したければ、チャネル内にActive Modelへの書き込みを付加するだけで良いですし、セキュリティを向上させたければ、このチャットにユーザー登録機能を被せるのも良いでしょう。
過去にHTTPベースのCometという手法が提案され、その改良型のServer-Sent Eventsなる技術も登場しましたが、HTTPヘッダのオーバーヘッドが大きく、殆どのブラウザにHTTPより遥かに効率的なWebSocketが実装された今となっては特殊な環境下でしか利用価値がないとされています。これからはWebSocket全盛の時代に突入すると思われるので、Rails5ユーザーの方は、ActiveCableを利用する練習を行っておく価値は大いにあると考えられます。
やはりWeb技術は複雑であり、定評のあるWebアプリケーションフレームワークを使用したとしても、一筋縄で行くとは限りません。Web技術自体が変化し続けており、まとまりがないため、他人の手法がそのまま適用できることも少ないです。従って、Webアプリケーション開発においては1次情報の調査能力,分析能力がモノを言うと言えるでしょう。
参考文献
本章では、本記事のリアルタイムWebアプリケーション作成において参考にした文献を記載します。
- https://www.ruby-lang.org/ja/ オブジェクト指向スクリプト言語 Ruby
ここでRuby言語の仕様やチュートリアルを確認できます。
- http://railstutorial.jp/ Ruby on Rails チュートリアル 実例を使ってRailsを学ぼう Michael Hartl (マイケル・ハートル)
Rails入門における定番中の定番です。実運用を視野に入れているため、読むのに時間は掛かります。
- http://railsguides.jp/ Ruby on Rails ガイド (5.0 対応)
公式の日本語Railsガイドです。Railsに関する本格的な勉強だけでなく、Railsを用いたWebアプリケーションの開発においても何度も参照することになります。
- http://api.rubyonrails.org/ Ruby on Rails API
Railsを用いた開発では頻繁に参照します。eRuby(ERB)の記述では特に重宝することでしょう。ネット上に転がるソースコードの詳細な意味を確認する際にも有用です。
- http://qiita.com/jnchito/items/aec75fab42804287d71b Rails 5 + ActionCableで作る!シンプルなチャットアプリ(DHH氏のデモ動画より)
この記事よりもう少し詳細な解説があります。この記事の次に読むと良いでしょう。
- http://qiita.com/masarufuruya/items/2bd5dfe03096057af63f Socket.ioとは何か?リアルタイムWebアプリケーションを実現する技術をまとめてみた
リアルタイムWebを実現する技術の歴史について簡単に解説しています。体系的にリアルタイムWeb技術を捉えたい人にお薦めです。
- http://www.slideshare.net/mawarimichi/websocketwebrtc WebSocket / WebRTCの技術紹介
リアルタイムWebを実現する技術の歴史について詳しくスライドで解説しています。体系的にリアルタイムWeb技術を捉えたい人にお薦めです。
- http://labs.gree.jp/blog/2014/08/11070/ Server Sent Events(SSE)の使いどころと使い方
Server-Sent Eventsの詳細な分析が行われています。やはり、WebSocketが利用できない場合の代替手段に留まるという結果が出ています。