I have some custom middleware that is include at the top of my stack and what i need to do in it is access the rails cache. How can I load/access the rails cache from the middleware without having to load the whole rails app up first?
Rails on Rack
Rails Application's Rack Object
ApplicationName::Application is the primary Rack application object of a Rails application. Any Rack compliant web server should be using ApplicationName::Application object to serve a Rails application. Rails.application refers to the same application object.
rails server
rails server does the basic job of creating a Rack::Server object and starting the webserver.
Here's how rails server creates an instance of Rack::Server
Rails::Server.new.tap do |server|
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
end
The Rails::Server inherits from Rack::Server and calls the Rack::Server#start method this way:
class Server < ::Rack::Server
def start
...
super
end
end
Here's how it loads the middlewares:
def middleware
middlewares = []
middlewares << [Rails::Rack::Debugger] if options[:debugger]
middlewares << [::Rack::ContentLength]
Hash.new(middlewares)
end
courtsy : http://guides.rubyonrails.org/rails_on_rack.html
and also u can follow : https://devcenter.heroku.com/articles/rack-cache-memcached-rails31
I just did a test, and
Rails.cache.fetch('test') { 'A test' }
inside of a custom middleware, and it worked just fine.
Can you try that?
Related
So... I have a bit of a conundrum.
I've written some rack middleware, it's stored on disk at app/middleware/eat_bacon.rb, it looks something like
module Middleware
class EatBacon
def initialize(app)
#app = app
end
def call(env)
Thread.current[:mouth] = 'Bacon'
#app.call(env)
end
end
end
I'm trying to load / use some middleware in a rails-api (rails 3.2.19)
In my config/application.rb I've gotten the middleware to load in two different ways, one way works when running the app, the other way works for rspec
Works for running app
config.middleware.insert_before 0, 'Middleware::EatBacon'
but when I run rspec, I get
/Volumes/HardDrive/Me/.rvm/gems/ruby-2.0.0-p451#bacon/gems/activesupport-3.2.19/lib/active_support/inflector/methods.rb:230:in `block in constantize': uninitialized constant Middleware (NameError)
Works for RSpec
config.after_initialize do
config.middleware.insert_before 0, 'Middleware::EatBacon'
end
But now the middleware is not loaded when the application runs, ie Thread.current[:mouth] never gets bacon
Seems weird to have to implement this differently in config/environments/test.rb and config/environments/production.rb, but I guess thats what I'll end up doing unless someone has a better idea
The way I solved this was to first move the middleware to the lib folder:
lib/middleware/eats_bacon.rb
AND Then, in config/application.rb I added a require_relative, so that file now looks like
...
Bundler.require(*Rails.groups)
require_relative '../lib/middleware/eats_bacon.rb'
module BaconEaterApp
class Application < Rails::Application
...
Then at the bottom of that file
...
# Middleware
config.middleware.insert_before 0, 'Middleware::EatBacon'
...
I have a Rack middleware that inserts itself automatically into the app when the gem is specified within the Gemfile:
gem 'headhunter'
Then the middleware registers itself like this:
module Headhunter
module Rails
class Railtie < ::Rails::Railtie
initializer "headhunter.hijack" do |app|
head_hunter = Runner.new(::Rails.root)
at_exit do
head_hunter.report
head_hunter.clean_up!
end
app.middleware.insert(0, Headhunter::Rack::CapturingMiddleware, head_hunter)
end
end
end
end
Now I want to make sure that the middleware is actually inserted in an RSpec feature/request spec.
I have a dummy Rails app in spec/dummy which does the following:
require 'spec_helper'
feature 'Middleware integration' do
scenario "Integrating the middleware into the Rack stack" do
expect(Headhunter::Rack::CapturingMiddleware.any_instance).to receive(:call)
visit posts_path
end
end
Sadly this fails and I don't know why. By placing a binding.pry within call I can tell for sure that it is called.
Is there maybe even a better way to test this? Thanks a lot for help.
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 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/
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