rails server 调研

访问流程

在浏览器发起请求后,URL 由路由(route.rb)解析,确定交由哪个控制器控制并解析得到参数。之后调用 erb 模板,生成 HTML,将此 HTML 返回到浏览器进行渲染呈现。

以 cookbook3 项目作为示例,当我们访问 http://127.0.0.1:3000/ 时,我们得到的日志如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Started GET "/" for 127.0.0.1 at 2023-10-24 19:24:03 +0800
ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Processing by RecipesController#index as HTML
Rendering layout layouts/application.html.erb
Rendering recipes/index.html.erb within layouts/application
Recipe Load (0.1ms) SELECT "recipes".* FROM "recipes"
↳ app/views/recipes/index.html.erb:6
Category Load (0.1ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ app/views/recipes/_recipe.html.erb:9
Rendered recipes/_recipe.html.erb (Duration: 86.3ms | Allocations: 11430)
Category Load (0.0ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
↳ app/views/recipes/_recipe.html.erb:9
Rendered recipes/_recipe.html.erb (Duration: 1.0ms | Allocations: 492)
CACHE Category Load (0.0ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
↳ app/views/recipes/_recipe.html.erb:9
Rendered recipes/_recipe.html.erb (Duration: 0.7ms | Allocations: 448)
Rendered recipes/index.html.erb within layouts/application (Duration: 131.1ms | Allocations: 24227)
Rendered layout layouts/application.html.erb (Duration: 362.1ms | Allocations: 50060)
Completed 200 OK in 498ms (Views: 380.8ms | ActiveRecord: 0.9ms | Allocations: 67023)

在日志头记录了请求的类型、日期和来源。随后 ActiveRecord::SchemaMigration Load 进行数据库查询,检查数据库中的迁移版本。这是在检查数据库结构是否与应用程序期望的相匹配。

Processing by RecipesController#index as HTML 这句话意为该请求由 RecipeController 控制器进行处理,转发为 #index 操作,并且期待 HTML 响应。

Rendering layout layouts/application.html.erb 指出了正在呈现应用程序的主布局文件 application.html.erb

Rendering recipes/index.html.erb within layouts/application 意为 recipes/index.html.erb 视图嵌套于主布局中。

Recipe Load (0.1ms) SELECT "recipes".* FROM "recipes" 表示 SQL 查询,从数据库中查询 recipes 的数据。之后的几句日志都意为从数据库中查询、获取数据。

1
2
3
Rendered recipes/_recipe.html.erb (Duration: 0.7ms | Allocations: 448)
Rendered recipes/index.html.erb within layouts/application (Duration: 131.1ms | Allocations: 24227)
Rendered layout layouts/application.html.erb (Duration: 362.1ms | Allocations: 50060)

表明已成功返回 Render,网页成功呈现。

Completed 200 OK in 498ms (Views: 380.8ms | ActiveRecord: 0.9ms | Allocations: 67023) 这是日志的结束部分,指明整个请求处理的耗费时间、状态码、以及资源分配信息。

Rails 启动流程

config/boot.rb 文件

此文件包含如下内容:

1
2
3
4
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)

require "bundler/setup" # Set up gems listed in the Gemfile.
# require "bootsnap/setup" # Speed up boot time by caching expensive operations.

环境变量 BUNDLE_GEMFILE 的作用是制定 bundle 文件路径。

require "bundle/setup" 的目的是加载 Bundler 库并设置应用程序的环境。Bundler 是 Ruby 的依赖管理器,用于管理应用程序所需的 Gem 依赖项。

其具体会做以下事情:

  1. 加载 Bundle 库。
  2. 设置 Gem 加载路径。
  3. 检查 Gem 依赖项。

# require "bootsnap/setup" # Speed up boot time by caching expensive operations. 这句话会用于加速 rails 的启动速度。但是在我的本机上由于某些兼容问题,导致包含此选项会报错,无法启动 rails server,故而将其注掉。

bin/rails.rb 文件

内容如下:

1
2
3
4
#!/usr/bin/env ruby.exe
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"

其中 rails/commands.rb 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# frozen_string_literal: true

require "rails/command"

aliases = {
"g" => "generate",
"d" => "destroy",
"c" => "console",
"s" => "server",
"db" => "dbconsole",
"r" => "runner",
"t" => "test"
}

command = ARGV.shift
command = aliases[command] || command

Rails::Command.invoke command, ARGV

这部分内容主要在于对控制台指令的转发。例如,如果我们输入 rails s,则会被解释成 rails server

Gem 包的加载顺序

列在 Gemfile 中的包通常是按照声明顺序加载的。有些 Gem 包之间可能存在依赖关系,Bundler 会确保存在依赖的 Gem 在加载时正确处理。