Rack 実践 (Rack-based Application)

いままで Ruby on Rails を使った Web アプリケーション開発は経験してきたが、Ruby on Rails を使わずに 基幹フレームワークの Rack のみを使って Web アプリケーションを構築したい。Rack に関する基礎知識は、以前私が書いた記事を参照していただければと思う。

この記事では、実践的な Rack アプリケーションが動いていくまでの道筋を、断片的ながら紹介していこうと思う。この記事は備忘録的なところがあるため、分かりにくい表現や、正確でない情報を含んでいるかもしれない。申し訳ないが、そういった範囲に関しては、読者の皆さんで補える範囲は調べて頂けると助かる。

やりたい事

  1. Bundler を使って混乱の少ない gem 管理
  2. ERB の使い方を覚える
    • Ruby による動的なHTML生成には、今回 ERB を利用する
    • クライアントからのリクエストに対して、どのように HTML をレスポンスするべきか考える
  3. Rack から静的なファイル(javascriptスタイルシート等)を配信する方法
  4. Ruby on Rails のアセットパイプラインを、Rack でどのように実現するか(1)
    • まず Sprockets のことを知る
  5. Ruby on Rails のように Sass, CoffeeScript を使えるようにしたい
  6. Session 情報を Cookie に保存する
  7. (つづく)

ERB

標準添付ライブラリ紹介 【第 10 回】 ERB

ERB を利用する。これを使うことで、任意のテキストファイルに、Ruby スクリプトの実行結果を埋め込めるようになる。やりたい事のイメージは次の通り。

  1. クライアント → Web サーバーに HTTP リクエス
  2. Web サーバー → Rack → アプリケーションに処理がわたる
  3. リクエストに対応するページ(HTML)をレスポンス

例えば、「/hoge」という URI に対してリクエストが来た場合、次のように実装できる。

# application.ru (このファイルは config.ru などから参照される)
# ...

require 'erb'

map '/hoge' do
  name = 'maeharin'

  run Proc.new {|env|
    [
      200,
      {"Content-Type" => "text/html"},
      [ERB.new( File.read('view/hoge.erb', :encoding => Encoding::UTF_8) ).result(binding)]
    ]
  }
end

# ...

ビューは別のファイルに、以下のような感じで記述した。

# view/hoge.erb
<!DOCTYPE html>
<html>
<head>
  <meta charset='utf-8'>
  <title>hoge</title>
</head>
<body>
  <p>My name is <%= name %> .</p>
</body>
</html>

Rack から静的ファイルをレスポンスする

Rack から静的ファイルをレスポンスするには、次の標準ライブラリが用意されている。

Rack 上のドキュメントルートの指定と、任意のディレクトリ以下にあるファイルを静的ファイルとして、サービスするという指定ができる。Rack::Builder を使っているならば、次のような use を追加するだけで良い。

use Rack::Static, :urls => ["/images", "/stylesheets", "/javascripts"], :root => "public"

これは「/public/images」「/public/stylesheets」「/public/javascripts」に対するリクエストを、静的ファイルへのリクエストと解釈して、対象となるファイルをうまくレスポンスしてくれる。


Ruby on Rails のようなアセットパイプラインを実現する

Ruby on Rails のゴキゲンな機能といえば、私は 3.1.x 系から導入された「アセットパイプライン」だと思う。これは少々とっつきにくい印象があるが、理解に必要な労力に対して、十分大きなリターンを得られると思うので、是非きちんと理解して欲しい。

アセットパイプラインは、次の3つのことを非常に簡単に扱えるようにした。

  1. Ruby on Rails 上で、Sass、CoffeeScript を使えるようにした。
  2. 複数のスタイルシートJavaScript(これらをアセットと呼ぶ)を結合・ミニファイ(圧縮)可能にした。
  3. フィンガープリンティングによる最新のリソースキャッシュ機構を、開発者の手の届くものにした。

アセットパイプラインの解説には、この仕組みを支えているのが Sprockets(スプロケット)という gem であると紹介されている。というわけで、私も Sprockets が一体何をやってくれるのか、きちんと調べることにした。

README.md の内容を読むことで、Sprockets がやってくれていることが朧気に見えてくる。とりあえずは Rack アプリケーションに、次のような記述を追加する。

# application.ru
# ...
require 'sprockets'

# Sprockets Assets management.
map '/assets' do
  environment = Sprockets::Environment.new
  environment.append_path 'assets/javascripts'
  environment.append_path 'assets/stylesheets'
  
  run environment
end

# ...

アセットファイルとして次の Sass ファイルを用意する

// assets/stylesheets/hello.css.scss
p {
  font-size: 20px;
  color: blue;
}

ビューファイル(ERB)は次のような感じ

<%# view/hello.erb %>
<!DOCTYPE html>
<html>
  <head>
  <meta charset='utf-8'>
  <title>Sprockets Sample</title>
  <link rel="stylesheet" type="text/css" href="assets/hello.css" />
  </head>
  
<body>
  <p>hoge hoge</p>
</body>
</html>

これによって、view/hello.erb を呼び出すリクエストが来たとき、おそらく次のようなことが行われている。

  1. クライアントから view/hello.erb を呼び出すリクエストがくる
  2. view/hello.erb の内容(HTML)がクライアントにレスポンスされる
  3. HTML が '/assets/hello.css' を要求しているので、'/assets/hello.css' をリクエストする
  4. Sprockets が要求されたアセットを生成して、レスポンスする

ここで疑問になったのは、Sprockets がどこまでやってくれるのか? 私は最初、Ruby on Rails のアセットパイプラインの仕組みの大部分をカバーしている gem と思っていた。しかし、調べてみるとそこまでやってない感じがする。Sprockets の README.md には「CoffeeScript, Sass などをサポートし、アセットファイルをパッキングする gem である」と説明してある。つまり、Ruby on Rails が提供していた、アセットファイルのプリコンパイル機能や、フィンガープリンティングを使ったキャッシュ機構は、Sprockets を使うだけでは単純に実現することはできないように思える。

プリコンパイル機能や、フィンガープリンティングを使ったキャッシュ機構に関しては、別に時間を割いてちょっと調べてみる必要がある。

Sass や CoffeeScript をサポートしたい

これは Sprockets がサポートしているため、Sprockets を使えば私たちがすることはない。

Session 情報を Cookie に保存する

Ruby on Rails では 2.0 から Session の保存先が、デフォルトで Cookie になった。いままで Web サーバーが Session を保持する方法は、Web サーバー上のファイルシステム内に Session を保管、もしくは DB 上に Session を持たせるのが一般的だったらしい。

上記の2つの方法には、大きく次のデメリットがある。Web サーバー上のファイルシステムに保持する方法は、Web サーバーが状態を持つことになるため(ステートフル)、Web サーバーをスケールアウトするのが難しくなる。一方、DB で保持する方法は、DB 問い合わせがそもそも高コストである。

Session 情報を Cookie に持たせる方法は、Web サーバーとのステートレスな関係を維持しつつ、DB アクセスも発生しない良い方法に思える。(ただし、HTTP リクエストごとに、毎回 Session 情報をやり取りするコストは多目に見なければいけない)

Session 情報を Cookie に保存する方法は、Cookie(に保存された Session)の改ざん防止対策をきちんと行わなければ危険である。なので、その部分の実装コストはかかる。それを乗り越えて、使えるようになれば、Session をお手軽に扱うことができるようになる。

Rack には、この Session 情報を Cookie に保存するためのミドルウェアが標準で提供されている。

Class Rack::Session::Cookie

使い方は簡単で、以下のコードを Rack::Builder に埋め込むと利用準備が整う。

use Rack::Session::Cookie, :secret => 'change_me'

「:secret => 'change_me'」を付加しないと、Cookie の改ざんチェックが無効になってしまう。これによって、Rack::Request の session から次のようにアクセスが可能である。

# ...
req = Rack::Request.new(env)

# 書き込み
req.session['test'] = "hoge hoge piyo piyo"

# 読み込み
p "session test: #{req.session['test']}"

# ...

参考:2.0のcookie session storeを体感する
参考:Rack::Session::Cookieを使う

つづく

... 執筆中