アプリケーションの処理フローを追ってみる
今回調査すること
- Requestがどのように受け渡されるか
- ActionControllerのバージョン: 2.3.5
最初の流れ
httpリクエストの簡単な流れは,リクエスト=>処理=>レスポンスだ。
その受け渡しの部分を順番に見ていく。
リクエスト
まずは、HTTPリクエストを受け取り、処理側に渡す部分。
Railsでは今Rackが標準で利用されている。詳しく調べてないけどRackはHTTPサーバ(MongrelやPassenger)とアプリケーションの間で動くミドルウェアでアプリケーションから見たリクエストをどのHTTPサーバから来ても同一のものに変更してくれるものだと認識している。
Rackのサンプル
こうするとMongrelを起動してポート3000を利用して起動する。
require "rubygems" require "rack" class HelloRack def call(env) [200, {"Content-Type" => "text/plain"}, ["Hello"]] end end Rack::Handler::Mongrel.run HelloRack.new, :Port => 3000
起動すると以下のテストをパスする。
require "rubygems" require "expectations" require "net/http" Expectations do expect "Hello" do Net::HTTP.get("localhost", "/", 3000) end end
ということは実質、Rack::Handler::Mongrel.runの処理を見ればいいことになる。
Rack::Handler::Mongrel.run
コード見る通りoption解析してリクエストを受け付ける。
module Rack module Handler class Mongrel < ::Mongrel::HttpHandler def self.run(app, options={}) server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0', options[:Port] || 8080) . . . . server.register('/', Rack::Handler::Mongrel.new(app)) server.run.join end end end
Mongrelについて昔ちょっと調べたので調べない。
要は、以下のことがいえればまずはいいとおもう。
Rackを起動すると、任意のHTTPサーバがリクエスト受付を開始する。 リクエストが来た際はそれをアプリケーションに渡し、スレッドに処理を任せる。 処理が終わった後、レスポンスを返す。
少しリクエスト=>処理=>レスポンスの流れが明確になったかな。
で、今回はRailsの処理だから、RackがRailsにどのようにリクエストを渡しているのか見てみる。
Railsアプリの起動
Rails開発者であればきっと誰もがしたことであろう、アプリ起動の流れからリクエストの受付を見ていきます。
ruby script/server
を追っていきます。
script/server
中身は本当に簡単。
#!/usr/bin/env ruby require File.expand_path('../../config/boot', __FILE__) require 'commands/server'
config/bootの所はよんだことあるけどまだブログにのせてないので後でのせる。
多分サーバ起動してるところはserver.rbであると推測できる。
server.rb
Railsに内包されている。(lib/commands/server.rb)
コードが意外と長いので、流れだけ書く。
1. オプション解析 2. Rackから利用サーバを取得 3. RAILS_ROOT/tmp/ 以下にアプリ利用ディレクトリを作成する 4. RAILS_ROOT/config/environment.rbを読み込みActionController::Dispatcheを生成(RAILS_ROOT以下にconfig.ruがあればそれを優先する) 5. Prefixの設定 6. Rack::Builderを利用してprefixに対してDispathcerを登録する 7. Server起動
リクエストの受付を処理している部分は上のフローの456なのでそこだけ順番に見ていく。
Dispatcher.new
Rack application objectを返す。あまり深入りしない。
Rack::Builder.new
ここはちょっとコードを見てみる。
app = Rack::Builder.new { use Rails::Rack::LogTailer unless options[:detach] use Rails::Rack::Debugger if options[:debugger] map map_path do use Rails::Rack::Static run inner_app end }.to_app
Rack::Builder.newの後のブロックはinstance_evalが呼ばれているだけである。
map map_path 内で行われているのはpathに対するアプリケーションの動作である。
ここでまたRails::Rack::Staticってやつがいろいろしてそうなので見に行く。
先に結論ぽい感じのことを理解できた気がするのでかいておく。
ここでは、Rackに対してRailsアプリケーションの動作を登録する。 アプリケーション起動時のパスに対して、アプリケーションの動作を登録する。 RackとRailsアプリケーションのパスの認識はRails::Rack::Staticにより行われる。 認識後、Dispatherに処理を渡してアプリケーションを呼ぶ。
Rails::Rack::Static
一部抜粋すると以下のような実装になっている。
module Rails module Rack class Static FILE_METHODS = %w(GET HEAD).freeze def initialize(app) @app = app @file_server = ::Rack::File.new(File.join(RAILS_ROOT, "public")) end def call(env) path = env['PATH_INFO'].chomp('/') method = env['REQUEST_METHOD'] if FILE_METHODS.include?(method) if file_exist?(path) return @file_server.call(env) else cached_path = directory_exist?(path) ? "#{path}/index" : path cached_path += ::ActionController::Base.page_cache_extension if file_exist?(cached_path) env['PATH_INFO'] = cached_path return @file_server.call(env) end end end @app.call(env) end end end end
ざっくりいうと静的ファイルか動的ファイルかを判断してそれに応じてレシーバを変えている。
public以下はファイルサーバとして、それ以外はDispatherに処理を依存する。
じゃあ後はさっきおいておいたDispatcherの動きを見れば良い。
ActionController::Dispather
なんか昔はRAILS_ROOT/publicにおいていた気もするんだけどいつの間にかなくなってたな…
initializeから動きを追ってみる。
def initialize(output = $stdout, request = nil, response = nil) @output = output build_middleware_stack if @@cache_classes end
どうってことない。次。
def build_middleware_stack @app = @@middleware.build(lambda { |env| self.dup._call(env) }) end
この@@middlewareがRack ObjectにRailsの処理内容をつめたものである。(多分)
このmiddlewareはクラス変数として次のように定義される。
self.middleware = MiddlewareStack.new do |middleware| middlewares = File.join(File.dirname(__FILE__), "middlewares.rb") middleware.instance_eval(File.read(middlewares)) end
middleware_stackはただのArrayを継承したクラスなんだけど、buildするとmiddlewareクラスを生成する。
そこにRailsとして必要な処理の内容をinstance_eval利用して定義する。(File.readのとこ)
きっとイメージは以下のようになっているはず。
middleware_stack = [middleware(Rack::Lock), middleware(ActionController::Failfalse), middleware(ActionController::ParamsParser)]
実行時はこういったフィルター的なものをかまして処理していくんじゃないかなと思う。
build_middleware_stackにもどる。
lambdaの中では、実際の処理を登録するとこだろう。Routing::Routes.call(@env)でアプリケーション側の呼び先を指定して、callで呼ぶ