在写一个 Gem,想把 Gem 中的 middleware 设置在::ActionDispatch::Static
之后,本来觉得这样很容易做到,直到我发现 Rails 使用::ActionDispatch::Static
这个 Middleware 是有条件的,那就是config.serve_static_assets
要打开。如果config.serve_static_assets
为 false 的话,那么毫无疑问就会出现No such middleware to insert after: "::ActionDispatch::Static"
错误。这个错误还不是调用insert_after
的时候立即出错的,否则倒是可以捕捉异常后做点 fallback 操作的。为了让 middleware 总是插入在我想要的位置上,我只能把代码写成这样:
class Railtie < Rails::Railtie
initializer 'phpfaker.initialize_middleware' do |app|
dependency = if app.config.serve_static_assets
"::ActionDispatch::Static"
elsif not app.config.allow_concurrency
"::Rack::Lock"
else
"::Rack::Runtime"
end
app.middleware.insert_after dependency, Middleware
end
end if defined?(Rails)
毫无疑问这段代码非常不优雅,也不容易测试,而且事实上高度依赖了Rails::Application
的实现,基本上是层层 fallback 直到::Rack::Runtime
为止,因为最后这个 Middleware 的加载不依赖任何条件,所以总是能够成功。不过如果把这段代码拿去给一个 Engine 执行,那恐怕还是要出错的,因为 Engine 可能什么默认 Middleware 都没有,任何insert_after
都是不行的。如果用户自定义了 Rails 的 Middleware,取消了部分 Middleware 的话,那同理,这段代码依然会发生错误。
事实上我这个 Middleware 对位置并不是特别敏感,只要排名靠前就行了,但是我反复查阅了 Rails 对于 Middleware 的实现,竟然没有任何允许我将 Middleware 插在最前面的操作,或是大致插入在某个位置的操作(当然我也完全可以自己实现一套,不过老是做这样的傻事不好啊。。)。又由于在启动阶段针对 MiddlewareStack 的操作实质上都是在操作 Proxy,没有办法直接获取 Build Stack 时 Middleware 的名单。因此我没法在运行时确定一个排名靠前的 Middleware 然后抱佛脚。所以。。额。。悲催了。。