こんにちは、キャスレーコンサルティングSI部の反田(そった)です。
今回はRubyの軽量WebアプリDSLといわれるSinatraを紹介します。
Sinatraは説明は以下が詳しいですが
http://www.sinatrarb.com/intro-jp.html
ざっくり説明すると
- 超簡単にWebアプリが作れて
- Herokuなどクラウド環境でらくらく公開できる
という特徴があります。
この記事ではSinatraを使ったことがない人向けにSinatraをインストールして実際に簡単なWebアプリを作ってみる実演をします。
インストール
Win環境でまだRuby入っていないならば以下などからインストールして下さい。
http://www.artonx.org/data/asr/
Rubyがすでに使える環境ならばインストールはgemから
gem install sinatra
するだけです。簡単ですね。開発用ツールのsinatra-contribもインストールします。
gem install sinatra-contrib
確認してみましょう。
gem list sinatra *** LOCAL GEMS *** sinatra (1.4.3) sinatra-contrib (1.4.0)
インストールされました。
SinatraをインストールしたらHello Worldしてみます。
以下のようなファイルを作成します。
sinatra_helloworld.rb
require 'sinatra' get '/' do 'Hello world!' end
出来上がったら早速実行してみましょう。
ruby sinatra_helloworld.rb
以下のように出力されました。
== Sinatra/1.4.3 has taken the stage on 4567 for development with backup from Thin >> Thin web server (v1.5.1 codename Straight Razor) >> Maximum connections set to 1024 >> Listening on localhost:4567, CTRL+C to stop
Sinatraは引数なしで実行するとlocalhost:4567で起動するようです。localhost:4567にアクセスすると
Hello world!と表示されました。
http://localhost:4567/
Sinatraではget '/' do
の中に処理を書くだけでlocalhost:4567/にアクセスした際の処理が書けてしまいます。
get '/hello' do
とすればlocalhost:4567/helloの処理が書けます。設定ファイルなど必要ないので、すごく簡単です。
最後に実行中のSinatra(Rubyスクリプト)を止めてみましょう。起動メッセージの通りコンソールからCtrl+Cと入力すると止まります。止まった後に先ほどのリンクにアクセスすると、エラー画面が表示されるのが確認できます。
^C>> Stopping ... == Sinatra has ended his set (crowd applauds)
テスト方法
アプリを作る前にごくごく簡単にテスト方法を確認しましょう。
先ほどのsinatra_helloworld.rbと同じフォルダに以下のファイルを作成します。
sinatra_helloworld_test.rb
require './sinatra_helloworld' require 'test/unit' require 'rack/test' class SinatraHelloworldTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_helloworld get '/' assert_equal 'Hello world!', last_response.body end end
テストを実行するには以下のようにすればよいです。
ruby sinatra_helloworld_test.rb
結果
Run options: # Running tests: Finished tests in 0.041632s, 24.0200 tests/s, 24.0200 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin10.8.0]
また、assert_equalメソッドの’Hello world!’を’Hello WORLD!’に変更してテストを実行すると失敗することも確認できます。
Run options: # Running tests: [1/1] SinatraHelloworldTest#test_helloworld = 0.03 s 1) Failure: test_helloworld(SinatraHelloworldTest) [sinatra_helloworld_test.rb:14]: <"Hello WORLD!"> expected but was <"Hello world!">. Finished tests in 0.041901s, 23.8658 tests/s, 23.8658 assertions/s. 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin10.8.0]
test/unitの実行方法はここを参考にしました。Ruby 2.0.0 リファレンスマニュアル > test/unitライブラリ
アプリを作る
さて実際にアプリを作ってみましょう。
何を作るかなのですがこちらの記事を参考にあるブラウザから別のブラウザに画像ファイルを受け渡すアプリを作成したいと思います(rendez-vous)。
以下、簡単に動作を説明します。
画像を送る側をブラウザA、受け取る側をブラウザBとすると2つのパターンで動作します。
1つ目のパターンでは先にファイルの送信が完了しているパターンです。送る側は受け取る側のアクセスがあるまで待ちます。
2つ目のパターンではファイルの受信が先になるパターンです。受け取る側は送る側の処理が終わるまで待ちます。
元記事ではCGIを使っているところをSinatraに変更します。また、drbに関しても説明したかったのですがSinatraの説明にしぼるために省略します。
CGI版からの変更点を見てみましょう。
- RdVSpaceをアプリケーションスコープでもつ
- Sinatraでのファイルアップロード方法
実際に作成したスクリプトを示した上で解説します。
require 'sinatra' require 'sinatra/reloader' if development? require 'erb' require 'rinda/tuplespace' class RdVSpace def initialize @ts = Rinda::TupleSpace.new(1) @expires = 30 end def write(name, type, body) _,_,key = @ts.take([:get, name, nil], @expires) @ts.write([:post, key, type, body], @expires) end def take(name) key = Object.new @ts.write([:get, name, key], @expires) _,_, type, body = @ts.take([:post, key, nil, nil], @expires) [type, body] end end set :rdv, RdVSpace.new get '/' do erb RHTML end get '/upload' do name = params[:name] if name != '' type, body = settings.rdv.take(name) content_type type body else "no name fail." end end post '/upload' do name = params[:name] if params[:file] type = params[:file][:type] file_body = params[:file][:tempfile] settings.rdv.write(name, type, file_body) "success write" else redirect "/upload?name=#{name}" end end RHTML = <<EOS <html> <title>RdVUp!</title><meta name="viewport" content="width=320" /> <body> <form method='post' action='/upload' enctype='multipart/form-data'> <input type='file' name='file'/><br /> key: <input type='textfield' name='name' value=''/><br /> <input type='submit' value='RdV' /> </form> </body> </html> EOS
2行目require 'sinatra/reloader' if development?
はsinatra-contribがないと使えません。
開発時だけサーバーを再起動せずにスクリプトの変更が反映されるようになります。便利!
7行目のRdVSpaceクラスではRindaのTupleSpaceを使って待ち合わせ処理を実現しています。元記事からの変更はありません。
とてもおもしろいのですがSinatraはあんまり関係ないです。
26行目 set :rdv, RdVSpace.new
この処理でRdVSpaceをインスタンス化してアプリケーションのスコープで保存しています。
CGI版では別プロセスにずっと存在しているので、Sinatra版でも同様にずっと存在していて欲しいのですが
素直にインスタンス変数として書くとアクセスできません。
これはSinatraにはスコープがありスコープごとにアクセスできるかが変化するためです。
どのリクエストからでもアクセスできるようするためにアプリケーションスコープに保存しています。
くわしくはこちらから
28行目 http://localhost:4567/にアクセスした際に実行されるメソッドです。erbメソッドで送信/受信用の画面を表示します。get,postの処理はリクエストスコープです。
32行目 送信/受信用画面からファイル指定せずにpostするとリダイレクトされて呼ばれます。
35行目のsettings.rdv.take(name)
のようにsettingsヘルパーを利用するとリクエストスコープからアプリケーションスコープにアクセスできます。
43行目 送信/受信用画面からpostするとこのメソッドが呼ばれます。
ファイルが指定されているかをチェックします。
ファイルが指定されているかは45行目のように判定します。
アップロードされたファイルは47行目のparams[:file][:tempfile]
のように:tempfileで取得します。
48行目では35行目同様アプリケーションスコープにアクセスしています。
55行目 eRuby形式で送信/受信用画面を作成しています。ただのHTMLです。
アプリを公開する
最後に今回作成したアプリをheroku上で公開してみました。
https://www.heroku.com/
herokuに公開するのは初めてでしたがアカウント作成から公開まで半日足らずでできました。すごい!
以下のリンクから動かしてみて下さい。(しばらくは公開しています)
http://intense-bastion-9523.herokuapp.com/
まとめ
今回はSinatraのインストールからテスト、アプリケーションスコープの利用方法、ファイルアップロードの仕方を紹介しました。
秋の夜長のプログラミングのともにSinatraはいかがでしょうか?
それではまた。