Pinto公開に向けて #3 ― Addressableを使ってみた
あらすじ
PintoというソーシャルブックマークサービスをRubyで作ろうとしています。URI駆動設計がアツいので、前回はURIを設計しました。今回はURI Templatesに基づくルーティング処理をAddressableで実装してみます。
おことわり
Rubyはいじり始めたばかりなので、以下、おかしなコードが含まれるかもしれません。お気づきの方はご指摘いただけると、中の人が喜びます。
まずはrackup用スクリプト
Webサーバ依存なコードは書きたくないので、Rackで実装することに以前決めました。
RackのREADMEにある通り、rackupというツールを使うと、特別な設定なしにWebサーバが起動でき、RackベースのWebアプリが実行できます。
というわけで、まずはrackup用のスクリプトを書きます。とりあえず下記のようにしました。
# script/server.ru $LOAD_PATH << '../lib' require 'pinto/dispatcher' run Pinto::Dispatcher.new
下記コマンドで実行。
cd script rackup -s mongrel -p 80 server.ru
次にディスパッチャ
続いて、rackupから呼ばれるディスパッチャを書きます。まだまだ完成形ではないですが、今は下記のようになっています。
# lib/pinto/dispatcher.rb require 'addressable/uri' require 'pinto/encoding' require 'pinto/uri/extract_processor' require 'rack' module Pinto class Dispatcher def call(env) uri_templates = { 'top' => 'http://pinto.jp/', 'index' => 'http://pinto.jp/index{lang}', 'signup_forms_openid' => 'http://pinto.jp/signup_forms/openid{lang}', 'signup_forms_auth' => 'http://pinto.jp/signup_forms/auth{lang}{query}', 'signup_forms_account' => 'http://pinto.jp/signup_forms/account{lang}{query}', 'users' => 'http://pinto.jp/users{lang}', 'user' => 'http://pinto.jp/users/{username}{lang}' } request = Rack::Request.new(env) parsed_uri = Addressable::URI.parse(request.url) uri_templates.each do |controller, uri_template| param = parsed_uri.extract_mapping(uri_template, Pinto::URI::ExtractProcessor) next if param.nil? param['query'] = Pinto::Encoding.to_utf8(request.GET) param['form'] = Pinto::Encoding.to_utf8(request.POST) # require "pinto/controller/#{controller}" # ToDo: コントローラ呼び出し処理を実装する return [ 200, {'Content-Type' => 'text/plain; charset=UTF-8'}, ['controller: ' + controller + "\nparam: " + param.inspect] ] end [ 404, {'Content-Type' => 'text/plain; charset=UTF-8'}, ['Not Found'] ] end end end
uri_templatesハッシュに、コントローラ名とURIテンプレートを定義しています。各ブラケット(URI Templatesでは「template expansion」と呼ぶらしい)の意味は下記の通りです。
- {lang}
- 「.ja」「.en」または空文字列。言語指定用の拡張子のようなイメージです。
- {query}
- クエリストリング(空の場合もある)。URI Templatesのjoinオペレータで定義しても良さそうですが、現状のAddressableはjoinに対応していないので、ざっくりとにしました。
- {username}
- ユーザ名。文字種は決めていませんが、とりあえず英数字といくつかの記号でしょう。
続いてextract用プロセッサ
Addressable::URI#extract_mappingでURIテンプレートに実際のURLをぶつけてブラケット部分の値を抜き出す(extract)のですが、このとき第2引数としてプロセッサが渡せます。
プロセッサの実装については、リンク先のサンプルコードを見ていただくのが早いのですが、簡単にいうと、self.matchではマッチング用の正規表現が、self.restoreでは実際に取得したい値が指定できます。
先に定義したテンプレートに基づき、下記のように書きました。
# lib/pinto/uri/extract_processor.rb module Pinto class URI class ExtractProcessor def self.match(name) return '\.ja|\.en|' if name == 'lang' return '\?.+|' if name == 'query' return '[0-9a-zA-Z_\-]+' if name == 'username' return '.*' end def self.restore(name, value) return value.gsub(/^\./, '') if name == 'lang' return value.gsub(/^\?/, '') if name == 'query' return value end end end end
matchではもっとスマートな正規表現が書きたいのですが、例えばカッコが含まれたりするとうまく動きませんでした。追究するならAddressableのコードを読まないとダメですね。
おまけ:文字コード変換スクリプト
ディスパッチャの中で Pinto::Encoding.to_utf8(request.GET) などとしているわけですが、これ、そもそも必要なのかどうか、よく分かっていません(えー)。そうしないと日本語が正しく表示できなかったので、今のところそうしているだけです。
せっかくなので、コードを晒します。
# lib/pinto/encoding.rb require 'kconv' module Pinto class Encoding def self.to_utf8(param) return param.toutf8 if param.respond_to? 'toutf8' if param.kind_of? Array return param.map do |v| self.to_utf8(v) end end if param.kind_of? Hash return param.map do |k, v| [self.to_utf8(k), self.to_utf8(v)] end end return param end end end
Ruby 1.9ではM17N対応が行われ、この辺りの処理がだいぶ変わるはずなので、今は気にしないでおこうと思っています。
次のアクション
- コントローラの呼び出し部分を実装する