Ruby
Rails
勉強会
コードリーディング

Rails 1.0のコードを読む(Webrick周り)

はじめに

  • マネーフォワードさんのMF Rails勉強会 ~Rails 1.0のコードを読む~に参加させて頂きました。
  • コードリーディングをする上で幾つかお題が出され、読んでみたい分野のチームに参加する
  • Railsのroutingやconfig部分を読むチームに参加しました。(が、途中から逸れてwebrickの方に行ってしまいました・・・)

この記事のTopics

  • Rails v1.0.0時代はRackが無くて、webrickとlighttpdの2択だった
  • 当時はwebrickとlighttpd以外を使うときは、Rails自体に手を入れる必要があった
  • DHHは人生何周もしているに違いない(という噂)

Railsのコード準備

git pull https://github.com/rails/rails
cd rails
git checkout v1.0.0

コードリーディングする上でのお題

  • Railsの起動周り(routing周りとは初期化)
    • の予定だったが、途中から逸れてwebrickの方に行ってしまった

コードリーディング

  • Railsのコード単体でなく、Railsから生成したアプリのコードからも読み進めるとわかりやすいのでは?ということで、用意していただいたサンプルアプリのコードと一緒に読み進めました。(MFの担当者の方ありがとうございました!)
  • サンプルアプリは「hello_app」
  • 以下、本記事のどちらのソースコードを読んでいるかは、
    • hello_app/:サンプルアプリのコード
    • rails/:rails自体のコード

rails起動!(hello_app/script/server)

  • 皆さん一番実行されていると思う rails server で呼ばれる所
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'
  • config/bootcommands/server の処理を行っている
  • 結果的に、 config/boot がroutingやconfigの処理だったようだが、いきなり飛ばしてしまった・・・
    • 最初で脱線ッ・・・・!!!

rails/railties/lib/commands/server.rb

...
(L9)
server = case ARGV.first
  when "lighttpd"
    ARGV.shift
  when "webrick"
    ARGV.shift
  else
    if RUBY_PLATFORM !~ /mswin/ && !silence_stderr { `lighttpd -version` }.blank? && defined?(FCGI)
      "lighttpd"
    else
      "webrick"
    end
end
...
  • いきなりのパワースポットがくる
  • Webサーバはlighttpdかwebrickの2択がべた書きされている
  • mswinってwindows server?らしき物もlighttpdで起動
...
(L28)
require "commands/servers/#{server}"
  • 上記で選択されたserverへ(ここではwebricに進みました)

rails/railties/lib/commands/servers/webrick.rb

...
(L4)
OPTIONS = {
  :port            => 3000,
  :ip              => "0.0.0.0",
  :environment     => (ENV['RAILS_ENV'] || "development").dup,
  :server_root     => File.expand_path(RAILS_ROOT + "/public/"),
  :server_type     => WEBrick::SimpleServer,
  :charset         => "UTF-8",
  :mime_types      => WEBrick::HTTPUtils::DefaultMimeTypes
}
  • webrickのオプション郡の指定
  • 起動メッセージの表示など
...
(L59)
DispatchServlet.dispatch(OPTIONS)
  • Servletってなんだ?Java?という雰囲気になった。
  • まあまずは読んでみよう。(脱線してたんだけど)

rails/railties/lib/webrick_server.rb

class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
...
  def self.dispatch(options = {})
    ...
    (L61)
    server = WEBrick::HTTPServer.new(params)
    server.mount('/', DispatchServlet, options)
    ...
  end
...
end
  • webrickのオプションをセットして、webrickのインスタンス生成している
  • '/' に自身?(DispatchServlet)をマウントしてする
  • DispatchServlet は何か?
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
...
  (L72)
  def initialize(server, options) #:nodoc:
    @server_options = options
    @file_handler = WEBrick::HTTPServlet::FileHandler.new(server, options[:server_root])
    Dir.chdir(ABSOLUTE_RAILS_ROOT)
    super
  end
...
end
  • webrickのFileHandlerをセットしているっぽい。(この先はwebrickのソースなので読まなかった)
  • 結果として、 '/' にwebrickのFileHandlerをマウントしている
  • (時間が惜しかったので「そういうもの」として読み進む)
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
...
  def self.dispatch(options = {})
    ...
    (L67)
    require "dispatcher"

    server.start
    ...
  end
...
end
  • dispacher をrequire
  • その後にサーバをスタートしているっぽい

rails/railties/lib/dispatcher.rb

class Dispatcher
  class << self
    (L34)
    ...
    def dispatch(cgi = nil, session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
      if cgi ||= new_cgi(output)
        request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi)
        prepare_application
        ActionController::Routing::Routes.recognize!(request).process(request, response).out(output)
      end
      ...
    end
    ...  
  end
  ...
end
  • ここが一番重かった
  • ActionControllerのrequestクラスとresponseクラスを使っている
  • prepare_application については先をざっくり読んだがわからなかったので飛ばす
ActionController::Routing::Routes.recognize!(request).process(request, response).out(output)
  • ActionController::Routing::Routes
    • おそらくroutes.rb関連のオブジェクトでしょう!という仮説で進む。

rails/actionpack/lib/action_controller/routing.rb(.recognize!(request)部分)

module ActionController
  module Routing #:nodoc:
    ...
    class RouteSet #:nodoc:
    ...
    (L452)
    def recognize(request)
      ...
      (L461)
      controller = hash['controller']  
      hash['controller'] = controller.controller_path  
      request.path_parameters = hash  
      controller.new 
    end
    alias :recognize! :recognize
    ...
  end
  ...
end
  • recognize! のエイリアス
  • ここも詳細に読み込むことができず、「おそらくcontroller関連のオブジェクトがhashに入っており、それをnewする」と推測

rails/actionpack/lib/action_controller/base.rb(.process(request, response) 部分)

module ActionController #:nodoc:
  ...
  class Base
  ...
    class << self
      ...
      (L361)
      def process(request, response, method = :perform_action, *arguments) #:nodoc:
        initialize_template_class(response)
        assign_shortcuts(request, response)
        initialize_current_url
        @action_name = params['action'] || 'index'
        @variables_added = nil

        log_processing if logger
        send(method, *arguments)
        @response
      ensure
        close_session
      end
      ...
      (L851)
      def perform_action
        if self.class.action_methods.include?(action_name) || self.class.action_methods.include?('method_missing')
          send(action_name)
          render unless performed?
        elsif template_exists? && template_public?
          render
        else
          raise UnknownAction, "No action responded to #{action_name}", caller
        end
      end

    end
    ...
  end
  ...
end
  • 詳細の読み込むことができなかったが、どこからかactionのmethodを取得してきて実行。
  • ざっと見た感じ、render_textとかを@responseにセット

rails/actionpack/lib/action_controller/cgi_process.rb(.out(output)部分)

    def out(output = $stdout)
      (L163)
      convert_content_type!(@headers)
      ...
    end
  • headerをセットしている・・・ぽい?

終了

  • 終盤に「あれ・・・routingとかconfigとか出てこなくね?」ってなって、一番最初で読む方向を間違ったことに気づく

感想

  • これを20代で作り上げるとかすごすぎだろ・・・DHHェ・・・
  • 全部を読む時間がなくて、仮説で読み飛ばしてしまったところが多く、あとでじっくり読み直したいと思った。
  • webrickにすごい依存した書き方だった
    • rackが来るとどう変わるのか興味深い
  • これが現在の5系になるまでどのように変わっていくか知りたいと思った
  • 1時間で読みましたが、時間もっと欲しいね!

最後に

  • 生のRubyコミッタに会えて「ラピュタは本当に存在したんだ!」という気持ちになりました
  • とても良い経験をさせていただいたマネーフォワード様に感謝!
  • 「v2のソースを読む」とかぜひやってほしい・・・・(最新まで!)
  • a_matsudaさんと握手できてよかった!!!

(おまけ)懇親会で聞けたこと

  • Railsのバージョンごとの進化について
    • v1からv2への大きな変化はREST
    • RackはRails v2.2〜2.3あたりで入った
    • v2からv3も大きな変更があった(Merbとの結合)
  • DHHについて
    • 25歳くらいでほぼ一人でrails作り上げたDHHすごい。
    • DHHは人生何周もしているに違いない(という噂)
  • その他
    • Rubyはどんどん早くなっているが、Railsは機能が増えていって重くなっていっている。