はじめに
前回はリクエストに対する処理(getで書いたブロック)がどう登録されるのかを見てきました。今回は起動編、つまり、
ruby myapp.rb
とした場合に何が起こっているのかを見ていきます。
あらためてmyapp.rbを見てみましょう。
require 'sinatra'
get '/' do
'Hello world!'
end
myapp.rbにはサーバを起動するコードは書かれていません。が、先の通りにmyapp.rbを実行するとサーバが起動します。今回はこのからくりを探るのが目的です。
Sinatra::Application(sinatra/main.rbの方)
sinatra/main.rbに書かれているコードのうち前回無視した方を見てみます。
module Sinatra
class Application < Base
# we assume that the first file that requires 'sinatra' is the
# app_file. all other path related options are calculated based
# on this path by default.
set :app_file, caller_files.first || $0
set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) }
# オプション処理
end
at_exit { Application.run! if $!.nil? && Application.run? }
end
setやcaller_files等はSinatra::Baseに書かれていますが大体予想通りのものなので省略します。
上記のコードで注目すべきは下から2行目、at_exitです。リファレンスにあるようにこのメソッドで登録したブロックはインタプリタが終了するときに実行されます。これが「サーバ起動を書いてないのにサーバが起動するからくり」です。
Sinatra::Base.run!
以上終わり、となるわけはもちろんなく、Application.run!の中に入っていきます。前回も見たようにApplicationにはあまり処理は書かれておらず、run!メソッドはBaseクラスの方に書かれています。
# Run the Sinatra app as a self-hosted server using
# Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
# with the constructed handler once we have taken the stage.
def run!(options = {}, &block)
return if running?
set options
handler = detect_rack_handler
handler_name = handler.name.gsub(/.*::/, '')
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
server_settings.merge!(:Port => port, :Host => bind)
begin
start_server(handler, server_settings, handler_name, &block)
rescue Errno::EADDRINUSE
$stderr.puts "== Someone is already performing on port #{port}!"
raise
ensure
quit!
end
end
detect_rack_handlerと関連コード
detect_rack_handlerはrun!のコメントに書かれているようなことが行われているわけですがRubyに慣れてないとわかりにくいと思うので説明します。
def detect_rack_handler
servers = Array(server)
servers.each do |server_name|
begin
return Rack::Handler.get(server_name.to_s)
rescue LoadError, NameError
end
end
fail "Server handler (#{servers.join(',')}) not found."
end
「で、serverはどうなってるの?」と探すと以下のようになっています。
set :server, %w[HTTP webrick]
if ruby_engine == 'macruby'
server.unshift 'control_tower'
else
server.unshift 'reel'
server.unshift 'puma'
server.unshift 'mongrel' if ruby_engine.nil?
server.unshift 'thin' if ruby_engine != 'jruby'
server.unshift 'trinidad' if ruby_engine == 'jruby'
end
run!のコメントと違ってるようなw
それはともかく、このsetやif文がどこに書かれているかというと、SinatraモジュールのBaseクラス直下です。
Rubyは(Pythonもですが)「クラス定義に普通の実行文が書ける。それらの実行文はクラスが定義される際に実行される(そもそもメソッド定義なども実行文である)」という特徴があります。ここら辺がJavaなどとは大きく異なる点です。
start_server
サーバの起動はstart_serverメソッドで行われます。
ここで重要なのはRackハンドラに渡しているのはApplicationクラスオブジェクト1という点です。つまり、リクエストが届いてハンドラから呼び出される場合、Applicationクラスのクラスメソッドのcallが呼び出されることになります。call後の流れについては詳しくは処理編で説明します。
# Starts the server by running the Rack Handler.
def start_server(handler, server_settings, handler_name)
# Ensure we initialize middleware before startup, to match standard Rack
# behavior, by ensuring an instance exists:
prototype
# Run the instance we created:
handler.run(self, server_settings) do |server|
unless suppress_messages?
$stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
end
setup_traps
set :running_server, server
set :handler_name, handler_name
server.threaded = settings.threaded if server.respond_to? :threaded=
yield server if block_given?
end
end
prototypeメソッド
start_serverでは初めにprototypeというメソッドが呼び出されています。起動編の最後にこのメソッドについて見てみましょう。
# The prototype instance used to process requests.
def prototype
@prototype ||= new
end
prototypeはクラスメソッドなので、newとはつまり、「Application.new」ということです。
「つまり、ここでApplicationクラスのインスタンスが作られてるんだな」と普通は考えると思いますが、話はもう少し複雑です(複雑でなければ取り上げません)
以下のコードを見てください。
# Create a new instance without middleware in front of it.
alias new! new unless method_defined? :new!
# Create a new instance of the class fronted by its middleware
# pipeline. The object is guaranteed to respond to #call but may not be
# an instance of the class new was called on.
def new(*args, &bk)
instance = new!(*args, &bk)
Wrapper.new(build(instance).to_app, instance)
end
なんじゃこりゃ( ゚д゚)
そう、なんとRubyはインスタンスを作るnewメソッドを差し替えることができるのです。つまり以下のことが行われています。
- 本来のnew→new!にエイリアスすることで保存
- newを再定義
buildメソッドではコメントにあるようにミドルウェアの初期化が行われ、それがWrapperインスタンスにくるまれます。まあそこら辺については本題ではないので次のリクエスト処理編では無視して読み進めていきます。
ここまでのまとめ
以上ここまでサーバ起動について見てきました。大事なのはat_exitです。他にも「自分で書くことはないかもしれないけどライブラリ読む際には出てくるかもしれない記法」がいくつかありました。
- Sinatraでサーバ起動コードを書かなくてもサーバが起動するのはat_exitにサーバ起動が書かれているから。
- Rubyではクラス定義直下に実行文が書ける。うまく利用すると環境に応じた処理を記述することなどができる。
- クラスメソッド中で単に「new」と書かれている場合、「そのクラスのインスタンスを生成する(self.new。selfはクラスオブジェクト)」という意味である。
- Rubyではnewメソッドを再定義することが可能である(本来のnewはaliasを使うことで保存できる)
処理編に続く。
-
書かれているのはBaseクラスですが普通にアプリを動かす際にはBaseクラスを継承したApplicationクラスがselfです。 ↩