Sinatraを支えるRuby記法 - 起動編

# はじめに

[前回](https://qiita.com/junjis0203/items/619ac6d45feded34880d)はリクエストに対する処理（getで書いたブロック）がどう登録されるのかを見てきました。今回は起動編、つまり、

```
ruby myapp.rb
```

とした場合に何が起こっているのかを見ていきます。

あらためてmyapp.rbを見てみましょう。

```rb:myapp.rb
require 'sinatra'

get '/' do
  'Hello world!'
end
```

myapp.rbには*サーバを起動するコード*は書かれていません。が、先の通りにmyapp.rbを実行するとサーバが起動します。今回はこのからくりを探るのが目的です。

# Sinatra::Application（sinatra/main.rbの方）

sinatra/main.rbに書かれているコードのうち[前回無視した方](https://github.com/sinatra/sinatra/blob/v2.0.5/lib/sinatra/main.rb#L26)を見てみます。

```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*です。[リファレンス](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/at_exit.html)にあるようにこのメソッドで登録したブロックはインタプリタが終了するときに実行されます。これが「サーバ起動を書いてないのにサーバが起動するからくり」です。

# Sinatra::Base.run!

以上終わり、となるわけはもちろんなく、Application.run!の中に入っていきます。前回も見たようにApplicationにはあまり処理は書かれておらず、[run!メソッド](https://github.com/sinatra/sinatra/blob/v2.0.5/lib/sinatra/base.rb#L1450)はBaseクラスの方に書かれています。

```rb:sinatra/base.rbより抜粋
      # 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](https://github.com/sinatra/sinatra/blob/v2.0.5/lib/sinatra/base.rb#L1707)はrun!のコメントに書かれているようなことが行われているわけですがRubyに慣れてないとわかりにくいと思うので説明します。

```rb:sinatra/base.rbより抜粋
      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はどうなってるの？」と探すと[以下](https://github.com/sinatra/sinatra/blob/v2.0.5/lib/sinatra/base.rb#L1805)のようになっています。

```rb:sinatra/base.rbより抜粋
    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!のコメントと違ってるようなｗ
それはともかく、このsetやif文がどこに書かれているかというと、*SinatraモジュールのBaseクラス直下*です。
Rubyは（Pythonもですが）「クラス定義に普通の実行文が書ける。それらの実行文はクラスが定義される際に実行される（そもそもメソッド定義なども実行文である）」という特徴があります。ここら辺がJavaなどとは大きく異なる点です。

## start_server

サーバの起動は[start_serverメソッド](https://github.com/sinatra/sinatra/blob/v2.0.5/lib/sinatra/base.rb#L1520)で行われます。
ここで重要なのはRackハンドラに渡しているのは*Applicationクラスオブジェクト*[^1]という点です。つまり、リクエストが届いてハンドラから呼び出される場合、*Applicationクラスのクラスメソッドのcall*が呼び出されることになります。call後の流れについては詳しくは処理編で説明します。

[^1]: 書かれているのはBaseクラスですが普通にアプリを動かす際にはBaseクラスを継承したApplicationクラスがselfです。

```rb:sinatra/base.rbより抜粋
      # 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](https://github.com/sinatra/sinatra/blob/v2.0.5/lib/sinatra/base.rb#L1476)というメソッドが呼び出されています。起動編の最後にこのメソッドについて見てみましょう。

```rb:sinatra/base.rbより抜粋
      # The prototype instance used to process requests.
      def prototype
        @prototype ||= new
      end
```

prototypeはクラスメソッドなので、newとはつまり、「Application.new」ということです。

「つまり、ここでApplicationクラスのインスタンスが作られてるんだな」と普通は考えると思いますが、話はもう少し複雑です（複雑でなければ取り上げません）
[以下のコード](https://github.com/sinatra/sinatra/blob/v2.0.5/lib/sinatra/base.rb#L1481)を見てください。

```rb:sinatra/base.rbより抜粋
      # 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を使うことで保存できる）

[処理編](https://qiita.com/junjis0203/items/8dfd506d0f66455e589e)に続く。