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