I am using Ruby on Rails 3 and I am trying to use the Rack. Since I am not expert in this matter, I would like to know some thing about that.
The following code is from here.
require 'rack'
class Rack::ForceDomain
def initialize(app, domain)
#app = app
#domain = domain
end
def call(env)
request = Rack::Request.new(env)
if #domain and request.host != #domain
fake_request = Rack::Request.new(env.merge("HTTP_HOST" => #domain))
Rack::Response.new([], 301, "Location" => fake_request.url).finish
else
#app.call(env)
end
end
end
What is the variable app and from where its values are retrieved?
From where and how to pass the domain variable in the initialize method?
Rack is a middleware to interface a higher level app (like rails) to a webserver (like mongrel). In rails, you can get this code to work by using:
# config.middleware.use "Rack::ForceDomain", "mydomain.com"
App is a reference to the Rails instance. Domain is added by the person you got that code from, it is not standard Rack initialize.
You do not need to go down to the rack level for what you are doing though for this. I personally prefer to do the rewrite through nginx, but you can do it in rails 3.
In your config/routes.rb file:
constraints(:host => /example.com/) do
root :to => redirect("http://www.example.com")
match '/*path', :to => redirect {|params| "http://www.example.com/#{params[:path]}"}
end
This is from http://railsdog.com/blog/2010/10/29/redirect-non-www-requests-the-rails3-way/
Related
I am developing a gem which is also a Rails::Engine
I would like the engine to add a custom middleware to the host application and I have done that with the following code
module MyModule
class Engine < ::Rails::Engine
isolate_namespace MyModule
initializer "my_gem.middleware" do |app|
app.config.app_middleware.use "MyModule::MyMiddleware"
end
end
end
However this also adds the middleware to those routes defined in the routes.rb file of the Engine. How can I avoid this? I only want the middleware to be added to the host app.
For example, consider the following routes defined in the host application
Rails.application.routes.draw do
mount MyModule::Engine => "/engine"
root :to => Proc.new { |env| [200, {'Content-Type' => 'text/html'}, ["Hello World"]] }
end
Everything under /engine should NOT go through MyMiddleware
I am probably going down the wrong path to achieve this and may be I should look at some other solution?
I don't see how you can do that without putting it in the engine. The initializer is run on boot. You could create an URL matcher in your middleware to skip whatever it does if url is /engine. Note that I had to use app.config.middleware.use, not app.config.app_middleware.use
I have a gem I'm working on that uses a railtie to add a middleware. Very simple stuff, followed the rails guides section almost exactly. Works fine in development/staging/production.
The middleware initializes a hash-like object in the env at a particular key.
But in my capybara tests, this key is only sometimes initialized. I added a debugger to the middleware and found that it isn't called every time I use the visit method.
What's more is that in this particular spec file, there are 4 examples, and each example calls visit once. But when I run the spec file, the middleware is sometimes called 3 times and sometimes called 2 times. Obviously the middleware stack should be called for every request.
tl;dr: sometimes calling visit in my capybara feature specs (with rack-test driver) does not result in my middleware stack being called.
Help?
ruby 2.0.0-p353
rails 4.0.2
capybara 2.2.1
rack-test 0.6.2
EDIT: This is some of the relevant code here: how the middleware is added and what it does. MyGem::Middleware#call is only sometimes called when using Capybara's visit method.
# railtie.rb
module MyGem
class Railtie < Rails::Railtie
initializer "my_gem.configure_rails_initialization" do |app|
app.middleware.use MyGem::Middleware
end
end
end
# middleware.rb
module MyGem
class Middleware
def initialize(app, options={})
#app = app
# options unused
end
def call(env)
# using a special internal version of the Rack::Session::Cookie class
session = MyGem::Rack::Session::Cookie.new(
#app,
:coder => MyGem::Rack::Session::Cookie::Base64::Marshal.new,
:key => ENV_SESSION_KEY,
:path => '/',
:domain => domain(env),
:expire_after => 6.weeks.to_i, # seconds till this expires
:secret => 'my_gem_secret_14f1c4ad25a6be00fe53f5fd2d746167',
)
# use Rack::Session:Cookie method
session.context(env, #app)
end
end
end
Figured it out!
I was also adding a Warden hook that expected the env key to be added after signing users in and out, and if the Warden::Manager middleware was added prior to my gem's middleware, then it would error out when running my hook that expected that env key to be set.
Solution was to do this in my railtie:
app.middleware.insert_before Warden::Manager, MyGem::Middleware
I have a Rack middleware that I want to embed in my Rails app. By embed, I mean injecting it into my application's layout, so that <%= yield %> contains the body of what the Rack app returns.
Here's my current solution:
def show
result = MyRackApp.new.call(request.env)
render :text => result[2].read, :status => result[0]
end
This is problematic, though, since result[2] does not always have a IO style read method, it might be a Rack::File for example.
So, how do I make a Rails controller render a [200, {}, some_io_object] type Rack response? Alternatively, can I do it the other way around with a Rack middleware that is able to render Rails layouts?
I'm using Rails 3.0.9.
If you want to use a rack application from Rails, you have to mount it from your router (routes.rb)
AppName::Application.routes.draw do
# Other routes
mount MyRackApp => "/pathToReachIt"
# root to: '...'
end
More info here
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 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"
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
In theory, we could monkey-patch this validation to comply with webdav verbs like the railsdav project used to do (note that is rails 2 there, in rails 3 one needs to monkey-patch action_dispatch/http/request).
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:
Be above ActionDispatch method validation code, to avoid ActionController::UnknownHttpMethod
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 new rack application "app" will be asked a few methods down the chain, that the old rack application "routes_app" used to resopnd to, we delegate theese to the old original application "routes_app" with a little method_missing magic.
And voila: everything 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.
Note that this doesn't work with passenger. The best way seems to be monkey patching rails.
See: dav4rack wiki
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
The "mount" semantics in routes.rb should be rack-level (not rails/railtie/whatever), to allow, in this way, handeling of HTTP extensions, or at least have a method for this case "mount_rack"
And we have a winner.
https://rails.lighthouseapp.com/projects/8994/tickets/5895-allow-mounting-of-rack-apps-that-deal-with-http-extensions
I'm trying to get a sinatra app as a subpath in my rails 3 app.
Specifically, the resque queuing system has a sinatra based web interface that I would like to have accessible through /resque on my usual rails app.
You can see the project here: http://github.com/defunkt/resque
I found some people talking about adding a rackup file and doing this sort of thing:
run Rack::URLMap.new( \
"/" => ActionController::Dispatcher.new,
"/resque" => Resque::Server.new
)
But I don't really know where to put that or how to make it run. My deployment is with passenger, but it would me nice to also have it running when I run 'rails server' too. Any suggestions?
--edit--
I've made some progress by putting the following in config/routes.rb:
match '/resque(/:page)', :to => Rack::URLMap.new("/resque" => Resque::Server.new)
Which seems to work pretty well, however it loses the public folder, (which is defined within the gem I guess), and as a result, there is no styling information, nor images.
You can setup any rack endpoint as a route in rails 3. This guide by wycats goes over what you are looking for and many of the other things you can do in rails3:
http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/
For example:
class HomeApp < Sinatra::Base
get "/" do
"Hello World!"
end
end
Basecamp::Application.routes do
match "/home", :to => HomeApp
end
Yehuda (/Scott S)'s solution doesn't work for me with Rails 3.0.4 and Sinatra 1.2.1... setting :anchor => false in the matcher is the key:
# in routes.rb
match "/blog" => MySinatraBlogApp, :anchor => false
# Sinatra app
class MySinatraBlogApp < Sinatra::Base
# this now will match /blog/archives
get "/archives" do
"my old posts"
end
end
(answer c/o Michael Raidel - http://inductor.induktiv.at/blog/2010/05/23/mount-rack-apps-in-rails-3/)