5.5. S2CGI Webフレームワーク

S2CGI Webフレームワークは、Apache上でCGIスクリプトとし実行されるRubyアプリケーションを作成するための小さなフレームワークです。

5.5.1. セットアップ

5.5.1.1. ダウンロード

次のURLからsvn exportして下さい。

  • https://www.seasar.org/svn/sandbox/s2container.ruby/trunk/s2cgi/
次のディレクトリが作成されます。

project/
  config/        # 設定ファイルディレクトリ
  lib/           # サービスやロジッククラスの保存ディレクトリ
  public/        # WEB公開ディレクトリ
  test/          # UnitTestディレクトリ
  tpl/           # erbテンプレートファイル保存ディレクトリ
  var/           # データやログ保存ディレクトリ
  vendor/        # 依存ライブラリ保存ディレクトリ

5.5.1.2. S2Container.Rubyのインストール

インストールドキュメントにしたがって S2Container.Ruby をインストールして下さい。

5.5.1.3. Rackのインストール

Rackを使用する場合は、Rackをインストールして下さい。

5.5.1.4. Apacheの設定

拡張子cgiをcgiスクリプトとして登録します。
public ディレクトリを公開する設定とします。
AddHandler cgi-script .cgi
Alias /s2cgi/ "/path/to/project/public/"
<Directory "/path/to/project/public">
    Options ExecCGI
    AllowOverride None
    Order allow,deny
    Allow from localhost
</Directory>

5.5.1.5. s2cgiの設定

Base URLを適切に設定します。Base URLの設定は config ディレクトリの environment.rb で行います。
% cat config/environment.rb | grep BASE_URL
BASE_URL = '/s2cgi'
%
publicディレクトリのcgiスクリプトの所有者と実行権を確認してください。cgiスクリプトのシェバングを適切に設定します。
% ls -l public/cgi/quick1.cgi
-rwxr--r-- 1 apache apache 1250 Feb  1 00:00 public/cgi/quick1.cgi
% head -1 public/cgi/quick1.cgi
#!/usr/bin/env ruby
%
varディレクトリの所有者と実行権を確認してください。
% ls -ld var
drwxr-xr-x 6 apache apache 4096 Feb  1 00:00 var
%

5.5.2. フレームワークの起動

CGI フレームワークの起動は、Seasar::CGI.run メソッドを呼びます。 public/cgiディレクトリに次のような quick1.cgiを作成します。

% cat public/cgi/quick1.cgi
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname(__FILE__) + '/../../config/cgi.rb'
Seasar::CGI.run
%
次に、tpl/cgi ディレクトリに quick1.cgiのerbテンプレートファイルを「quick1.html」として作成します。
% cat tpl/cgi/quick1.html
Hello World
%
ブラウザで、http://localhost/s2cgi/cgi/quick1.cgi にアクセスすると、「Hello World」 と表示されます。

5.5.3. Seasar::CGI::Pageクラス

Seasar::CGI.run メソッドには、引数でSeasar::CGI::Pageクラスのインスタンスを渡すことができます。 public/cgiディレクトリに次のような quick2.cgi を作成します。

% cat public/cgi/quick2.cgi
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname(__FILE__) + '/../../config/cgi.rb'

class Page < Seasar::CGI::Page; end
Seasar::CGI.run(Page.new)
%
次に、tpl/cgi ディレクトリに quick2.cgiのerbテンプレートファイルを「quick2.html」として作成します。
% cat tpl/cgi/quick2.html
Hello World 2
%
ブラウザで、http://localhost/s2cgi/cgi/quick2.cgi にアクセスすると、「Hello World 2」 と表示されます。

Seasar::CGI.run メソッドは、引数が nil の場合、S2ContainerからSeasar::CGI::Pageクラスのコンポーネントを取得します。 コンテナにSeasar::CGI::Pageクラスのコンポーネントが存在しない場合は、Seasar::CGI::Pageクラスのインスタンスを自動生成して使用します。 (上述の「フレームワークの起動」の例になります。)

Seasar::CGI::Pageクラスは、デフォルト処理として、tpl ディレクトリ以下のerbテンプレートファイルからコンテンツを生成します。 erbテンプレートファイルへのパスは、SCRIPT_NAMEのBASE_URL以下のパスが使用されます。「/s2cgi/cgi/quick2.cgi」の場合は、 「tpl/cgi/quick2.html」がerbテンプレートファイルとなります。

S2ContainerからPageコンポーネントを取得する場合は、次のようなCGIスクリプトになります。

% cat public/cgi/quick3.cgi
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname(__FILE__) + '/../../config/cgi.rb'

class Page < Seasar::CGI::Page
  s2comp
end
Seasar::CGI.run
%

5.5.4. Class Variables

Seasar::CGI::Pageクラスのクラス変数を次に示します。

@@fatal. 

フレームワーク起動後にエラーが発生した場合に、実行される手続きオブジェクトを設定します。手続きオブジェクトには、 キャッチされたエラーインスタンスとSeasar::CGI::Pageクラスのインスタンスが渡されます。

デフォルトでは、config/cgi.rb内で次のように設定されています。

Seasar::CGI::Page.fatal {|e, page|
  s2logger.fatal() {"#{e.message} #{e.backtrace}"}
  print "Location: #{BASE_URL}fatal.html\n\n";
}

@@tpl_dir. 

erbテンプレートファイルを保存するディレクトリへのパスを設定します。デフォルトではプロジェクトディレクトリの tplディレクトリが設定されています。

@@tpl_ext. 

erbテンプレートファイルの拡張子を設定します。デフォルトは「html」です。


5.5.5. Instance Variables

Seasar::CGI::Pageクラスのインスタンス変数を次に示します。get/postメソッドの処理において使用できます。

@cgi. 

Ruby添付ライブラリのCGIインスタンスを保持します。

@contents. 

レスポンスコンテンツを保持する変数です。puts, p, renderメソッドの結果文字列が追記されます。

@auto_render. 

get/postメソッドの呼び出し後に、@contentsのサイズが0の場合に、自動的にrenderメソッドを呼ぶかどうかを Booleanで設定します。

@auto_response. 

get/postメソッドの呼び出し後に、@contentsを、@cgi.outで出力するかどうかをBooleanで設定します。

@headers. 

auto_response時に、@cgi.outで出力するHTTPヘッダをHashで保持します。


5.5.6. get/postメソッド

Pageクラスでは、WEB層に関する次の処理を行います。

  • ページ遷移に関する処理 ( redirect メソッドなど )
  • erb テンプレートに関する処理 ( render/partial メソッドなど )
  • リクエストパラメータに関する処理 ( バリデーション関連 )
  • セッションに関する処理
  • サービスやロジックの呼び出し
  • その他、HTTPヘッダに関する処理など

Pageクラスにgetメソッドを定義すると、リクエストメソッドがGETの場合に呼ばれます。postメソッドも同様に、リクエストメソッドが POSTの場合に実行されます。次のようなCGIスクリプトを作成します。

% cat public/cgi/quick5.cgi
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname(__FILE__) + '/../../config/cgi.rb'

class Page < Seasar::CGI::Page
  s2comp
  def get
    puts 'Hello World 5'
  end
end
Seasar::CGI.run
%

ブラウザで、http://localhost/s2cgi/cgi/quick5.cgi にアクセスすると、「Hello World 5」 と表示されます。 putsメソッドとpメソッドは、Seasar::CGI::Pageクラスでオーバーライドされています。それぞれ、引数で渡されたオブジェクトの 文字列表現とinspect結果をレスポンスコンテンツに追加します。レスポンスコンテンツのサイズが0でない場合は、erbテンプレートファイルの 自動読み込みは行われないため、上記サンプルでは、erbテンプレートファイルを用意する必要はありません。


5.5.7. paramメソッド

Seasar::CGI::Page#paramメソッドは、引数で指定されたキー名のリクエストパラメータを返します。リクエストパラメータに キーが存在しない場合はnilを返します。キーが複数存在する場合は配列を返します。

param(name)
  • 第1引数: リクエストパラメータのキー名

paramメソッドは、 リクエストパラメータの検証を行う場合にも使用します。検証設定についてはこちらを参照下さい。


5.5.8. renderメソッド

Seasar::CGI::Page#renderメソッドはerbテンプレートファイルを処理し、結果文字列を@contentsに追記します。引数でerbテンプレートファイルを指定することができます。 erbテンプレートファイルの指定は、@@tpl_dirで指定されるディレクトリ以下の相対パスで指定します。「@@tpl_dir/cgi/quick6.html」 テンプレートファイルを指定する場合は、次のようになります。

render 'cgi/quick6'

引数を省略した場合は、SCRIPT_NAMEからBASE_URLを除いた相対パスが使用されます。SCRIPT_NAMEが「/s2cgi/cgi/quick6.cgi」、BASE_URLが「/s2cgi」の場合は、 テンプレートファイルは「cgi/quick6」となります。テンプレートファイルの拡張子には、@@tpl_ext値が使用されます。

renderメソッドは、レイアウトファイルを自動検索します。レイアウトファイルが見つかった場合は、テンプレートファイルの処理を行いません。 レイアウトファイルがテンプレートファイルとして処理されます。レイアウトファイルの検索は、テンプレートファイルに「_layout」を付加したファイルが 存在するかを確認します。存在しない場合は、テンプレートファイルと同じディレクトリに「layout」というファイルが存在するかを確認します。 テンプレートファイルが「cgi/quick6」の場合は、まず「cgi/quick6_layout」が存在するかを確認し、存在しない場合は、「cgi/layout」 が存在するかを確認します。


5.5.9. partialメソッド

Seasar::CGI::Page#partialメソッドは、テンプレートファイルを処理し、文字列を返します。引数でerbテンプレートファイルを指定できます。レイアウトファイル内でメインコンテンツの テンプレートを読み込んだり、テンプレートファイルを部分的に分割したりする場合に使用します。レイアウトファイル内でメインコンテンツのテンプレートファイルを指定 する場合は、@templateを引数に指定して下さい。

<!-- レイアウトファイル -->
<%= partial(@template) %>   # メインコンテンツの読み込み

5.5.10. redirectメソッド

Seasar::CGI::Page#redirectメソッドは、引数で指定されたURLをHTTPヘッダのLocationに設定し、リダイレクトを実施します。 リダイレクトを実施後、exitを実行しCGIスクリプトを終了します。第二引数でリダイレクトする際のリクエストパラメータを Hashで設定できます。

redirect('http://xxx.com/index.cgi', :year => 2009)

5.5.11. validateメソッド

Seasar::CGI::Page#validateメソッドは、リクエストパラメータの検証を行う処理を含むブロックを受け取ります。第一引数では、 :all、:get、:postを指定し、どのリクエストメソッドのアクセス時に検証を行うかを指定することができます。 戻り値としてBooleanを返します。戻り値がFalseの場合は、リクエストメソッドに対応するget/postメソッドの呼び出しが行われません。

class Page < Seasar::CGI::Page
  validate {         # :allと同じ。 GET、POST リクエストの場合に実行される
    param :year, :numeric
    valid?
  }

  validate(:get) {   # GET リクエストの場合に実行される
    param :year, :numeric
    valid?
  }

  validate(:post) {  # POST リクエストの場合に実行される
    param :year, :numeric
    valid?
  }
end

validateメソッドに渡されるブロック内では、リクエストパラメータを検証するために次のメソッドが使用できます。

param メソッド. 

param(name, type = nil, options = nil)
  • 第1引数: リクエストパラメータのキー名
  • 第2引数: バリデーションタイプ
  • 第3引数: 検証メソッドに渡されるオプション

リクエストパラメータを nameで指定し、適用する検証メソッドを typeで指定します。一つのリクエストパラメータに複数の バリデーションタイプを登録することができます。


valid? メソッド. 

valid?(name = nil, type = nil)
  • 第1引数: リクエストパラメータのキー名
  • 第2引数: バリデーションタイプ
  • 戻り値: Boolean

引数の name と type に合致する登録エントリがすべてValidな場合にTrueを返します。1つでもErrorが ある場合は、Falseとなります。引数 name がnilの場合は、typeに合致する検証エントリが対象となります。 引数typeがnilの場合は、nameに合致する検証エントリが対象となります。nameおよびtypeが共にnilの場合は、 すべての登録エントリが対象となります。


valids メソッド. 

valids(name = nil, type = nil)
  • 第1引数: リクエストパラメータのキー名
  • 第2引数: バリデーションタイプ
  • 戻り値: Seasar::Validate::Entryインスタンスの配列

引数の name と type に合致する登録エントリのうち、検証結果がValidなエントリを配列で返します。


error? メソッド. 

error?(name = nil, type = nil)
  • 第1引数: リクエストパラメータのキー名
  • 第2引数: バリデーションタイプ
  • 戻り値: Boolean

引数の name と type に合致する登録エントリのうち、1つでもErrorがある場合にTrueを返します。 すべてのエントリがValidな場合はFalseを返します。

次のように、erbテンプレートファイル内で使用することができます。

username : <input type="text" name="username" value=""/>
<% if error?(:username) %><span class="err_msg">ユーザ名を4~8文字で入力して下さい。</span><% end %>

errors メソッド. 

errors(name = nil, type = nil)
  • 第1引数: リクエストパラメータのキー名
  • 第2引数: バリデーションタイプ
  • 戻り値: Seasar::Validate::Entryインスタンスの配列

引数の name と type に合致する登録エントリのうち、検証結果がErrorのエントリを配列で返します。


if_errors メソッド. 

if_errors(name = nil, type = nil, &procedure)
  • 第1引数: リクエストパラメータのキー名
  • 第2引数: バリデーションタイプ
  • 第3引数: ブロック

引数の name と type に合致する登録エントリのうち、1つでもErrorがある場合に、ブロック引数にすべてのエラーエントリ配列を 渡して呼び出します。

次のように、erbテンプレートファイル内で使用することができます。

<% if_errors do |errors| %>
  <%=h errors.size %>件の問題が発生しました。
<% end %>

5.5.11.1. バリデーションタイプ

バリデーションタイプごとに使用されるメソッドは、Seasar::Validate::Utilsモジュールに定義されているメソッドが 使用されます。その際、メソッド名は、バリデーションタイプに"?"を付加した名前になります。バリデーションタイプが「:int」の 場合は、メソッド名は「int?」とになります。
また、Seasar::Validate::Utils::Validators Hashに登録されている手続きオブジェクトも使用されます。 バリデーションタイプで指定されるSymbolがHashキーとして使用されます。


:int. 

Integerかどうかを検証します。
使用されるメソッドは、Seasar::Validate::Utils.int? です。
オプションはHashで指定します。(省略可)

  • :min => 最小値
  • :max => 最大値
  • :include => :min、:maxで指定される数値を含むかどうか。デフォルトはTrueです。
  • :required => リクエストパラメータが存在するかどうか。デフォルトはTrueです。
paramメソッドの例は次になります。
param :year, :int, :min => 2000, :max => 2050

:numeric. 

数値文字列かどうかを検証します。
使用されるメソッドは、Seasar::Validate::Utils.numeric? です。
オプションはHashで指定します。(省略可)

  • :min => 最小値
  • :max => 最大値
  • :include => :min、:maxで指定される数値を含むかどうか。デフォルトはTrueです。
  • :required => リクエストパラメータが存在するかどうか。デフォルトはTrueです。
paramメソッドの例は次になります。
param :year, :int, :min => 2000, :max => 2050

:string. 

Stringかどうかを検証します。
使用されるメソッドは、Seasar::Validate::Utils.string? です。
オプションはHashで指定します。(省略可)

  • :min => 最短文字数
  • :max => 最長文字数
  • :include => :min、:maxで指定される数値を含むかどうか。デフォルトはTrueです。
  • :required => リクエストパラメータが存在するかどうか。デフォルトはTrueです。
paramメソッドの例は次になります。
param :name, :string, :min => 6, :max => 8

:array. 

配列かどうかを検証します。
使用されるメソッドは、Seasar::Validate::Utils.array? です。
オプションはHashで指定します。(省略可)

  • :min => 最小メンバ数
  • :max => 最大メンバ数
  • :include => :min、:maxで指定される数値を含むかどうか。デフォルトはTrueです。
  • :required => リクエストパラメータが存在するかどうか。デフォルトはTrueです。
paramメソッドの例は次になります。
param :names, :array, :min => 6, :max => 8

:member, :in. 

オプションで指定する配列のメンバかどうかを検証します。
使用されるメソッドは、Seasar::Validate::Utils.member? です。
オプションはHashまたはArrayで指定します。

  • :items => メンバ配列
  • :required => リクエストパラメータが存在するかどうか。デフォルトはTrueです。
paramメソッドの例は次になります。
param :names, :member, %w[foo bar hoge huga]

:regexp. 

オプションで指定するRegexpにマッチするかどうかを検証します。
使用されるメソッドは、Seasar::Validate::Utils.regexp? です。
オプションはHashまたはRegexpで指定します。

  • :regexp => Regexp
  • :required => リクエストパラメータが存在するかどうか。デフォルトはTrueです。
paramメソッドの例は次になります。
param :name, :regexp, /^abc/

:alpha. 

アルファベット文字列かどうかを検証します。
使用されるメソッドは、Seasar::Validate::Utils.alpha? です。
オプションはHashで指定します。(省略可)

  • :case => :down または、:up
  • :required => リクエストパラメータが存在するかどうか。デフォルトはTrueです。
paramメソッドの例は次になります。
param :name, :alpha, :case => :down

5.5.11.2. validate_get/validate_postメソッド

Validationメソッドで検証ブロックを登録する代わりに、GETアクセスに対してはvalidate_getメソッド、 POSTアクセスに対してはvalidate_postメソッドを定義して、バリデーションを実施することもできます。

class Page < Seasar::CGI::Page
  def validate_get   # GET リクエストの場合に実行される
    param :year, :numeric
    valid?
  end

  def validate_post  # POST リクエストの場合に実行される
    param :year, :numeric
    valid?
  end
end

5.5.12. セッション管理

S2CGIのセッションは、Ruby添付ライブラリのCGI::Session を使用します。newメソッドの引数で渡すオプションは、Seasar::CGI::Session.optionsに設定されているHash値が使用されます。 デフォルトでは、config/cgi.rb内で次のように設定されています。

Seasar::CGI::Session.options = {'tmpdir' => SESSION_DIR, 'database_manager' => CGI::Session::PStore}

Seasar::CGI::Page#get_session メソッド. 

CGI::Sessionを生成して返します。セッションが開始されていない場合は、nilを返します。

Seasar::CGI::Page#start_session メソッド. 

CGI::Sessionの新規インスタンスを返します。


5.5.13. Dependency Injection

CGIスクリプト内で定義するPageクラスをコンポーネント登録すると、Seasar::CGI.runメソッドは、登録されたPageコンポーネントを S2Containerから取得して実行します。Pageコンポーネントに依存するコンポーネントのインジェクション設定を行うことで、DIを実施できます。

lib/exampleディレクトリに次のようなサービスクラスを作成します。

% cat lib/example/some-service.rb
module Example
  class SomeService
    s2comp
    def add(a, b)
      return a + b
    end
  end
end

次のようなCGIスクリプトを作成します。Pageクラスでは、SomeServiceインスタンスを受け取る「:some_service」 アクセッサメソッドを定義します。

% cat public/cgi/quick6.cgi
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname(__FILE__) + '/../../config/cgi.rb'
require 'example/some-service'
class Page < Seasar::CGI::Page
  s2comp
  attr_accessor :some_service
  def get
    puts '1 + 2 = ' + @some_service.add(1, 2).to_s
  end
end
Seasar::CGI.run
%

ブラウザで、http://localhost/s2cgi/cgi/quick6.cgi にアクセスすると、「1 + 2 = 3」 と表示されます。


5.5.14. Unit Test

libディレクトリに作成するサービスやロジッククラスのUnitTestはtestディレクトリに作成します。
testディレクトリに作成したUnitTestは、test-suite.rbで実行できます。

% ruby test/test-suite.rb
Loaded suite .
Started
...
Finished in 0.001 seconds.

3 tests, 8 assertions, 0 failures, 0 errors
%

5.5.15. Rack CGI

5.5.15.1. フレームワークの起動

Rack CGI フレームワークの起動は、Seasar::Rack::CGI.run メソッドを呼びます。 public/rackディレクトリに次のような quick1.cgiを作成します。

% cat public/rack/quick1.cgi
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname(__FILE__) + '/../../config/rack.rb'
Seasar::Rack::CGI.run
%
次に、tpl/rack ディレクトリに quick1.cgiのerbテンプレートファイルを「quick1.html」として作成します。
% cat tpl/rack/quick1.html
Hello World
%
ブラウザで、http://localhost/s2cgi/rack/quick1.cgi にアクセスすると、「Hello World」 と表示されます。

5.5.15.2. Seasar::Rack::CGI::Pageクラス

Seasar::Rack::CGI.run メソッドには、引数でSeasar::Rack::CGI::Pageクラスのインスタンスを渡すことができます。 public/rackディレクトリに次のような quick2.cgi を作成します。

% cat public/rack/quick2.cgi
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname(__FILE__) + '/../../config/rack.rb'

class Page < Seasar::Rack::CGI::Page; end
Seasar::Rack::CGI.run(Page.new)
%
次に、tpl/rackディレクトリに quick2.cgiのerbテンプレートファイルを「quick2.html」として作成します。
% cat tpl/cgi/quick2.html
Hello World 2
%
ブラウザで、http://localhost/s2cgi/rack/quick2.cgi にアクセスすると、「Hello World 2」 と表示されます。

Seasar::Rack::CGI.run メソッドは、引数が nil の場合、S2ContainerからSeasar::Rack::CGI::Pageクラスのコンポーネントを取得します。 コンテナにSeasar::Rack::CGI::Pageクラスのコンポーネントが存在しない場合は、Seasar::Rack::CGI::Pageクラスのインスタンスを自動生成して使用します。


5.5.15.3. Instance Variables

@env. 

Seasar::Rack::CGI::Page#callに渡されるRack環境引数

@request. 

Rack::Requestのインスタンスを保持します。

@response. 

Rack::Responseのインスタンスを保持します。@response.finishメソッドは、get/postメソッドの呼び出し後に自動的に実行されます。

@auto_render. 

get/postメソッドの呼び出し後に、@response.body.size が 0、 @response.status が 200 の場合に、 自動的にrenderメソッドを呼ぶかどうかをBooleanで設定します。


5.5.15.4. Rack Up

Rack Up を行う手続きオブジェクトをSeasar::Rack::CGI::Page.rack クラス変数に設定します。デフォルト値として、config/rack.rbで 次のように設定されています。
Seasar::Rack::CGI::Page.rack {|page|
  Rack::Builder.app do
    use Seasar::Rack::CGI::Stdin2StringIO
    use Rack::ShowStatus
    use Rack::ShowExceptions
    use Rack::MethodOverride
    use Rack::ContentLength
    use Rack::Session::Cookie
    run page
  end
}

Rack Up 手続きオブジェクトは、Seasar::Rack::CGI.runメソッド内で実行されます。 その際、S2Containerから取得されたPageインスタンスや新規生成されたPageインスタンスが引数として渡されます。
また、Rack Up 手続きオブジェクトは、Seasar::Rack::CGI.runメソッドのブロック引数としても設定できます。

% cat public/rack/quick3.cgi
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname(__FILE__) + '/../../config/rack.rb'
class Page < Seasar::Rack::CGI::Page
  s2comp
  def get
    puts 'Hello World 3'
  end
end
Seasar::Rack::CGI.run {|page|
  Rack::Builder.app do
    use Seasar::Rack::CGI::Stdin2StringIO
    run page
  end
}
%


© Copyright The Seasar Foundation and the others 2008-2009, all rights reserved.