目次へ

8. ActionController::Live

2013/10/03 シナジーマーケティング(株) 鈴木 圭

8.1. ActionController::Live とは

ActionController::Live とはリアルタイム Push 通信を可能にする機能です。

ActionController::Live 自体はクライアントにデータをストリームで返す(少しずつ返す)ことを可能にするものであり、それに Server-Sent Events のような技術と組み合わせることで、サーバ側からの Push 通知が可能となります。

サーバからのリアルタイム Push 通知を実現するには複数の技術を組み合わせることで実現します。その組み合わせとは、

  • HTTP/1.1 ではレスポンスヘッダで「Transfer-Encoding: chunked」を指定することで、データを複数のチャンクに分けて、少しずつレスポンスを返すことができる。
  • ActionController::Live はレスポンスを「Transfer-Encoding: chunked」を指定してデータを返す。
  • Server-Sent Events はサーバからのイベント通知(Push 通知)を受け取る仕組み。

ということです。

サーバからの Push 通知を行う他の技術としては Commet や WebSocket などもありますが、ActionController::Live を用いた Push 通知では上記の技術を組み合わせます。

8.3. サーバからのリアルタイム Push 通知

それでは ActionController::Live を用いたリアルタイム Push 通知を行ってみましょう。

サーバ側では ActionController::Live を使い、クライアント側は Server-Sent Events を使用することにします。

8.2.1. コントローラの実装

コントローラ側は IndexController#stream に実装することにします。

class IndexController < ApplicationController

  # (1)
  include ActionController::Live

  def stream
    # (2)
    response.headers['Content-Type'] = 'text/event-stream'

    10.times do |i|
      # (3)
      response.stream.write("event: message\n")
      response.stream.write("data: Hello #{i+1}\n\n")
      sleep 1
    end

    response.stream.write("event: done\n")
    response.stream.write("data: done\n\n")
  ensure
    # (4)
    response.stream.close
  end

end

ポイントは以下の通りです。

  • (1) ActionController::Live モジュールを include する。
  • (2) レスポンスヘッダで「Content-Type: text/event-stream」を指定する。
  • (3) response.stream.write でデータを書き込む。
  • (4) 最後に response.stream.close を呼び出す。

(1) は ActionController::Live を使うために必要です。

(2) はクライアント側で使用する Server-Sent Events で決められた Content-Type を指定しています。(3) においても、Server-Sent Events で決められた形式のデータを書き込んでいます。

そして最後に response.stream.close を呼び出します。これによってクライアントとのコネクションが切断されるため、非常に重要です。

8.2.2. ルーティングの設定

ルーティングは次のように設定します。

get 'stream' => 'index#stream'

8.2.3. アプリケーションサーバを Puma に変更

ここではアプリケーションサーバとして Puma を使うことにします。

Gemfile に以下の行を追加し、「bundle install」を実行しましょう。

gem 'puma'

もし Linux 環境で開発しているのであれば、アプリケーションサーバを起動してから wget コマンドは crul コマンドで動作確認することができます。

# wget の場合.
wget -O- -d http://localhost:3000/stream

# curl の場合.
curl -i http://localhost:3000/stream

上記コマンドを実行すると、少しずつレスポンスが返される様子が分かるはずです。

8.2.4. View の実装

最後に View を実装します。

app/views/index/index.html.erb は次のようになります。

<!-- (1) -->
<div id="result">
</div>

<%= javascript_tag do %>
    $(function() {
        <!-- (2) -->
        var eventSource = new EventSource("/stream");

        <!-- (3) -->
        eventSource.addEventListener("message", function(event) {
            $("#result").text(event.data);
        });
        eventSource.addEventListener("done", function(event) {
            this.close();
            $("#result").text(event.data);
        });
    });
<% end %>

(1) にはサーバからデータを受け取るたびに、その内容を設定します。(つまり動作確認用です)。

(2) と (3) が Server-Sent Events を用いた処理になります。

Server-Sent Events では EventSource オブジェクトを生成することから始まります(2)。そして生成したオブジェクトに対してイベントハンドラを設定します(3)。そこではサーバからデータを受け取るごとに、<div id=“result”> の中にその値を設定しています。

ここまで実装したらアプリケーションサーバを起動し、http://localhost:3000/ にアクセスすれば動作を見ることができます。

8.3. アプリケーションサーバの選定

必須ではないものの実運用を行う上では、

  • サーバが大量の同時接続に耐えられること。

が望ましいです。というのも、ActionController::Live を使用するとデータを全て返しきるまではコネクションが維持されるため、同時接続数が多くなる傾向があるからです。そのため、リソース消費量の観点ではマルチプロセス方式よりマルチスレッド方式が有利です。

Rails のアプリケーションサーバを大きく分類すると、マルチプロセス方式(WEBrick や Thin、Unicorn など)とマルチスレッド方式(Puma や Rainbows! など)があります。

マルチプロセス方式では、1 つのプロセスは同時に 1 つのリクエストしか処理を行いません。そのため、大量の同時接続に耐えるためには起動するプロセスの数を増やす必要があります。もう一方のマルチスレッド方式では 1 つのプロセスが同時に複数のリクエストを処理するため、大量の同時接続に耐えるためにはスレッド数を増やします。スレッドはプロセスよりも消費するマシンリソースが少ないため、同じ同時接続数に耐えるために必要なマシンリソースはマルチスレッド方式の方が少なくなります。

ではなぜマルチプロセス方式のアプリケーションサーバが存在するのかというと、1 つのプロセスは同時に 1 つのリクエストしか処理をしないため、アプリケーションサーバの実装がシンプルになるなどのメリットがあるからです。また、マルチスレッド方式のアプリケーションサーバを使用するということは、アプリケーションをスレッドセーフにする必要があるため、実装の負担が若干高くなるという違いもあります。

どちらかが絶対的に良いというわけではありません。しかし、Rails4.0 はデフォルトでスレッドセーフになりました。このタイミングでマルチスレッド方式のアプリケーションサーバの導入を検討することは十分に価値があるのではないでしょうか。

8.4. まとめ

ActionController::Live を使うことでサーバ側からのリアルタイム Push 通知が可能になりますが、それを実現するためにはいくつかの技術を組み合わせることや、サーバやクライアントがそれに対応していることが必要であると説明しました。

なかなか前提条件が多いですが、アイディア次第で面白い応用ができるかもしれないですね。使いどころとしては、「レスポンスのデータ量は多くなるかもしれないが、少しずつデータを送ることができる」という場合でしょう。例えば Twitter や Facebook のタイムラインのような機能です。他にも TECHSCORE BLOG の「ActionController::LiveとServer-Sent Events で地図上にじわじわ表示する」で一つの応用例を紹介していますので、ぜひともご覧ください。

さて、今回で Rails4.0 の解説は終わりですが、いかがでしたでしょうか。

新機能があり、数多くの改善もありました。非推奨や廃止となった機能もあります。

Ruby on Rails は今の最善を追及するフレームワークです。正しければ取り入れる、正しくなければ切り捨てる。変化の多いフレームワークです。

Ruby on Rails が脚光を浴び始めた頃を思い出します。「15 分で Blog ができる」というキャッチコピーに多くの人々が反応しました。賞賛もあり、批判もあり、様々な言葉が交わされました。そして、その頃が懐かしく感じられるほどに大きく変化してきました。数々の新機能が生まれ、数々の機能が非推奨となり、削除されてきました。

変化しすぎでしょうか。受け取り方は人それぞれでしょう。

Ruby は「楽しくプログラミングしよう」と言います。Ruby on Rails は「プログラマの幸せと高い生産性を持続するのだ」と言います。どちらも本音の言葉だと思います。

Ruby も Ruby on Rails もプログラマに高い生産性を与えてくれます。使いこなせば今までよりも短い時間で物事を成し遂げることができます。それによって生まれた時間を何に使えば良いのでしょう。自分を高めるために使うべきです。実装が短時間で終わったのなら、その時間でミドルウェアやインフラに取り組むことができます。設計やアーキテクチャを学ぶために使うこともできます。Ruby も Ruby on Rails もそれを考える時間、行動する時間を与えてくれます。

昔と比べるとお手軽さが少しだけ減ったこと。最善を求めて変化を続けるフレームワークであること。どちらの側面も理解した上で Ruby on Rails と付き合うことが出来れば、楽しくプログラミングすることも、高い生産性を発揮することもできるのではないかと思います。

Enjoy Rails!!

↑このページの先頭へ

こちらもチェック!

PR
  • XMLDB.jp
  • シナジーマーケティング研究開発グループブログ