Rails チュートリアル 1章 勉強メモ16

Rails チュートリアルの勉強メモ

railstutorial.jp

  • 前回までのあらすじ
    • hello メソッドにたどり着くまでをみている
    • イマココ --> #58 Puma::Configuration::ConfigMiddleware.call(env#Hash) at $HOME/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/puma-3.12.1/lib/puma/configuration.rb:227

hello メソッドにたどり着くまで

railties-5.1.7/lib/rails/engine.rb @ line 522 Rails::Engine#call:

    520: def call(env)
    521:   req = build_request env
 => 522:   app.call req.env
    523: end
  • rails の中に入った
  • app は前回の@app の中の Rack::Sendfile 部分
  • req は ActionDispatch::Request

    • req.env は多分 env と同じ
  • rack-2.0.7/lib/rack/sendfile.rb @ line 111 Rack::Sendfile#call

  • Rack は Ruby の WEBサーバーアプリ
    • HTTPリクエストの処理は簡単で共通できるので、RackはWEBサーバーとWEBフレームワークの接続のためのAPIを提供することを目的としている。

railsguides.jp

leahneukirchen.org

Sendfile

  • Sendfile ミドルウェアは、リクエストをX-Sendfile に置き換える
  • X-Sendfile
    • バックエンドから返されたヘッダーから内部へのリダイレクトをできるようにする

www.nginx.com

www.nginx.com

  • X-Sendfile は Apache で Nginx は X-Accel-Redirect らしい
    • 微妙に違う?
    • ファイルシステムの一部をプライベートURL階層にマップする必要がある
    • ↓わかりやすかった

X-SendFile、X-Accel-Redirectの使い方 | 純規の暇人趣味ブログ

  • コードに戻る
    110:     def call(env)
 => 111:       status, headers, body = @app.call(env)
    112:       if body.respond_to?(:to_path)
    113:         case type = variation(env)
    114:         when 'X-Accel-Redirect'
    115:           path = ::File.expand_path(body.to_path)
    116:           if url = map_accel_path(env, path)
  • この @app は ActionDispatch::Static

ActionDispatch

e-words.jp

  • GETおよびHEADリクエストのみがファイルを返す
actionpack-5.1.7/lib/action_dispatch/middleware/static.rb @ line 125 ActionDispatch::Static#call:

    114: def call(env)
    115:   req = Rack::Request.new env
    116: 
    117:   if req.get? || req.head?
    118:     path = req.path_info.chomp("/".freeze)
    119:     if match = @file_handler.match?(path)
    120:       req.path_info = match
    121:       return @file_handler.serve(req)
    122:     end
    123:   end
    124: 
 => 125:   @app.call(req.env)
    126: end
  • req は Rack::Request (env の内容が入ってる
  • req.get? || req.head? (リクエストがgetかheadか
    • 今回は GET なので true
  • req.path_info は "/" なので path は ""
  • match? はファイルへのパスを取得する
    • public 以下のファイルの存在を確認するために Static クラスで利用される
[87] pry(#<ActionDispatch::Static>)> @file_handler.match?("robots.txt")
=> "robots.txt"
[88] pry(#<ActionDispatch::Static>)> @file_handler.match?("404")
=> "404.html"
[89] pry(#<ActionDispatch::Static>)> @file_handler.match?("200")
=> nil
  • 今回は path は "" なので nil を返す
  • @app.call(req.env)
  • また次の call へ・・・

ActionDispatch::Executor#call

railsguides.jp

actionpack-5.1.7/lib/action_dispatch/middleware/executor.rb @ line 12 ActionDispatch::Executor#call:

     9: def call(env)
    10:   state = @executor.run!
    11:   begin
 => 12:     response = @app.call(env)
    13:     returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
    14:   ensure
    15:     state.complete! unless returned
    16:   end
    17: end
  • state は状態が入ってるんだろう
  • response = @app.call(env)
  • 次のapp.call

ActiveSupport::Cache::Strategy::LocalCache::Middleware#call

  • このクラスはミドルウェアのローカルストレージをラップします。らしい
activesupport-5.1.7/lib/active_support/cache/strategy/local_cache_middleware.rb @ line 27 ActiveSupport::Cache::Strategy::LocalCache::Middleware#call:

    25: def call(env)
    26:   LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
 => 27:   response = @app.call(env)
    28:   response[2] = ::Rack::BodyProxy.new(response[2]) do
    29:     LocalCacheRegistry.set_cache_for(local_cache_key, nil)
    30:   end
    31:   cleanup_on_body_close = true
    32:   response
    33: rescue Rack::Utils::InvalidParameterError
    34:   [400, {}, []]
    35: ensure
    36:   LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless
    37:     cleanup_on_body_close
    38: end
  • 次の@app.call

Rack::Runtime#call

  • リクエストの応答時間を秒単位で示す X-Runtime を設定するらしい
  • アプリケーションの前に配置して処理時間を確認したり、すべてのミドルウェアの前に置いて、それらの時間を含めることもできる。らしい
rack-2.0.7/lib/rack/runtime.rb @ line 22 Rack::Runtime#call:

    20: def call(env)
    21:   start_time = Utils.clock_time
 => 22:   status, headers, body = @app.call(env)
    23:   request_time = Utils.clock_time - start_time
    24: 
    25:   unless headers.has_key?(@header_name)
    26:     headers[@header_name] = FORMAT_STRING % request_time
    27:   end
    28: 
    29:   [status, headers, body]
    30: end
  • start_timeを入れて、@app.callへ
  • @app を pry で見てみるとかなり重なっていて先が遠そう

Rack::MethodOverride#call

rack-2.0.7/lib/rack/method_override.rb @ line 22 Rack::MethodOverride#call:

    13: def call(env)
    14:   if allowed_methods.include?(env[REQUEST_METHOD])
    20:   end
    21: 
 => 22:   @app.call(env)
    23: end
  • ALLOWED_METHODS = %w[POST] なのでスルー

ActionDispatch::RequestId#call

  • request_id をセットして app.call

ActionDispatch::RemoteIp#call

  • リモートIPを取り、app.call

Sprockets::Rails::QuietAssets#call

  • このミドルウェアは、リクエストが現在のアセットprefixのパスと一致するかどうかをチェックし、一致する場合はリクエストをログに出力しません。

techracho.bpsinc.jp

  • env['PATH_INFO'] =~ @assets_regex はマッチしないので次のapp.call

Rails::Rack::Logger#call

  • ログを記録する
  • Rails::Rack::Logger#call_app へ
  • request のログをスタートして @app.call(env)

ActionDispatch::ShowExceptions#call

  • 例外を拾う感じ?
  • @app.call(env)

WebConsole::Middleware#call

  • request.from_whitelisted_ip? は true だが、id_for_repl_session_update(request)、id_for_repl_session_stack_frame_change(request) は nil なので次の call_app(env)
  • WebConsole::Middleware#call_app の中の @app.call(env)

ActionDispatch::DebugExceptions#call

ActionDispatch::Executor#call

  • state = @executor.run! して @app.call(env)
    • state が何かよくわからない

ActionDispatch::Callbacks#call

  • run_callbacks :call
  • callbacks.empty? なので @app.call(env)

ActiveRecord::Migration::CheckPending#call

  • config.active_record.migration_error が設定されているとチェックが入る
  • 未適用のマイグレーションを見つけるとこのエラーを投げるらしい

qiita.com

  • 次の @app.call(env)

ActionDispatch::Cookies#call

  • Cookie の何かだろうけどすぐに @app.call(env) なので次へ

Rack::Session::Abstract::Persisted#call

  • context(env)
  • session のオプションを配置して? app.call(req.env)

Rack::Head#call

  • Rack :: Headは、すべてのHEADリクエストに対して空のボディを返します。他のすべてのリクエストは変更されません。らしい
  • すぐに @app.call(env)

Rack::ConditionalGet#call

  • GET なので when "GET", "HEAD" に入る
  • すぐに @app.call(env)

Rack::ETag#call

  • ETag の何かだろうけどここもすぐに @app.call(env)
  • ETag ってなんだろう

blog.redbox.ne.jp

ActionDispatch::Routing::RouteSet#call

  • @router.serve(req)

ActionDispatch::Journey::Router#serve

    32:       def serve(req)
 => 33:         find_routes(req).each do |match, parameters, route|
    34:           set_params  = req.path_parameters
    35:           path_info   = req.path_info
    36:           script_name = req.script_name
    37: 
    38:           unless route.path.anchored
actionpack-5.1.7/lib/action_dispatch/journey/router.rb @ line 50 ActionDispatch::Journey::Router#serve:

    45:             val.dup.force_encoding(::Encoding::UTF_8)
    46:           }
    47: 
    48:           req.path_parameters = set_params.merge parameters
    49: 
 => 50:           status, headers, body = route.app.serve(req)
    51: 
    52:           if "pass" == headers["X-Cascade"]
    53:             req.script_name     = script_name
    54:             req.path_info       = path_info
    55:             req.path_parameters = set_params
[139] pry(#<ActionDispatch::Journey::Router>)> match
=> #<ActionDispatch::Journey::Path::Pattern::MatchData:0x00007fd32815aa60 @match=#<MatchData "/">, @names=[], @offsets=[0]>
[140] pry(#<ActionDispatch::Journey::Router>)> parameters
=> {:controller=>"application", :action=>"hello"}
[141] pry(#<ActionDispatch::Journey::Router>)> route
=> #<ActionDispatch::Journey::Route:0x00007fd32c124218
  • parameters に値を入れて route.app.serve(req)

ActionDispatch::Routing::RouteSet::Dispatcher#serve

  • params などをセットして dispatch(controller, params[:action], req, res)
  • dispatch では controller.dispatch(action, req, res) を実行

ActionController::Metal.dispatch

  • ActionController :: Metal はシンプルなコントローラー
  • ActionController :: Base によって提供される追加の機能なしで有効なRackインターフェースを提供する

  • middleware_stack.any? は false なので new.dispatch(name, req, res)

  • dispatch では request と response をセットして process(name)
  • name は hello

ActionView::Rendering#process

actionview-5.1.7/lib/action_view/rendering.rb @ line 30 ActionView::Rendering#process:

    28: def process(*) #:nodoc:
    29:   old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
 => 30:   super
    31: ensure
    32:   I18n.config = old_config
    33: end
  • I18n ってなんだろう

railsguides.jp

  • super の先で process_action(action_name, *args)

ActiveRecord::Railties::ControllerRuntime#process_action

activerecord-5.1.7/lib/active_record/railties/controller_runtime.rb @ line 22 ActiveRecord::Railties::ControllerRuntime#process_action:

    17: def process_action(action, *args)
    18:   # We also need to reset the runtime before each action
    19:   # because of queries in middleware or in cases we are streaming
    20:   # and it won't be cleaned up by the method below.
    21:   ActiveRecord::LogSubscriber.reset_runtime
 => 22:   super
    23: end
  • 各アクションの前にランタイムをリセットする必要があるらしい
  • ActionController::ParamsWrapper#process_action
    • _wrapper_enabled? は false なので super へ
  • ActionController::Instrumentation#process_action
  • ActionController::Rescue#process_action
  • AbstractController::Callbacks#process_action
  • ActiveSupport::Callbacks#run_callbacks
  • AbstractController::Callbacks#process_action
  • ActionController::Rendering#process_action AbstractController::Base#process_action
  • 長かったので諦めました眠い・・・
actionpack-5.1.7/lib/abstract_controller/base.rb @ line 186 AbstractController::Base#process_action:

    185: def process_action(method_name, *args)
 => 186:   send_action(method_name, *args)
    187: end

ActionController::BasicImplicitRender#send_action

actionpack-5.1.7/lib/action_controller/metal/basic_implicit_render.rb @ line 4 ActionController::BasicImplicitRender#send_action:

    3: def send_action(method, *args)
 => 4:   super.tap { default_render unless performed? }
    5: end
  • method は hello
workspace/Rails/environment/hello_app/app/controllers/application_controller.rb @ line 7 ApplicationController#hello:

    5: def hello
    6:   binding.pry
 => 7:   render html: "hello, world!"
    8: end
  • ついに hello へ辿り着いた・・・時間かけた割にあんまりよくわからなかった・・・
  • 次は Rails チュートリアルに戻ります。
  • render も少しだけ追った
actionpack-5.1.7/lib/action_controller/metal/instrumentation.rb @ line 42 ActionController::Instrumentation#render:

    41: def render(*args)
 => 42:   render_output = nil
    43:   self.view_runtime = cleanup_view_runtime do
    44:     Benchmark.ms { render_output = super }
    45:   end
    46:   render_output
    47: end
actionpack-5.1.7/lib/action_controller/metal/rendering.rb @ line 35 ActionController::Rendering#render:

    34: def render(*args) #:nodoc:
 => 35:   raise ::AbstractController::DoubleRenderError if response_body
    36:   super
    37: end
actionpack-5.1.7/lib/abstract_controller/rendering.rb @ line 24 AbstractController::Rendering#render:

    22: def render(*args, &block)
    23:   options = _normalize_render(*args, &block)
 => 24:   rendered_body = render_to_body(options)
    25:   if options[:html]
    26:     _set_html_content_type
    27:   else
    28:     _set_rendered_content_type rendered_format
    29:   end
    30:   self.response_body = rendered_body
    31: end

[1] pry(#<ApplicationController>)> options
=> {:html=>"hello, world!", :prefixes=>["application"], :template=>"hello"}