翻译 Rails 从 Request 到 Response (1)

white____ · 发布于 2016年9月24日 · 最后由 white____ 回复于 2016年11月05日 · 2740 次阅读
27357
本帖已被设为精华帖!

本文翻译自:Rails from Request to Response 系列;个人选择了自己感兴趣的部分进行翻译,需要阅读原文的同学请戳前面的链接。

第一部分 导言(Introduction)

服务器

在讲 Rails 调用栈之前,先简单介绍一下不同服务器应用的作用,其中并不会涉及到各个服务器应用(比如 Thin 和 Unicorn 或 Nginx)的细节,因为文章的重点是讲 Rails 端的一些东西。

这里举一个 Unicorn 的简单例子,管窥整个 Rails 应用。

Unicorn架构

Unicorn 是一个实现了Rack接口的服务器应用,通过多个 worker 并行处理请求(request)。启动时,主进程会将 Rails App 代码加在到内存中,随后以加载进来内存为原料进行复制,生成一定数量的 worker,并对他们进行监控和信号捕获(比如被用作关闭和重启的 QUIT,TERM,USR1 信号等等)。这些 worker 负责处理的一个个真实的 web 请求(request)。

下图是 Unicorn 的架构(这幅图片来自Github一篇很棒的文章): Unicorn Architecture

这些 worker 从共享的 socket 中读取 HTTP 请求(request),并将它们发给 Rails 应用。随后得到响应(response),写回到共享的 socket 中。这个过程中的大部份都发生在Unicorn HttpServer 类的 #process_client 方法中,下面是相关部分的代码:

# unicorn/lib/unicorn/http_server.rb

def process_client(client)
  status, headers, body = @app.call(env = @request.read(client))

  ...

  http_response_write(client, status, headers, body,
                      @request.response_start_sent)

  client.shutdown
  client.close
rescue => e
  handle_error(client, e)
end

其中省略了一些关于HTTP 100状态处理和 Rack socket hijacking 相关的代码,感兴趣的话可以阅读完整版本

我们可以看到,这个方法的核心逻辑相当的简明!

第一行是Rack specification:Rack App 其实就是一个 Ruby Object,我们只需要为它写一个可以接受 hash 参数 env 的 #call 方法,让它的返回 [status, headers, body](译者:还不是很明白 rack 是什么鬼的同学可以去看一下这个视频,亲测好评)。

这个就是 Rails,Sinatra,Padrino 等那些兼容了Rack接口的框架的核心。回到 #process_client 方法,可以看到,我们向 @app 的 #call 方法传递 env 参数,并在 client 关闭之前,将响应(response)写回。

你没猜错,这个 @app 就是我们的 Rails 项目,我们来看他的声明:

# blog/config/application.rb

module Blog
  class Application < Rails::Application
    ...
  end
end

这就是 Rails 调用栈的入口,但是如果你仔细观察,你会发现 #call 并没有定义在 Blog::Application,而是被声明在了父类 Rails::Application 中。

现在开始,我们需要了解 Rails 应用的继承机制,以及一个请求(request)是如何在 Rails 内部被处理的。

Rails Application and Engines

我们之前提到,整个 Rails 应用的入口 #call 被定义在了 Rails::Application 中,我们通过继承来使用它。这里是它的定义(源码):

# rails/railties/lib/rails/application.rb

module Rails
  class Application < Engine

    # Implements call according to the Rack API. It simply
    # dispatches the request to the underlying middleware stack.
    def call(env)
      env["ORIGINAL_FULLPATH"] = build_original_fullpath(env)
      env["ORIGINAL_SCRIPT_NAME"] = env["SCRIPT_NAME"]
      super(env)
    end

    ...

  end
end

这里并没有什么东西,大部分功能通过调用 super 来执行。如果我们跟着代码看,可以发现 Rails::Application 类继承于 Rails::Engine 类。如果你熟悉Rails engines,你会惊喜地发现,Rails::Application 就是一个超级 engine!

我们来看看 Engine 类中的 #call 方法:

# rails/railties/lib/rails/engine.rb

module Rails
  class Engine < Railtie

    def call(env)
      env.merge!(env_config)
      if env['SCRIPT_NAME']
        env.merge! "ROUTES_#{routes.object_id}_SCRIPT_NAME" => env['SCRIPT_NAME'].dup
      end
      app.call(env)
    end

    ...

  end
end

所以,Engine 类继承于 Rails::Railtie 。通过观察源码,我们可以发现Railtie0是 Rails 框架的核心,他为 initializers,config keys,generators 和 rack tasks 等提供钩子方法。

所有Rails的主要部件(ActionMailer,ActionView,ActionController 和 ActiveRecord)都是一个 Railtie,这也就是为什么你可以随意拆装组合这些部件。

在 Engine 的 #call 方法中我们看到了 #call 的另一个代理方法(delegation),这里的 app 代表的是什么?在同一个文件中,我们发现了他的定义:

# rails/railties/lib/rails/engine.rb

# Returns the underlying rack application for this engine.
def app
  @app ||= begin
    config.middleware = config.middleware.merge_into(default_middleware_stack)
    config.middleware.build(endpoint)
  end
end

现在我们到了一个牛逼的位置。这个engine构造了一个类似Rack application的中间件,并将 #call 方法代理(delegate)给它,他的终点是我们应用的 routes,ActionDispatch::Routing::RouteSet 类的一个实例。

Rack middleware可以用来「过滤」请求(request)和响应(response),并且可以将一个请求(request)处理过程分解成多个步骤,并视为一个「管道」进行处理,比如:处理权限认证,缓存等等。

你可以通过执行 rake middleware 来列出一个应用所使用的全部中间件。这里是我对 Blog 应用执行这条指令所得到的结果:

$ RAILS_ENV=production rake middleware
use Rack::Sendfile
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007f7ffb206f20>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run Blog::Application.routes

其中的大部分都不会讲,因为没有必要去了解全部这些中间件,即使一个请求(request),在到达 Blog::Application.routes 之前,会途径列表中从上到下所有的中间件。

到这里,我们已经完成了 App server / Rails application stack 的导言部分的介绍,接下来会着重介绍 Rails routing / dispatch stack。

转自本人 Blog: http://www.cnblogs.com/little-bai/p/5711185.html

共收到 2 条回复
2 huacnlee 将本帖设为了精华贴 9月28日 10:04
2

那些兼容了Rake接口的框架的核心

应该是 “Rack 接口” 吧

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册