【发布时间】:2011-04-29 20:58:46
【问题描述】:
1 以下内容更多是为了向代码开发人员指出一个可能被视为缺陷的导轨问题。
2 我也向更了解的人征求了一些意见。
我想通过 Warden 身份验证将 WebDAV 添加到我的 Rails 3 应用程序。我的warden 中间件是通过Devise 注入的。
http://github.com/chrisroberts/dav4rack
http://github.com/hassox/warden
http://github.com/plataformatec/devise
我无法从 Rails 应用程序(路由)内部安装 DAV4Rack 处理程序,如下所示:
# in config/routes.rb
mount DAV4Rack::Handler.new(
:root => Rails.root.to_s, # <= it's just an example
:root_uri_path => '/webdav',
:resource_class => Dav::DocumentResource # <= my custom resource, you could use FileResource from dav4rack
), :at => "/webdav"
因为 rails 会验证 HTTP 动词(GET POST PUT ..),而 webdav 使用诸如 PROPFIND 之类的 HTTP 扩展不会验证,会抛出以下异常:
ActionController::UnknownHttpMethod (PROPFIND, accepted HTTP methods are get, head, put, post, delete, and options)
此验证在 ActionDispatch 中进行:
/usr/local/lib/ruby/gems/1.9.1/gems/actionpack-3.0.0/lib/action_dispatch/http/request.rb +56 +72
in (56) "def request_method" and (72) "def method"
执行验证的 ActionDispatch 示例代码,让事情变得清晰:
def method
@method ||= begin
method = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
method
end
end
理论上,我们可以对该验证进行猴子补丁以符合railsdav project used to do 之类的 webdav 动词(注意那里是 rails 2,在 rails 3 中需要猴子补丁 action_dispatch/http/request)。
要将 DAV4Rack 处理程序添加到 rails 应用程序,我必须在机架级别将处理程序安装在 ActionDispatch 之外,如下所示:
# config.ru
require ::File.expand_path('../config/environment', __FILE__)
require 'dav4rack/interceptor'
require 'dav/document_resource'
app = Rack::Builder.new{
map '/webdav/' do
run DAV4Rack::Handler.new(
:root => Rails.root.to_s,
:root_uri_path => '/webdav',
:resource_class => Dav::DocumentResource
)
end
map '/' do
use DAV4Rack::Interceptor, :mappings => {
'/webdav/' => {
:resource_class => Dav::DocumentResource
},
}
run Pmp::Application
end
}.to_app
run app
现在我的应用程序中支持 Webdav。但它仍然需要身份验证,为此我想使用warden。
# in document_resource.rb
def check_authentication
puts request.env['warden'] # nil :(
end
Warden 为 nil,因为我的 DAV4Rack::Handler 安装在会话和warden 中间件之上。 使用“rake 中间件”检查我的堆栈,我可以看到以下内容:
> rake middleware
use ActionDispatch::Static
use Rack::Lock
use ActiveSupport::Cache::Strategy::LocalCache
use Rack::Runtime
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::RemoteIp
use Rack::Sendfile
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::MethodOverride
use ActionDispatch::Head
use ActionDispatch::BestStandardsSupport
use Warden::Manager
run Pmp::Application.routes
我相信通过用 DAV 处理程序包装“Pmp::Application.routes”(就像我在上面对 config.ru 中的“Pmp::Application”所做的那样)会将我的 webdav 处理程序注入堆栈中的正确位置满足两个条件:
- 上面是ActionDispatch方法验证码,避免ActionController::UnknownHttpMethod
- 位于会话和 Warden::Manager 之下,以便我可以使用warden 身份验证。
如何做到这一点?查看“rake 中间件”otput 似乎很明显要覆盖“Pmp::Application.routes”方法:
# in my app at APP_ROOT/config/application.rb
# override the routes method inherited from Rails::Application#routes
def routes
routes_app = super
app = Rack::Builder.new {
map '/webdav/' do
run DAV4Rack::Handler.new(
:root => Rails.root.to_s,
:root_uri_path => '/webdav',
:resource_class => Dav::DocumentResource
)
end
map '/' do
use DAV4Rack::Interceptor, :mappings => {
'/webdav/' => {
:resource_class => Dav::DocumentResource
},
}
run routes_app
end
}.to_app
class << app; self end.class_eval do
attr_accessor :routes_app
def method_missing(sym, *args, &block)
routes_app.send sym, *args, &block
end
end
app.routes_app = routes_app
app
end
因为我们的新机架应用程序“app”将被询问几个方法,旧的机架应用程序“routes_app”过去常常响应,我们将这些委托给旧的原始应用程序“routes_app”,并带有一点method_missing魔法.
瞧:一切正常! 大获成功。
只有一个问题:我不喜欢它。除了覆盖 routes 方法之外,必须有更好的方法来完成所有这些封装。
请注意,这不适用于乘客。最好的方法似乎是猴子修补铁轨。
见:dav4rack wiki
大问题:
有没有更好的方法通过机架安装或其他方式在“Pmp::Application#routes”应用程序上方添加机架应用程序???
大结论
- routes.rb 中的“mount”语义应该是机架级别的(不是 rails/railtie/whatever),以允许以这种方式处理 HTTP 扩展,或者至少有一种方法用于这种情况“mount_rack”
【问题讨论】:
-
好问题。我不知道答案,但也许 Rails Metal 可以参与解决方案? wiki.rubyonrails.org/releases/rails2dot3/railties
-
Rails Metal 不再存在(以旧形式)在 Rails 3 中,因为在 Rack 环境中不需要 Metal。 R3 中的 Metal 是一个精简的控制器。 tektastic.com/2010/07/…
-
路由中的
match '/webdav' => DAV4Rack::Handler.new和application.rb中的 config.middleware.insert(DAV4Rack::Interceptor) 怎么样?
标签: ruby-on-rails ruby http webdav rack