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.

   > GitHub - chrisroberts/dav4rack: WebDAV for Rack      GitHub - wardencommunity/warden: General Rack Authentication Framework      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"