1以下更多的是向代码开发人员指出一个可以被视为缺陷的Rails问题。
我也向更了解的人寻求一些机会。
我想通过Warden身份验证将WebDAV添加到我的Rails3应用程序中。我的典狱长中间件是通过device注入的。
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
理论上,我们可以对这个验证进行修补,以符合WebDAV动词,如
railsdav project used to do
(注意,这里是rails 2,在rails 3中,需要monkey patch 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支持。但它仍然需要认证,为此我想使用典狱长。
# in document_resource.rb
def check_authentication
puts request.env['warden'] # nil :(
end
Warden为零,因为我的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
我相信,通过将“pmp::application.routes”与dav处理程序包装在一起(就像我上面在config.ru中对“pmp::application”所做的那样),可以在正确的位置将我的webdav处理程序插入堆栈,以满足以下两个条件:
-
在actionDispatch方法验证代码之上,以避免actionController::UnknownHttpMethod
-
在session和warden::manager下面,这样我就可以使用warden身份验证。
怎么做?从“rake middleware”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”,而少了一点方法的魔力。
瞧:一切都正常!
巨大的成功。
只有一个问题:我不喜欢。除了重写Routes方法之外,必须有更好的方法来完成所有这些封装。
注意,这对乘客不起作用。最好的办法似乎是猴子修补铁轨。
见:
dav4rack wiki
大问题是:
是否有更好的方法可以通过机架安装或其他方式将机架应用程序添加到“pmp::application routes”应用程序上方????
大结论
-
routes.rb中的“mount”语义应该是机架级的(而不是rails/railtie/whatever),这样就可以处理HTTP扩展,或者至少有一个方法来处理这种情况下的“mount_rack”。