今天在耳语上看到一段话说: 深圳是个大赌场,赌输的男人走了,赌赢的男人留了下来,还有的男人继续在搏。它是男人的天堂,又是男人的地狱,男人们在这里承受最大的压力,又享受最大的自由,男人们从一个极端到另一个极端,终于找到了自己,然后又失去了自己。
感觉写的很不错,深圳的创业氛围很浓,在这样的创业大潮中,如果不能深入其中,是件很遗憾的事情。 如果这里有雄心壮志的初创企业,也请联系我,大家一起为下半辈子成为“废人”而努力。
进入正题。 我们知道 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
知道了 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 是怎么执行的呢?
我们以 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。
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
接下来会以 ExceptionNotification::Rack 这个组件说明我们可以通过 middleware 所做的事。
未完待续。