Rails Reading Rails - Rack

soulspirit1229 · 2015年07月30日 · 最后由 Liu-XiaoDao 回复于 2017年10月24日 · 2808 次阅读

Reaing Rails - Rack

今天在耳语上看到一段话说: 深圳是个大赌场,赌输的男人走了,赌赢的男人留了下来,还有的男人继续在搏。它是男人的天堂,又是男人的地狱,男人们在这里承受最大的压力,又享受最大的自由,男人们从一个极端到另一个极端,终于找到了自己,然后又失去了自己。

感觉写的很不错,深圳的创业氛围很浓,在这样的创业大潮中,如果不能深入其中,是件很遗憾的事情。 如果这里有雄心壮志的初创企业,也请联系我,大家一起为下半辈子成为“废人”而努力。

进入正题。 我们知道 Rack 连接了 Web Server 和 Web App,它是个 middleware,同时也可以是一个最小的 web app。

最简单的 web.就是创建个 config.ru, 里面写上代码

run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['get rack\'d']] }

执行 rakeup config.ru,最简单的 web server 产生了。

那么 Rails 系统当中一般会使用哪些 middleware 呢?它们又是如何在 Rails 系统中一步步执行的呢?

首先我们来看有哪些 middleware 我们可以执行 bundle exec rake middleware 或者在 console 中执行 Rails.application.config.middleware。

use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ff45aaa90f0>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use MetaRequest::Middlewares::Headers
use BetterErrors::Middleware
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
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
use Rack::Cors
use Warden::Manager
use Bullet::Rack
use MetaRequest::Middlewares::MetaRequestHandler
use MetaRequest::Middlewares::AppRequestHandler
use ExceptionNotification::Rack
run Rocket2::Application.routes

middleware 的加载

知道了 rails 的加载结果,我们可以探究的更深,就是 Rails 是如何加载这些 middleware 的。 Rails 在启动过程中,会调用 Engine 下的 app 方法。

# 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

default_middleware_stack 方法就是加载了默认的 middleware,具体的代码在 railties 下面 DefaultMiddlewareStack 中 build_stack 方法。

middleware.use ::ActionDispatch::ParamsParser
middleware.use ::Rack::Head
middleware.use ::Rack::ConditionalGet

而每个 middleware 又是如何串在一起的呢?config.middleware.build(endpoint) 实现了这个功能. 在 ActionDispatch::MiddlewareStack 中的 build 方法阐述了 rails 是如何将这些 middleware 连在一起的。

def build(app = nil, &block)
   app ||= block
   raise "MiddlewareStack#build requires an app" unless app
   middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
 end

通过 inject 方法,将 app 一层层套在一起。

直白点解释就是:

m1 = middlewares.freeze.reverse[0]
ExceptionNotification::Rack
m1f = m1.build(app)
<ExceptionNotification::Rack:0x007fd2a22326d0 @app=#<ActionDispatch::Routing::RouteSet:0x007fd2a0e087e0>>
m2 = middlewares.freeze.reverse[1]
MetaRequest::Middlewares::AppRequestHandler
m2f = m2.build(m1f)
<MetaRequest::Middlewares::AppRequestHandler:0x007fd2a22336e8 @app=#<ExceptionNotification::Rack:0x007fd2a22326d0 @app=#<ActionDispatch::Routing::RouteSet:0x007fd2a0e087e0>>>

那么这些串起来的 middleware 是怎么执行的呢?

middleware 的执行

我们以 ActionDispatcher::ParamsParser 为例,

def initialize(app, parsers = {})
  @app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
end

def call(env)
  if params = parse_formatted_parameters(env)
    env["action_dispatch.request.request_parameters"] = params
  end

  @app.call(env)
end

从上文知道,@app是下一个 middleware 模块。Rails 在将 params 格式处理之后,就执行下一个 middleware 的 call 方法。 最后走到 ActionDispatch::Routing::RouteSet 的 call 方法

def call(env)
  @router.call(env)
end

走到 ActionDispatch::Journey::Router 的 call 方法。

def call(env)
  env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])

  find_routes(env).each do |match, parameters, route|
    script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
                                                       'PATH_INFO',
                                                       @params_key)

    unless route.path.anchored
      env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
      matched_path = match.post_match
      env['PATH_INFO']   = matched_path
      env['PATH_INFO']   = "/" + matched_path unless matched_path.start_with? "/"
    end

    env[@params_key] = (set_params || {}).merge parameters
    status, headers, body = route.app.call(env)

    if 'pass' == headers['X-Cascade']
      env['SCRIPT_NAME'] = script_name
      env['PATH_INFO']   = path_info
      env[@params_key]   = set_params
      next
    end

    return [status, headers, body]
  end

  return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
end

到这里就接着调用 dispatcher,然后调用我们 controller 的方法执行请求了。

middleware 的添加删除

我们知道我们可以通过添加,删除 middleware。

config.middleware.use Rack::BounceFavicon

上面就添加了一个 middleware,那么 Rails 是如何添加这个 middleware 的呢? config.middleware 是 Rails::Configuration::MiddlewareStackProxy 类的实例。我们来看看 use 方法。

def use(*args, &block)
  @operations << [__method__, args, block]
end

def merge_into(other) #:nodoc:
  @operations.each do |operation, args, block|
    other.send(operation, *args, &block)
  end
  other
end

后续执行 merge_into 方法的时候,是调用传入参数 other 的方法。所以 config.middleware 的 use 方法,其最终调用的是 ActionDispatcher::MiddlewareStack 的操作方法来完成. 看下面的代码就是 MiddlewareStack 中的方法,其中 middlewares 就是个数组。use 方法是将新生成的 middleware push 到 array 中。也就是说 Rails 维护一个 middleware 的数组,在初始化的时候通过读取 environment.rb 的方法来生成最终的 middleware 数组。

def insert(index, *args, &block)
  index = assert_index(index, :before)
  middleware = self.class::Middleware.new(*args, &block)
  middlewares.insert(index, middleware)
end

alias_method :insert_before, :insert

def insert_after(index, *args, &block)
  index = assert_index(index, :after)
  insert(index + 1, *args, &block)
end

def use(*args, &block)
  middleware = self.class::Middleware.new(*args, &block)
  middlewares.push(middleware)
end

常用的 rack

接下来会以 ExceptionNotification::Rack 这个组件说明我们可以通过 middleware 所做的事。

未完待续。

大家多给点意见啊,我会好好写下去的。

能否结合应用举例,太深的话有些枯燥,能看懂的人不多。

#2 楼 @chenge 好的,我下篇文章会注意

写的很好,刚开始学,对我帮助很大,谢谢

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