Rails 3, HTTP extensions (WebDAV) and Rack App mounting

A more readable version of this here:
http://stackoverflow.com/questions/4009082/rails-3-http-extensions-webdav-and-rack-app-mounting

Hello,

1 The following is more to point out to the code devs an issue of
rails that can be percieved as a flaw.
2 And also me asking some oppinions from people who know better.

I want to add WebDAV to my Rails 3 App with Warden authentication. My
warden middleware is injected via Devise.

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

I cannot mount DAV4Rack handlers from inside rails app (routes), like
this:

    # in 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"

because rails validates HTTP verbs (GET POST PUT ..), and webdav uses
HTTP extensions like PROPFIND that do not validate, throwing the
following exception:

    ActionController::UnknownHttpMethod (PROPFIND, accepted HTTP
methods are get, head, put, post, delete, and options)

This validation takes place in 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"

Sample code from ActionDispatch that does the validation, to make
things clear:

    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

To add DAV4Rack handlers to the rails app I have to mount the handler
outside of ActionDispatch, at rack level, like this:

    # 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

Now I have Webdav support in my application. But It still needs
authentication, and for that I'd like to use warden.

    # in document_resource.rb
    def check_authentication
      puts request.env['warden'] # nil :frowning:
    end

Warden is nil because my DAV4Rack::Handler is mounted above the
session and warden middleware.
Using "rake middleware" to inspect my stack I can see the following:

    > 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

I believe that by wrapping "Pmp::Application.routes" with DAV handler
(just like I do above for "Pmp::Application" in config.ru) will inject
my webdav handler in the stack at the right place to satisfy the two
conditions:

1. Be above ActionDispatch method validation code, to avoid
ActionController::UnknownHttpMethod
2. Be below session and Warden::Manager so I can use warden
authentication.

How to do that? Looking at "rake middleware" otput it seems obvious to
override the "Pmp::Application.routes" method:

    # 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

Because our replacing rack application "app" will be asked a few
methods down the chain, that we do not implement, we delegate theese
to the old original application "routes_app" with a little
method_missing magic.

And voila: evrything is working!
Great success.

Only one problem: I don't like it. There must be a better way to do
all this enveloping, other than overriding routes method.

### THE BIG QUESTION:
IS THERE A BETTER WAY TO ADD A RACK APP JUST ABOVE THE
"Pmp::Application#routes" APP BY MEANS OF RACK MOUNT OR OTHER ???

### THE BIG CONCLUSION
1. The "mount" semantics in routes.rb should be rack-level (not rails/
railtie/whatever), to allow, in this way, hadling of HTTP extensions,
or at least have a method for this case "mount_rack"