"小さい"Rackアプリケーション

ちょっと Rack について見てたりした(Rackまわり - Rubyとか Illustratorとか SFとか折紙とか)
で、この辺(第23回 Rackとは何か(1)Rackの生まれた背景:Ruby Freaks Lounge|gihyo.jp … 技術評論社)なんかも参考に最低限の Webアプリケーションを作ってみる。
(適宜自分ツイッターからの再録を挿みます)

Rack、config.ru

「rackup」コマンドで Rackを起動するとき、オプションで指定しないと設定ファイル「config.ru」を見に行く。アプリケーションのクラスを定義する Rubyスクリプトを require するのが定例の様だけど、そこでクラス定義してもいいわけだ。

class App
  def call(env)
     [200, {'Content-type' => 'text/plain'}, ['hello']]
  end # def call(env)
end # class App

rub App.new

これを「config.ru」という名前で保存し、「rackup」コマンドを実行すれば、取り敢えずどんなリクエストにも「hello」と返すだけの Webアプリケーションが起動する。

この辺を出発点に記述を少なくしていこうと思った。

なんかクラス定義を終わらせたら即座に .new してもいいよね、そしてそのまま runメソッドに渡す

run (class App
  def call(env)
     [200, {'Content-type' => 'text/plain'}, ['hello']]
  end # def call(env)
  App
end.new)

class 定義はそのままではそのクラスを返すわけじゃなくて、最後の実行文の結果を返す、ので定義最終行に「App」と書いておく、ちょっと無様。
なら、無名のクラスを作ってそれでいいじゃん

run(Class.new do
  def call(env)
     [200, {'Content-type' => 'text/plain'}, ['hello']]
  end # def call(env)
end.new)

config.ru 一行

メソッド定義も短くする、改行も自然に減らすようにメソッドで定義しよう

. connfig.ru「run Class.new{define_method(:call, lambda{|env| [200, {'Content-type' => 'text/plain'}, ['hello']]})}.new」 一行Rackアプリ #railstokyo
5:01 PM Nov 13th webから

run Class.new{define_method(:call, lambda{|env| [200, {'Content-type' => 'text/plain'}, ['hello']]})}.new

「config.ru」ファイル、一行になった。
さて、「run」に渡すのは、いちいちクラス作って .new しなくても、要するに「.call(env)」メソッドを持つオブジェクトならいいわけだ、lambda とか

. 一行Rackアプリ、config.ru「run lambda{|env| [200, {'Content-type' => 'text/plain'}, ['hello']]}」
7:39 PM Nov 14th webから

run lambda{|env| [200, {'Content-type' => 'text/plain'}, ['hello']]}

さて、先の技評記事にもあったが、callメソッドの返り値を作るには「Racc::Response」も使える

. 一行Rackアプリ「run lambda{|env| Rack::Response.new('hello').finish}」 もう少し短く出来たが、これでは text/html になる
7:40 PM Nov 14th webから

run lambda{|env| Rack::Response.new('hello').finish}

ステータスやヘッダの指定とか省略出来るのでそれなりに短くなったが、コメントにある通り、Content-typeの指定が html になってしまう。
あと、「.finish」注意、これを呼ぶ事で Rack::Responseオブジェクトが .callメソッドの返り値として期待される配列になる。

さて Sinatra では

一方、簡潔な Webアプリケーションにいいというシナトラでは、ここまで単純なのはどうなるんだろう。

. 一行Rackアプリ、一方シナトラ sinatra では、「ruby -rubygems -rsinatra -e 'get("/"){"hello"}'」(これは trxt/html になる)
8:10 PM Nov 14th webから

ruby -rubygems -rsinatra -e 'get("/"){"hello"}'

コマンドラインからの一行で、ルート「/」への GETリクエストに対して「hello」と返す Webアプリケーションとなる。
ここでちょっと、「ruby -rubygems -rsinatra」が効くのは Ruby 1.9以降かもしれないの注意。

Rackだってコマンドラインから

. 一行Rackアプリ、いや Rackアプリだってコマンドライン一行で書けた、140字ぎりぎり
9:09 PM Nov 14th webから

. 一行「ruby -rtempfile -e '`rackup #{Tempfile.open(["",".ru"]){|f| %Q[run lambda{|e| Rack::Response.new("hello").finish}].display f; f.path}}`'」
9:08 PM Nov 14th webから

ruby -rtempfile -e '`rackup #{Tempfile.open(["",".ru"]){|f| %Q[run lambda{|e| Rack::Response.new("hello").finish}].display f; f.path}}`'

「rackup」コマンドは設定「.ru」ファイルを要求する、それを tempfileで作ってみた。

Rack::Server を使ってコマンドラインから

http://rack.rubyforge.org/doc/ を見るに、「rackup」コマンドを使わなくても直接 Rack::Serverオブジェクトをたてることもできる様だ(http://rack.rubyforge.org/doc/classes/Rack/Server.html#M000205)。
そこに書いてあったサンプルスクリプト

 Rack::Server.start(
   :app => lambda do |e|
     [200, {'Content-Type' => 'text/html'}, ['hello world']]
   end,
   :server => 'cgi'
 )

は動かなかった(rack (1.2.1))。どうもイニシャライズのときの :appオプションと @appインスタンス変数の割り当てが微妙な感じ。なのでインスタンス変数を手で設定してやる。

. 一行Rackアプリ、コマンドライン一行で書く Rackアプリ、140字ぎりぎり
3:19 PM Nov 15th webから

. 一行「ruby -rubygems -r rack -e "Rack::Server.new.tap{|s| s.instance_variable_set :@app, proc{Rack::Response.new('hello').finish}}.start"」
3:18 PM Nov 15th webから

ruby -rubygems -r rack -e "Rack::Server.new.tap{|s| s.instance_variable_set :@app, proc{Rack::Response.new('hello').finish}}.start"

ちなみに「env」引数なんか見てないから省略した、そうなると lambda では引数の数のチェックが厳しいので proc を使う事にした。
ここでちょっと、コマンドラインの場合の引用符、Windowsでは二重引用符が外側の方が良いかもしれないの注意。