代码之家  ›  专栏  ›  技术社区  ›  clyfe

Rails 3、HTTP扩展(WebDAV)和机架应用程序安装

  •  7
  • clyfe  · 技术社区  · 14 年前

    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处理程序插入堆栈,以满足以下两个条件:

    1. 在actionDispatch方法验证代码之上,以避免actionController::UnknownHttpMethod
    2. 在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”应用程序上方????

    大结论

    1. routes.rb中的“mount”语义应该是机架级的(而不是rails/railtie/whatever),这样就可以处理HTTP扩展,或者至少有一个方法来处理这种情况下的“mount_rack”。
    1 回复  |  直到 14 年前
    推荐文章