Help us understand the problem. What is going on with this article?

Sinatraはどうやってリクエスト待受を開始しているのか?(コードを読んでみよう)

まとめ

サーバープログラムの動作の基本

細かい差は多々ありますが、言語・フレームワークに限らず一般的にサーバープログラムは起動すると

  • リクエストを受け取ったらどうするかを決める
  • リクエストの待受を開始する

という順で動作します。

でもSinatraは

Sinatra公式サイトのトップページにある、最初のサンプルプログラムの内容は

require 'sinatra'
get '/frank-says' do
  'Put this in your pipe & smoke it!'
end
  • sinatraを読み込む
  • リクエストを受け取ったらどうするかを決める

これだけしかやってないように見えます。でもこのコードはちゃんとリクエストの待受を開始します。不思議です。何故なのでしょうか。

Sinatraのコードを読んでみよう

SinatraのソースコードはGithubで公開されていて、誰でも読むことができます。

上記の謎を解明するために、Sinatraのコードを調べてみましょう。

今回は、現時点での最新版であるv2.0.8.1のコードで見ていきます。

sinatra.rb

require 'sinatra' で読み込まれるファイルは、Rubyの中の仕組みによってlib/sinatra.rbに決まります。

sinatra.rb
require 'sinatra/main'

enable :inline_templates

内容はこれだけです。3行目はtemplate、つまりERBファイルに関する事のようなので、今回は気にしないことにしましょう(雑な判断ですが、コードを読むときは脱線しやすいというのもあり、どこを読むかの優先度を考える事は重要です)。また別のファイルをrequireしているのでそちらを見てみます。

main.rb

main.rbを開いてみると、Sinatraモジュール(名前空間)の中にいくらかのコードとApplicationクラスの定義がある、という構成になっています。

ここで重要なのは、モジュールやクラスの定義の中に直接書いてあるコードは、メソッドの中のコードとは異なり、読み込んだ時点で実行されるということです。

読み進めてみましょう。

最初に目に入るのは、 if ARGV.any?から始まる大きなif節です。ARGVは、ターミナルからrubyを起動したときに渡した引数が入っている定数です。今回は、引数によって変わる動作の調査ではないので、ここはまるごと読み飛ばしてしまいます。

次に、require 'sinatra/base'と、またさらに別のファイルを読み込んでいます。今回はここが一番の難所です。なぜなら、この読み込んでいるbase.rbはSinatraの心臓部で、このファイルだけで2000行のコードが書いてあるからです。

base.rb

とはいえ、ここにも1つコツがあります。それは「メソッドの定義は読み飛ばす」ことです。なぜなら、メソッドは定義するだけでは何も処理を実行しないので、今回の最初の疑問である「メソッドを呼んでいないはずなのにリクエスト待受が始まる」こととは関係のない可能性のほうが大きいからです。

base.rbをざーっと眺めていくと、その大部分はメソッドの定義であることがわかります。

set :xxx, yyy という記述は、Rubyのgemのコードなどではよく出てくるのですが、大抵の場合は、「設定のxxxという項目にyyyという値を登録する(あとで使う)」という意味です。なのでこれも、それ単体で何かを仕込んでいる可能性は低いです。

error SomeClassName do ... end という記述も何度か出てきます。これは見るからにエラー処理に関する物のようなので、今回の調査目的からは外しても良さそうです。

…という感じで、今回の調査に関係あるものはこのファイルの中にはなさそうと判断できるので、main.rbに戻ります。(ざっくりとしか見ていないので、この後の調査で何も見つからなければ改めて精査する事になります)

main.rbの続き

Applicationクラスの定義の中身は、setの呼び出しとパラメータの設定関連の処理のようなので、ここも関係なさそうです。

その次のremove_constもパラメータ関連のようですが、その次

main.rb
at_exit { Application.run! if $!.nil? && Application.run? }

にはApplication.run!、日本語にすると「アプリケーションを起動する」という、いかにも何かありそうな記述が出てきました。

at_exit

at_exitとは何でしょうか。聞き慣れないメソッドの呼び出しを見たときには、とりあえずるりまサーチで検索してみましょう。

そうすると、見つかりました。Rubyで何もしなくてもいきなり使えるメソッドは、だいたいKernelクラスの提供しているメソッドなので、覚えておくと良いです。

このメソッドの機能は、「スクリプトの処理が全部終わって、実行を終了する前に、引数で渡した内容を実行するように設定する」というものです。確かにこの機能があれば、リクエストの待受を開始させる事ができそうです。

base.rb再び

あとは Application.run! が何をしているかですが、先程見たApplicationクラスの定義の中に、run!メソッドの定義はありませんでした。ということは、親クラスのBaseクラスにありそうです。

base.rbを再び開いて、ブラウザのページ内検索機能を使って、run!で検索してみましょう。

そうすると、見つかりました。特に注目は1460行目のstart_serverでしょう。名前の通り、サーバーを起動して、待受を開始していそうです。

ここから先はRackという、Rubyでwebサーバーを動かす仕組みについての知識が必要で、さらに難度が上がります。ですが、ここまでに見た内容からでも、「スクリプトの内容が全部終わったあと待受を開始する」という設定をしている可能性が高い、と一旦結論づけることができます。

コードを読んだらわかる

このように、まるで魔法のように動いているように見える仕組みでも、そう動いているのには必ず理由があって、rubyのgemであれば大抵は、Github上に置いてあるコードを読んでみることで疑問を解消できます。

例えばSinatraのコードはかなりしっかりしていて、プログラミング技法の勉強にもなりますし(これは物によってレベルの違いはあります)、このような疑問が生じたときは、チャンスだと思って、コードを読んでみると良いでしょう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした