今まで暗黙的に使ってたRailsのコードをちゃんと読もうと思ったのですが、最初からRailsのコードを読んでいくと深みにはまりそうだったので、まずはシンプルなsinatraの実装を読んでみようということで、Sinatra(ver 2.0.5)のWeb Serverが起動するまでのコードを読んでみました🎩
※ちなみにSinatraの由来は、アメリカの歌手「フランク・シナトラ」が由来みたいですね🇺🇸
Sinatraアプリの実行
今回は下記のようなファイルを作成し、
# app.rb require 'sinatra' get '/' do 'Hello world!' end
実行した際に下記のような起動メッセージが表示されるまでを見ていきます👀
$ ruby myapp.rb [2019-07-27 17:36:15] INFO WEBrick 1.4.2 [2019-07-27 17:36:15] INFO ruby 2.6.1 (2019-01-30) [x86_64-darwin17] == Sinatra (v2.0.5) has taken the stage on 4567 for development with backup from WEBrick
Sinatraのメインライブラリの読み込み
まずはapp.rb
の最初に記載されたrequire 'sinatra'
が実行され、sinatra
が読み込まれます。
https://github.com/sinatra/sinatra/blob/master/lib/sinatra.rb
sinatra
の中では、'sinatra/main'
が読み込まれて、enable :inline_templates
が実行されます。
require 'sinatra/main' enable :inline_templates
enable
メソッドが呼ばれるとset(:inline_templates, true)
が実行され、singleton_classにメソッドが定義されます。
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1256
def enable(*opts) opts.each { |key| set(key, true) } end
set(:inline_templates, true)
はなんか、ゴニョゴニョして最後はdefine_singleton
で、singleton_class
にinline_templates
へのsetterとgetterが定義される。
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1220
def set # ︙ なんかオプション値のErrorチェックしてraiseとかする処理 setter = proc { |val| set option, val, true } getter = proc { value } # ︙ なんかProcとかHashを渡された時を考慮してgetter、setterを調整する処理 define_singleton("#{option}=", setter) define_singleton(option, getter) define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?" self end # singleton_classにメソッドを定義する def define_singleton(name, content = Proc.new) singleton_class.class_eval do undef_method(name) if method_defined? name String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) end end
sinatra/main
を読み込むと、Sinatra::Application
が定義されます。
定義時にsingleton_classへの実行ファイル及び実行判定のメソッドの定義と、実行時のオプションのパース、そしてWebServerの起動処理が実行されます。
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb
require 'sinatra/base' module Sinatra class Application < Base # app_fileで実行ファイルのパス(app.rb)にアクセスできるようにsingleton_classにメソッドを定義 set :app_file, caller_files.first || $0 # runでapp_fileの値と引数で渡ってきたファイル名を絶対パスで比較するようにsingleton_classにメソッドを定義 # sinatraでは、実行中のファイルと引数で渡されたファイルが等しい場合に実行(ruby app.rb)とみなしている? set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) } # オブション周りの設定 if run? && ARGV.any? require 'optparse' OptionParser.new { |op| op.on('-p port', 'set the port (default is 4567)') { |val| set :port, Integer(val) } op.on('-o addr', "set the host (default is #{bind})") { |val| set :bind, val } op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym } op.on('-s server', 'specify rack server/handler (default is thin)') { |val| set :server, val } op.on('-q', 'turn on quiet mode (default is off)') { set :quiet, true } op.on('-x', 'turn on the mutex lock (default is off)') { set :lock, true } }.parse!(ARGV.dup) end end # 例外が発生してない、かつ実行の場合に、インタプリタ終了時(app.rbの実行完了後)にrun!を実行 # https://docs.ruby-lang.org/ja/latest/method/Kernel/m/at_exit.html at_exit { Application.run! if $!.nil? && Application.run? } end
ここまでがsinatraのメインモジュールの読み込み処理です📦
Web Serverの起動処理
実際のServer起動処理を見ていきます👀
まずrun!
では、RackHandlerのオブジェクトを取得し、設定を反映後Web Serverを起動します。
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1448
def run!(options = {}, &block) return if running? set options # RackHandlerのオブジェクトを取得(WebServerの本体?)してRename 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) # Web Serverの起動 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
実際のWeb Serverの起動処理は、handler.run
でWeb Serverを起動し、標準出力に、== Sinatra (v2.0.5) has taken the stage on 4567 for development with backup from WEBrick
が表示しています🎉
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1518
# 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| # 終了 or cgiじゃなければ(?)開始のメッセージを表示 # suppress_messages?の意味がよく分からなかった。。。 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
とりあえず、このような流れでWeb Serverが起動しているようですね!
おわりに
今回はSinatraの実行からWeb Serverを起動するまでを見てみました。
実際のWebServerの起動についてはもっと手順が必要だと思っていたのですが、意外とシンプルでしたね💦
※Rack::Handler.get
でWebServerのオブジェクトを取得してRack::Handler.run
する感じ。
Rackにかなりラップされているっぽい、かつRailsを扱っていてもRackはよく聞くので、こちらもちゃんと理解したいですね😥