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 :(
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"
--
You received this message because you are subscribed to the Google Groups
"Ruby on Rails: Talk" group.
To post to this group, send email to
rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To unsubscribe from this group, send email to
rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit this group at
http://groups.google.com/group/rubyonrails-talk?hl=en.