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

# はじめに
- マネーフォワードさんの[MF Rails勉強会 ~Rails 1.0のコードを読む~](https://moneyforward.connpass.com/event/66945/)に参加させて頂きました。
- コードリーディングをする上で幾つかお題が出され、読んでみたい分野のチームに参加する
- Railsのroutingやconfig部分を読むチームに参加しました。（が、途中から逸れてwebrickの方に行ってしまいました・・・）

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


# Railsのコード準備
```shell
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` で呼ばれる所

```source
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'
```
- `config/boot` と `commands/server` の処理を行っている
- 結果的に、 `config/boot` がroutingやconfigの処理だったようだが、いきなり飛ばしてしまった・・・
  - 最初で脱線ッ・・・・！！！

## rails/railties/lib/commands/server.rb

```source
...
(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で起動

```source
...
(L28)
require "commands/servers/#{server}"
```
- 上記で選択されたserverへ(ここではwebricに進みました)

## rails/railties/lib/commands/servers/webrick.rb
```source
...
(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のオプション郡の指定
- 起動メッセージの表示など

```source
...
(L59)
DispatchServlet.dispatch(OPTIONS)
```
- Servletってなんだ?Java?という雰囲気になった。
- まあまずは読んでみよう。（脱線してたんだけど）

## rails/railties/lib/webrick_server.rb
```source
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` は何か？

```source
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をマウントしている
- (時間が惜しかったので「そういうもの」として読み進む)

```source
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
...
  def self.dispatch(options = {})
    ...
    (L67)
    require "dispatcher"

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

## rails/railties/lib/dispatcher.rb
```source
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` については先をざっくり読んだがわからなかったので飛ばす

```source
ActionController::Routing::Routes.recognize!(request).process(request, response).out(output)
``` 
- `ActionController::Routing::Routes` 
  - おそらくroutes.rb関連のオブジェクトでしょう！という仮説で進む。

## rails/actionpack/lib/action_controller/routing.rb(`.recognize!(request)`部分)
```source
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は機能が増えていって重くなっていっている。