一、前言
1. 什么是Rack
Rack是ruby应用服务器和Rack应用程序之间的接口
2. Rack的特点
a.提供一个广泛支持的标准接口
Rack提供了一个标准接口,便于应用程序和应用服务器进行交互,一个Rack应用可以被任何和Rack兼容的应用服务器调用,最明显的一个例子,我们开发的rails应用或者Sinatra应用可以直接使用Webrick或者thin等服务器启动,不用做什么修改,非常方便。
b.模块化、高可重用性
Rack利用中间件实现最大程度模块化,从而提高Web应用程序部件的可重用性,从而提高开发效率
Rack中间件对Ruby Web框架也有很深远的影响:
- 不同Web框架之间可重用Rack中间件,这意味着你编写的中间件可以在所有主流框架中 使用;
- 可以通过不同中间件的组合组装出同一Web框架的不同变种以适应不同的需求;
- 可以组合不同Web框架为同一个更大的系统服务。
c.简单
Rack标准简单,这就很容易让用户实现一个Web服务器或者Web框架
Ruby 生态中的web server都有哪些?
Puma、Unicorn
Ruby 生态中的web 应用程序框架 都有哪些?
Ruby on Rails、 Sinatra
3. 这么多web server & web 应用程序,Rack是如何实现兼容并简单切换的呢?
- Rack 在 webserver 和web 应用程序 之间提供了一套最小的 API 接口。
- 如果 webserver 和 web 应用程序 都遵循 Rack 提供的这套规则,那么他们就可以随意的搭配使用。
- 那咱们今天的重点就是搞清楚rack 如何把web server 和 web 应用程序关联起来的。
二、初识Rack
1、协议
在 Rack 的协议中,将 Rack Server 描述成
- 需要实现 run 方法,当前方法接受app、options、block 参数
- 需要使用 register 方法进行服务器注册,方便 rack 找到 Rack Server。
在 Rack 的协议中,将 Rack 应用描述成
- 一个可以响应
call方法的 Ruby 对象 - 这个方法接受来自外界的一个参数,也就是环境(env)
- 返回一个只包含三个值的数组,按照顺序分别是状态码、Headers 以及响应的正文。
2、简单demo
# config.ru
require 'rack'
class MyApp
def call(env)
[200, { 'content-type'=> 'text/plain'}, ['Hello World!']]
end
end
# my_app = MyApp.new
run MyApp.new
上面这段代码就是我们实现的一个最简单的Rack应用,我们实现了一个类MyApp,他只有一 个call方法,此方法接受一个参数,返回一个数组,此数据包含status、header、body,在这 里呢,我使用WEBrick来启动这个应用,同样我也可以使用thin来启动,那么最后一句话就变成:
# 运行启动命令
rackup
# rackup -p 3000
# 进行请求
curl http://127.0.0.1:3000
# 结果输出
Hello World!
三、Rack实现原理
1、rackup做了什么?
通过运行rackup 命令启动服务,其实和运行rails s一样,都是gem提供的命令。
which rackup
...
version = ">= 0.a"
p "path: #{Gem.activate_bin_path("rack", "rackup", version)}"
load Gem.activate_bin_path('rack', 'rackup', version)
代码简述
- Gem.activate_bin_path 返回 rack 这个gem 中 rackup 这个可执行文件的路径。
- load 是kernel的方法,加载可执行文件,并且执行其中的代码。
gem 中rackup 文件的内容
#!/usr/bin/env ruby
# frozen_string_literal: true
require "rack"
Rack::Server.start
最终执行了 Rack::Server.start
2、Rack::Server.start的执行
- 找到Rack::Server,比如可能是puma
- 封装rack 应用,也就是把rack 应用 和 多个middleware 封装在一起。
- 这样rack 就把Rack::Server 和 rack 应用关联起来啦。
3、核心源码
3.1 以下是 Rack 核心源码的简化示例,展示了 Rack 模块、Request 类和 Response 类的基本实现:
# rack.rb
module Rack
VERSION = "2.3.0"
class Request
attr_reader :env
def initialize(env)
@env = env
end
def method
@env['REQUEST_METHOD']
end
def path_info
@env['PATH_INFO']
end
# 其他方法...
end
class Response
attr_accessor :status, :headers, :body
def initialize
@status = 200
@headers = {}
@body = []
end
def write(content)
@body << content
end
def finish
[@status, @headers, @body]
end
end
def self.call(env)
request = Request.new(env)
response = Response.new
# 处理请求逻辑...
response.finish
end
end
在 rack.rb 文件中,定义了 Rack 模块和一些全局常量,以及 Rack::Request 类和 Rack::Response 类。
Rack::Request 类封装了 HTTP 请求的相关信息,例如请求方法和路径等。它提供了一些方法来获取请求的各种属性。
Rack::Response 类封装了 HTTP 响应的相关信息,包括状态码、头部和正文内容。它提供了一些方法来设置响应的各种属性。
Rack 模块中的 self.call(env) 方法是 Rack 应用程序的入口点。当收到 HTTP 请求时,Rack 会调用该方法来处理请求。在示例中,创建了一个 Request 对象和一个 Response 对象,并在处理请求逻辑后返回最终的响应数据。
请注意,上述示例是简化的,并未包含 Rack 的完整实现。实际的 Rack 源码包含了更多的类、模块和方法,用于处理中间件、路由、异常处理等功能。
3.2 以下是 Rack 源码中用于遍历多个服务器和应用程序的核心部分:
# rack/builder.rb
module Rack
class Builder
def initialize(default_app = nil, &block)
@use = []
@run = default_app
instance_eval(&block) if block_given?
end
def use(middleware, *args, &block)
@use << lambda { |app| middleware.new(app, *args, &block) }
end
def run(app)
@run = app
end
def to_app
app = @run
@use.reverse_each do |middleware|
app = middleware.call(app)
end
app
end
end
end
上述代码展示了 Rack::Builder 类的关键部分。Rack::Builder 类用于构建 Rack 应用程序,它允许用户按顺序添加中间件,并指定最终的应用程序。
在 initialize 方法中,可以传入一个默认的应用程序(default_app)和一个块(block)。块中可以使用 use 方法来添加中间件,使用 run 方法来指定最终的应用程序。
use 方法接受一个中间件类(middleware)和可选的参数和块。它将中间件封装为一个 lambda,并将其添加到 @use 数组中。
run 方法用于指定最终的应用程序。
to_app 方法用于生成最终的 Rack 应用程序。它首先将 @run 变量赋值给 app,然后使用 reverse_each 方法遍历 @use 数组中的中间件。对于每个中间件,它将应用该中间件到 app 上,得到新的 app。最终,返回经过所有中间件处理后的应用程序。