Movatterモバイル変換


[0]ホーム

URL:


hp12c
rss
02 August 2012

エラーメッセージから学ぶRack - 最初の一歩

(追記:2012-12-25)本記事およびこれに続くRackの記事(全4本)をまとめて電子書籍化しました。「Gumroad」を通して100円にて販売しています。内容についての追加・変更はありませんが、誤記の修正およびメディア向けの調整を行っています。

Rack Ebook

電子書籍「エラーメッセージから学ぶRack」EPUB版

このリンクはGumroadにおける商品購入リンクになっています。クリックすると、オーバーレイ・ウインドウが立ち上がって、この場でクレジットカード決済による購入が可能です。購入にはクレジット情報およびメールアドレスの入力が必要になります。購入すると、入力したメールアドレスにコンテンツのDLリンクが送られてきます。

詳細は以下を参照して下さい。

電子書籍「エラーメッセージから学ぶRack」EPUB版をGumroadから出版しました!

購入ご検討のほどよろしくお願いしますm(__)m


Rackがわかりません。

Rackのサイトには、Rackについて次のように書いてあります。

Rack provides a minimal interface between webservers supporting Ruby and Ruby frameworks.

Rackは、Ruby向けWebサーバとRuby製フレームワークとの間の最小のインタフェースを提供します。

やっぱりよくわかりませんが、たぶんそれは、Ruby製Webフレームワークを作る人にとっては仮想Webサーバであり、またRuby向けWebサーバを作る人にとっては仮想Webフレームワークになるものだと理解します。

エラーメッセージから学ぶ

古くからの格言の一つに「Rackのことはrackupに聞け」というものがあります。Rackがわからないので、この格言に従いrackupに聞いてみることにします。

昨日はドラクエの発売日だったので、draqueというディレクトリを作って、ここでrackupを実行します。因みに私はドラクエは一度もやったことはありません。やっぱりそれは不幸なことですか?

%mkdir draque%cddraque%rackupconfiguration config.ru not found

config.ruという設定ファイルがないと言われました。それでは、これを作って再度rackupします。

%touch config.ru%rackup~/.rbenv/..../rack/builder.rb:129:in`to_app': missing run or map statement (RuntimeError)        from config.ru:1:in `<main>'

今度はrunまたはmapが見つからないと言われたので、config.ruにrunと書いてもう一度やってみます。

%echorun > config.ru%rackup              ~/.rbenv/.../rack/builder.rb:99:in`run': wrong number of arguments (0 for 1) (ArgumentError)        from config.ru:2:in `block in <main>'

今度は引数が足りないと言われました。runは恐らくWebアプリを走らせるコマンドでしょうから、Webアプリのインスタンスを渡せばよさそうです。試しに1を渡してみます。

# config.rurun1
%rackup>>Thin web server(v1.3.1 codename Triple Espresso)>>Maximum connectionssetto 1024>>Listening on 0.0.0.0:9292, CTRL+C to stop

ポート9292でThin Webサーバが立ち上がりました。

Browserでhttp://localhost:9292 にアクセスしてみます。

NoMethodError: undefined method`call' for 1:Fixnum

callメソッドがないと言われました。では、Fixnum#callを定義してみます。

# config.ruclassFixnumdefcallendendrun1

今度はどうでしょう。

ArgumentError: wrong number of arguments(1for0)        config.ru:3:in`call'

引数がないと言われました。引数を付けてみます。

# config.ruclassFixnumdefcall(arg)endendrun1

どうでしょう。

Rack::Lint::LintError: Status must be >=100 seen as integer

Statusは100以上の数でなければならないとのRack::Lint::LintErrorが吐かれました。ではcallメソッドが200を返すようにしてみます。

# config.ruclassFixnumdefcall(arg)200endendrun1

どうでしょう。

Rack::Lint::LintError: headers object should respond to#each, but doesn't (got NilClass as headers)

headersオブジェクトはNilClassだから#eachできないと言われました。では第2返り値として#eachできるオブジェクト[1]を渡してみます。

# config.ruclassFixnumdefcall(arg)return200,[1]endendrun1

どうでしょう。

Rack::Lint::LintError: header key must be a string, was Fixnum

今度はヘッダーキーが文字列じゃないと言われました。これで#eachできるオブジェクトがHashとわかりました。キーが文字列のHashオブジェクトを返してみます。

# config.ruclassFixnumdefcall(arg)return200,{'one'=>1}endendrun1

どうでしょう。

Rack::Lint::LintError: a header value must be a String, but the value of'one' is a Fixnum

今度は値も文字列じゃないとダメだと言われたので、これに対応してみます。

# config.ruclassFixnumdefcall(arg)return200,{'one'=>'1'}endendrun1

どうでしょう。

Rack::Lint::LintError: No Content-Type header found

Content-Typeヘッダーがないと言われました。用意します。

# config.ruclassFixnumdefcall(arg)return200,{'one'=>'1','Content-Type'=>'text/plain'}endendrun1

どうでしょう。

!! Unexpected errorwhileprocessing request: Response body must respond to each127.0.0.1 - -[02/Aug/2012 20:38:50]"GET / HTTP/1.1" 200 - 0.0010

GET / HTTP/1.1” 200“が返ってきました。しかし、レスポンスボディがeachできないと言っています。

それでは第3返り値として、eachできるbodyを返すようにしてみます。

# config.ruclassFixnumdefcall(arg)return200,{'one'=>'1','Content-Type'=>'text/plain'},"Welcome to ONE".charsendendrun1

どうでしょう。draque1

Browserにレスポンスが返ってきました。

以上のことをまとめます。

  1. rackupコマンドはWebサーバを起動する。
  2. その際、config.ruという設定ファイル(Rubyスクリプト)を読み込む。
  3. config.ruでは、Webアプリのインスタンスをrunメソッドに渡す。
  4. Webアプリのインスタンスは、1引数のcallメソッドを持っている必要がある。
  5. callメソッドは、3つの返り値、すなわち(1)100以上の数字からなるステータスコード、(2)少なくとも”Content-Type”をキーに、文字列を値に持つハッシュによるヘッダー、および(3)eachできるボディを返す。

Rack Webフレームワーク

さて、Webアプリが1では発展性が無さそうです。もう少しマシなWebアプリを考えます。

callメソッドを持っているオブジェクトと言えば、真っ先に思い浮かぶのはProcオブジェクトです。次に、思い浮かぶのはMethodオブジェクトです。ここでは後者を使ってみます。draqueメソッドを定義し、これをMethodオブジェクト化してrunに渡します。

# config.rudefdraque(arg)return200,{'one'=>'1','Content-Type'=>'text/plain'},"Welcome to the World of Draque!!".charsendrunmethod(:draque)

rackupしてBrowserでアクセスします。

draque2

いいようです。

さて次に、draqueに渡される引数について見てみます。まずはpします。

# config.rudefdraque(arg)pargreturn200,{'one'=>'1','Content-Type'=>'text/plain'},"Welcome to the World of Draque!!".charsendrunmethod(:draque)

コンソールに次のような出力が得られました。

%rackup>>Thin web server(v1.3.1 codename Triple Espresso)>>Maximum connectionssetto 1024>>Listening on 0.0.0.0:9292, CTRL+C to stop{"SERVER_SOFTWARE"=>"thin 1.3.1 codename Triple Espresso","SERVER_NAME"=>"localhost","rack.input"=>#<Rack::Lint::InputWrapper:0x00000100a156c0 @input=#<StringIO:0x000001009dab38>>, "rack.version"=>[1, 0], "rack.errors"=>#<Rack::Lint::ErrorWrapper:0x00000100a15648 @error=#<IO:<STDERR>>>, "rack.multithread"=>false, "rack.multiprocess"=>false, "rack.run_once"=>false, "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/", "PATH_INFO"=>"/", "REQUEST_URI"=>"/", "HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"localhost:9292", "HTTP_CONNECTION"=>"keep-alive", "HTTP_CACHE_CONTROL"=>"max-age=0", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1221.0 Safari/537.3", "HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch", "HTTP_ACCEPT_LANGUAGE"=>"ja,en-US;q=0.8,en;q=0.6", "HTTP_ACCEPT_CHARSET"=>"UTF-8,*;q=0.5", "HTTP_COOKIE"=>"_gauges_unique_year=1; _gauges_unique=1; _blog_app_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTVjMmY2ZDU1ODBiNTUxMDY5NGY3ZDkxNzQ3ZmRkOWVkBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMTQvY1IyYUpOaFBYUXpFYTNXOEU5SHlpYnVEU0ZEaDRxTmUwTzVINThmbmc9BjsARg%3D%3D--f9184f85f7974529836b4bce7c23a5f7132bf8df", "GATEWAY_INTERFACE"=>"CGI/1.2", "SERVER_PORT"=>"9292", "QUERY_STRING"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "rack.url_scheme"=>"http", "SCRIPT_NAME"=>"", "REMOTE_ADDR"=>"127.0.0.1", "async.callback"=>#<Method: Thin::Connection#post_process>, "async.close"=>#<EventMachine::DefaultDeferrable:0x00000100a06c38>}127.0.0.1 - - [02/Aug/2012 21:39:21] "GET / HTTP/1.1" 200 - 0.0023

クライアントの環境情報がWebサーバからハッシュで渡されていました。これらの情報があれば、クライアントの環境に応じたレスポンスが構築できそうです。まずは、これらを一覧表示するレスポンスを書いてみます。Content-Typeもtext/htmlに変更します。

# config.rudefdraque(env)return200,{'one'=>'1','Content-Type'=>'text/html'},body(env)enddefbody(env)["<h1>Welcome to the World of Draque!!</h1>"]+env.map{|k,v|"<p>%s => %s</p>"%[k,v]}endrunmethod(:draque)

どうでしょうか。

draque3

タイトルとともにクライアントの環境情報がレンダリングされました。

次に環境変数におけるパス情報を使って、パスに応じたレスポンスを返すようにしてみます。

case式でパスに応じてレスポンスを切り替えるようにします。

# config.rudefdraque(env)path=env['PATH_INFO']casepathwhen'/draque'then[200,headers,draque_body]when'/'then[200,headers,top_body(env)]else[404,headers,not_found]endenddefheaders{'Content-Type'=>'text/html'}enddeftop_body(env)["<h1>Welcome to the World of Draque!!</h1>"]+env.map{|k,v|"<p>%s => %s</p>"%[k,v]}enddefdraque_body["<img src='http://www.dqx.jp/storage/img/top/main_visual.png'>"]enddefnot_found["<img src='https://a248.e.akamai.net/assets.github.com/images/modules/404/parallax_octocat.png?1329921026'>","<img src='https://a248.e.akamai.net/assets.github.com/images/modules/404/parallax_errortext.png?1329921026'>"]endrunmethod(:draque)

/draqueにアクセスします。

draque4

次に、用意されていない/rubyにアクセスします。

draque5

うまくいっています。怒られるでしょうか。

さて、ここまで来たら、ルーティングはsinatra風に書きたいです。getメソッドを定義して、パスに応じたレスポンスを登録できるようにします。

# config.ru@routes={get:{}}defdraque(env)path=env['PATH_INFO']ifres=@routes[:get][path]res.call(env)else[404,headers,not_found]endenddefget(path,&blk)@routes[:get][path]=blkendget'/draque'do[200,headers,draque_body]endget'/'do|env|[200,headers,top_body(env)]enddefheaders{'Content-Type'=>'text/html'}enddeftop_body(env)["<h1>Welcome to the World of Draque!!</h1>"]+env.map{|k,v|"<p>%s => %s</p>"%[k,v]}enddefdraque_body["<img src='http://www.dqx.jp/storage/img/top/main_visual.png'>"]enddefnot_found["<img src='https://a248.e.akamai.net/assets.github.com/images/modules/404/parallax_octocat.png?1329921026'>","<img src='https://a248.e.akamai.net/assets.github.com/images/modules/404/parallax_errortext.png?1329921026'>"]endrunmethod(:draque)

わかりづらくなってきたので、フレームワークの部分をモジュール化します。

# config.rumoduleDraque@@routes={get:{}}defdraque(env)path=env['PATH_INFO']ifres=@@routes[:get][path]res.call(env)else[404,headers,not_found]endenddefget(path,&blk)@@routes[:get][path]=blkenddefheaders{'Content-Type'=>'text/html'}endendObject.send(:include,Draque)get'/draque'do[200,headers,draque_body]endget'/'do|env|[200,headers,top_body(env)]enddeftop_body(env)["<h1>Welcome to the World of Draque!!</h1>"]+env.map{|k,v|"<p>%s => %s</p>"%[k,v]}enddefdraque_body["<img src='http://www.dqx.jp/storage/img/top/main_visual.png'>"]enddefnot_found["<img src='https://a248.e.akamai.net/assets.github.com/images/modules/404/parallax_octocat.png?1329921026'>","<img src='https://a248.e.akamai.net/assets.github.com/images/modules/404/parallax_errortext.png?1329921026'>"]endrunmethod(:draque)

なんちゃってWebフレームワークdraqueの完成です^ ^;

Rack、最初の一歩は踏み出せたでしょうか。

Joke Rack Web framework Draque — Gist


(追記:2012-08-06) 続きを書きました。

エラーメッセージから学ぶRack - Middlewareの魔法


Rack [DVD] [Import]


  • このエントリーをはてなブックマークに追加

blog comments powered byDisqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS



  • このエントリーをはてなブックマークに追加

[8]ページ先頭

©2009-2025 Movatter.jp