Rails 3,HTTP扩展(WebDAV)和Rack App安装

时间:2010-10-24 16:19:24

标签: ruby-on-rails ruby http webdav rack

1以下更多内容是指代代码开发了一个可以被视为缺陷的轨道问题。
2还有我向那些了解得更好的人提出了一些看法。

我想通过Warden身份验证将WebDAV添加到我的Rails 3应用程序中。我的监护人中间件是通过Devise注入的。

  

http://github.com/chrisroberts/dav4rack
       http://github.com/hassox/warden
       http://github.com/plataformatec/devise

我无法从rails app(路由)内部安装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)。 / p>

要将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是零,因为我的DAV4Rack :: Handler安装在会话之上并且监控中间件。 使用“rake middleware”来检查我的堆栈,我可以看到以下内容:

> 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处理程序注入到正确位置的堆栈中满足这两个条件:

  1. 在ActionDispatch方法验证代码之上,以避免ActionController :: UnknownHttpMethod
  2. 低于会话和Warden :: Manager,所以我可以使用warden身份验证。
  3. 怎么做?看看“rake middleware”输出,显然可以覆盖“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”用于重新加载,我们将theese委托给旧的原始应用程序“routes_app”,带有一点method_missing魔法

    瞧,一切正常! 非常成功。

    只有一个问题:我不喜欢它。除了覆盖路线方法之外,必须有更好的方法来完成所有这些包络。

    请注意,这不适用于乘客。最好的方法似乎是猴子修补铁轨 请参阅:dav4rack wiki

    大问题:

    有一种更好的方法可以通过机架或其他方式在“Pmp :: Application#routes”APP之上添加一个机架应用程序

    大结论

    1. routes.rb中的“mount”语义应该是机架级的(而不是rails / railtie / whatever),以这种方式允许对HTTP扩展进行处理,或者至少为这种情况设置一个方法“mount_rack”