Hatena::ブログ(Diary)

vivid memo このページをアンテナに追加 RSSフィード

vivid code というサイトのメモ代わりに記事を書いていました。
現在ははてなブログに移行し、「ひだまりソケットは壊れない」 というブログで記事を書いています。 はてな id も id:nobuoka に変更しました。

2012-03-15

Ruby での Test::Unit や MiniTest::Unit を使った複数のファイルのテスト

Perl で複数のテストを実行する場合は prove コマンドがあるけれど、Ruby の場合はどうするのがいいんだろうなー。 と思って調べてみました。

Rubyベストプラクティス -プロフェッショナルによるコードとテクニック』 によると、Rake::TestTask を使うのが良いようです。 以下のような rakefile を書いて、あとは rake test で実行、と。

# coding: UTF-8

require 'rake/testtask'

task :default => [:test]

Rake::TestTask.new do |test|
  # $LOAD_PATH に追加するパス (デフォルトで 'lib' は入っている)
  test.libs << 'test'
  # テスト対象ファイルの指定
  test.test_files = Dir[ 'test/**/test_*.rb' ]
  test.verbose = true
end

このときのフォルダ構成は以下のようになります。

Project --+- lib -----+- ...
          |           +- ...
          |
          +- test ----+- test_main.rb
          |           +- test_aaa.rb
          |           +- ...
          |
          +- rakefile

Project フォルダにいる状態で、以下のようにコマンドをたたくと、test ディレクトリ内の "test_" ではじまるファイル名のファイルが全て実行されます。

$ rake test

2011-05-05

WEBrick の ProcHandler で DELETE メソッドや PUT メソッドを扱えるようにする

WEBrick で HTTP サーバーを作ってリクエストを WEBrick::HTTPServlet::ProcHandler オブジェクトで処理する、ということをしていたのですが、DELETE メソッドを投げると 405 エラーが返ってきてしまうという問題に直面しました。 レスポンスボディは以下のような感じでした。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<HTML>
  <HEAD><TITLE>Method Not Allowed</TITLE></HEAD>
  <BODY>
    <H1>Method Not Allowed</H1>
    unsupported method `DELETE'.
    <HR>
    <ADDRESS>
     WEBrick/1.3.1 (Ruby/1.9.2/2011-02-18) at
     localhost
    </ADDRESS>
  </BODY>
</HTML>

Ruby 1.9.2-p180 に添付されている webrick の WEBrick::HTTPServlet::ProcHandler は、デフォルトでは HEAD メソッドと GET メソッドと POST メソッドと OPTIONS メソッドしか処理しないようです。

DELETE メソッドと PUT メソッドも受け取って欲しかったので、以下のように WEBrick::HTTPServlet::ProcHandler クラスにインスタンスメソッドを追加して対応しました。 こういうときはオープンクラスであることは非常に便利でいいですね。 (オープンクラスじゃなくてもサブクラスを作ったりして対応はできますが。。)

##
# WEBrick::HTTPServlet::ProcHandler が DELETE メソッドや PUT メソッドを認識しないため
# 認識するように変更するための拡張
module WEBrick
  module HTTPServlet

    class ProcHandler < AbstractServlet
      alias do_PUT    do_GET
      alias do_DELETE do_GET
    end

  end
end

参考文献

2011-04-25

WEBrick サーバー (Ruby による web サーバー) を安全に停止する方法とデーモン化する方法

Ruby には、WEBrick という HTTP サーバーのフレームワークが標準添付ライブラリとして同梱されています。 Ruby on Rails などにも使われているようです。

で、私も web アプリケーションの開発に WEBrick を使ってみようと思ったのですが、WEBrick サーバーをきちんと停止させる方法がよくわからず結構悩んでしまいました。。 というわけで私がどういうことに悩み、結局どういう方法にたどり着いたのかを書いておきます。

また、デーモン化して動作させる方法についても記します。

WEBrick::GenericServer#shutdown メソッドでサーバーの動作を停止させる?

WEBrick のサーバーを動かすときには WEBrick::GenericServer#start メソッドを使用します。 それに対応するメソッドを調べると、WEBrick::GenericServer#shutdown メソッドが見つかりました。 このメソッドを使えばサーバーをきちんと停止させられそうです。

と思ったものの、以下のようなコードを書いても全然終了しません!

#! ruby-1.9.2 -EUTF-8:UTF-8
# coding: UTF-8

require 'webrick'

PROG_DIR_NAME = File.dirname( File.expand_path( __FILE__ ) )

# HTTP サーバーのインスタンス化
srv = WEBrick::HTTPServer.new( 
            DocumentRoot: PROG_DIR_NAME,
            BindAddress:  '127.0.0.1',
            Port:         10080 )

# サーバーを開始し, 直後に停止させるつもり
srv.start()
srv.shutdown() # しかしここに到達しない

上記コードを実行すると、サーバーの動作開始のメッセージは出力されますが、停止はしません。 詳しく調べるとわかるのですが、srv.shutdown() まで到達していないようです。

WEBrick::HTTPServer#start メソッドは内部でループをまわし続けている

ソースコードを読めば分かるのですが、WEBrick::GenericServer#start メソッドは外部からの接続を待つために永遠にループし続けるというプログラムになっています。 WEBrick::GenericServer#shutdown メソッドを実行すると、インスタンスの状態が変更されて WEBrick::GenericServer#start メソッド内のループを抜けるようになるのですが、上で示したコードでは srv.start() が終了してから srv.shutdown() を呼び出すというプログラムになっているので、うまくいかなかったわけです。

よって、srv.start() を実行しながら srv.shutdown() を実行する必要があります。 すなわち、別スレッドで実行するなどの方法をとらなければいけません。 今、行いたいことはプログラムの動作を止めることなので、最も自然な方法は TERM シグナルまたは INT シグナルをトラップして、srv.shutdown() を実行することです。

#! ruby-1.9.2 -EUTF-8:UTF-8
# coding: UTF-8

require 'webrick'

PROG_DIR_NAME = File.dirname( File.expand_path( __FILE__ ) )

# HTTP サーバーのインスタンス化
srv = WEBrick::HTTPServer.new( 
            DocumentRoot: PROG_DIR_NAME,
            BindAddress:  '127.0.0.1',
            Port:         10080 )

# シグナルをトラップして終了処理を行うように設定
shutdown_proc = ->( sig ){ srv.shutdown() }
[ :INT, :TERM ].each{ |e| Signal.trap( e, &shutdown_proc ) }
# サーバーを開始
srv.start()

このように書くことで、この ruby プログラムを実行している端末で Ctrl-C と入力してサーバープログラムを停止させたり、kill コマンドでプロセスに TERM シグナルや INT シグナルを送ることでサーバープログラムを停止させることができます。

デーモン化する

サーバーとして動作させるためには、多くの場合デーモン化させる必要があるかと思います。 WEBrick サーバーをデーモン化するには、サーバーのコンストラクタに渡すオプションに、ServerType: WEBrick::Daemon を渡すだけでおーけーです。

その他サーブレットのマウントなども行い、テスト用の簡単なプログラムを書くとこんな感じになりました。

#! ruby-1.9.2 -EUTF-8:UTF-8
# coding: UTF-8

require 'webrick'

PROG_DIR_NAME = File.dirname( File.expand_path( __FILE__ ) )

# HTTP サーバーのインスタンス化
srv = WEBrick::HTTPServer.new( 
            BindAddress:  '127.0.0.1',
            Port:         10080,
            ServerType:   WEBrick::Daemon,
            Logger:       WEBrick::Log.new( "#{PROG_DIR_NAME}/log.txt", WEBrick::Log::DEBUG ) )

# /resources に対しては単純にファイルハンドラを設定
# それ以外の場合はリクエストメソッドと URI を表示する処理を設定
srv.mount( '/resources', WEBrick::HTTPServlet::FileHandler, "#{PROG_DIR_NAME}/resources" )
srv.mount_proc( '/' ) do |req, res|
  res.content_type = 'text/plain; charset=UTF-8'
  res.body << "request_uri : #{req.request_uri}\n"
  res.body << "request_path : #{req.path_info}\n"
  res.body << "request_method : #{req.request_method}\n"
end

# シグナルをトラップして終了処理を行うように設定
shutdown_proc = ->( sig ){ srv.shutdown() }
[ :INT, :TERM ].each{ |e| Signal.trap( e, &shutdown_proc ) }

# サーバーの動作開始
srv.start()

このプログラムを実行して HTTP サーバーを立ち上げた後、web ブラウザで http://localhost:10080/ にアクセスすると WEBrick サーバーが応答を返してくれるはずです。 また、終了するためには以下のように ps コマンドで pid を調べ、kill してください。

$ ps aux | grep ruby
nobuoka   6167  0.6  0.8  57096  8880 ?        Sl   22:59   0:01 ruby webrick_test.rb
nobuoka   6181  0.0  0.0   7188   928 pts/0    S+   23:02   0:00 grep --color=auto ruby
$ kill 6167

参考文献

2011-04-21

Ruby におけるバイナリ文字列に対する正規表現マッチング

Ruby 1.9 系において、バイナリ文字列 (ASCII-8BIT の String オブジェクト) に対する正規表現マッチングをさせる方法について記します。 Ruby 1.9 系において URL デコードを行う際などに役に立ちます。

正規表現リテラルの n オプション

正規表現リテラルの後ろに n と書くことで、その正規表現をバイナリ扱い (エンコーディング ASCII-8BIT) にすることができます。 ASCII-8BIT は ASCII 互換なので、ASCII 文字列を中身に書くことができます。

regexp = /abcd/n

しかし、ASCII に含まれないものを中に入れるとエラーになります。

regexp = /abcdあ/n

この結果は以下のようになります。

SyntaxError: (irb):1: regexp encoding option 'n' differs from source encoding 'UTF-8'
/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /abcdあ/
        from /usr/local/bin/irb:12:in `<main>'

じゃあどうすればいいかというと、16 進表記のエスケープ文字 *1 を使います。

regexp = /abcd\xEE\xF0/n

URI デコードを行う関数

まあ ASCII-8BIT の正規表現はあんまり使い道はないと思いますが、例えば URI デコードなど、文字列のエンコードやデコードの際には役に立ちます。 ここに URI デコードの例を書いておきます。

##
# URI デコードを行う
#
# デコード対象になるのは文字列に含まれる '%XX' 形式の全ての文字.
# デコード対象の文字が妥当かどうか (16 進数表記に合致するか) やデコード後の文字列の妥当性
# (文字エンコーディングが正しいか) などのチェックはしない. 
# デコード後の文字列のエンコーディングは第 2 引数で指定. (デフォルトは UTF-8)
def uridec( str, enc = Encoding::UTF_8 )
  # 与えられた文字列を ASCII-8BIT 扱いにし、ASCII-8BIT の正規表現でマッチングする
  str.force_encoding(Encoding::ASCII_8BIT).gsub( /%../n ) do |s|
    [s[1,2].to_i(16)].pack('C')
  end.force_encoding(enc) # 指定のエンコーディングにして返す
end

*1:正式名称がよくわからないのでこう書いてます。。 "\xXX" 形式のアレです。

2011-04-13

Ruby においてメソッドがどこで定義されているのかを調べる方法

ruby-talk の 381425 で 「どのメソッドが呼び出されているのか調べる方法を知りたい」 という内容の質問がありました。 私も知らなかったのですが、Method オブジェクトを使えば簡単に知ることができるそうです。

というわけで、メソッドがどこで定義されているのかを調べる方法を書いておきます。

メソッドを定義しているクラスまたはモジュールへの参照を取得する

あるメソッドがどのクラス (またはモジュール) で定義されているかを知りたければ、そのメソッドに対応する Method オブジェクトを取得し、Method#owner メソッド を呼び出します。 それだけで、メソッドを定義しているクラス (またはモジュール) への参照を取得できます。

ちなみに、ある名前のメソッドに対応する Method オブジェクトを取得するには、Kernel#method メソッド を使用します。

'あい'.method( :to_s )
  #=> #<Method: String#to_s>
  # String オブジェクトの to_s メソッドに対応する Method オブジェクトを取得

以下に、指定したメソッドを定義しているクラスまたはモジュールへの参照を取得する方法の例を示します。

# 例として用いる Test クラスを生成
class Test
  # インスタンスメソッド
  def do_i
  end
  # クラスメソッド
  def self.do_c
  end
end

# == Test クラスの do_c クラスメソッドがどこで定義されているか調べる ==
# Test クラスの do_c メソッドに対応する Method オブジェクトを取得
m = Test.method( :do_c )
  #=> #<Method: Test.do_c>
# Method#owner メソッドの呼び出し
m.owner
  #=> #<Class:Test>
  # Test クラスの do_c クラスメソッドは Test クラスオブジェクトの
  # 固有クラスで定義されていることがわかる

# == Test オブジェクトの do_i メソッドがどこで定義されているか調べる ==
# Test オブジェクトの do_i メソッドに対応する Method オブジェクトを取得
m = Test.new().method( :do_i )
  #=> #<Method: Test#do_i>
# Method#owner メソッドの呼び出し
m.owner
  #=> Test
  # Test オブジェクトの do_i メソッドは Test クラスで
  # 定義されていることが分かる

# == Test オブジェクトの to_s メソッドがどこで定義されているか調べる ==
# Test オブジェクトの to_s メソッドに対応する Method オブジェクトを取得
m = Test.new().method( :to_s )
  #=> #<Method: Test(Kernel)#to_s>
# Method#owner メソッドの呼び出し
m.owner
  #=> Kernel
  # Test オブジェクトの to_s メソッドは Kernel モジュールで
  # 定義されていることがわかる

Ruby 1.8.7 および Ruby 1.9.2 で動作することを確認しました。

どのファイルで定義されているのか?

Ruby 1.9.2 には Method#source_location メソッド があり、これを使えばどのファイルの何行目でメソッドが定義されているかを調べることができます。 (Ruby コードで書かれている場合。 ネイティブコードなどでは nil になります。) 残念ながら Ruby 1.8 にはないようです。