ruby-on-rails - Rails 3、HTTP 扩展(WebDAV)和 Rack App 安装

标签 ruby-on-rails ruby http webdav rack

1 下面更多是给code devs指出rails的一个可以被认为是缺陷的问题。
2 我还征求了一些更了解的人的意见。

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

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 中需要猴子补丁 action_dispatch/http/request)。

要将 DAV4Rack 处理程序添加到 Rails 应用程序,我必须将处理程序安装在 ActionDispatch 之外,在 Rack 级别,如下所示:

# 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 安装在 session 和 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 处理程序注入(inject)堆栈中正确的位置满足两个条件:

  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

因为我们的新 Rack 应用程序“app”将被询问链下的一些方法,旧的 Rack 应用程序“routes_app”曾经响应过,我们将这些委托(delegate)给旧的原始应用程序“routes_app”并使用一些 method_missing 魔法.

瞧:一切正常! 巨大的成功。

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

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

大问题:

是否有更好的方法通过 Rack 安装或其他方式在“Pmp::Application#routes”应用程序上方添加一个 Rack 应用程序???

大结论

  1. routes.rb 中的“mount”语义应该是 Rack 级的(而不是 rails/railtie/其他),以允许以这种方式处理 HTTP 扩展,或者至少有一个针对这种情况的方法“mount_rack”

最佳答案

关于ruby-on-rails - Rails 3、HTTP 扩展(WebDAV)和 Rack App 安装,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4009082/

相关文章:

ruby-on-rails - rails4 中的未定义方法 `user?'

ruby-on-rails - 如何在 Rails 中构建投票/调查类型的应用程序

javascript - 有哪些工具可以解析 Javascript 并读取 Javascript 或 Ruby 的结果?

ruby - 如何在 Ruby 中将字符串保存到 CSV 文件?

C Socket Client 不从服务器接收任何字节

ruby-on-rails - shoulda-matchers 的 ActiveRecord 匹配器是否违反了 "test behavior not implementation"规则?

ruby-on-rails - rails 未定义方法 `attr_accessible'

ruby-on-rails - 如何验证ERB或捕获ERB语法错误

c++ - 使用套接字的 http 服务器响应

java - 配置 Tomcat 以发送兼容 IE 7 或 6 的网页