Rack middleware insert_before not working as expected - ruby-on-rails

I'm trying to use the SslEnforcer rack middleware in a Rails 4 project I'm working on. In the documentation of the gem, it is said that :
Cookies should be set before the SslEnforcer processes the headers, but due to the middleware calls chain Rack::SslEnforcer should be inserted before the ActionDispatch::Cookies.
So I've followed the instructions described there, and used this in my application.rb file :
config.middleware.insert_before ActionDispatch::Cookies, Rack::SslEnforcer
However, when I run rake middleware, I still get this order:
$ rake middleware
use Rack::Sendfile
...
...
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
...
...
use Rack::SslEnforcer
...
...
So the Rack::SslEnforcer middleware is not inserted before the ActionDispatch::Cookies middleware as expected. I've also tried to do this:
config.middleware.insert_before 0, Rack::SslEnforcer
which does work as expected, and inserts the Rack::SslEnforcer middleware first in the middleware stack (but I'm not sure this is a good thing to do?).
What am I missing here, do you have any idea why insert_before is not working as expected? Thanks for your help :)

Related

Rails 5 halt a custom http middleware while reloader is reloading.

In rails 5 development mode, I keep getting this Net::ReadTimeout(Net::ReadTimeout) error in my custom session middleware that does a http request to my local mock custom session engine. Also the error only triggered after I made a code change and rails renders an error page. This is extremely annoying and really slows down the development process since we all have to refresh twice to see the result of code change.
After tracing down the code in those middlewares, It appears that my custom session middleware kick off the http request before the reloader complete reloading.
I wondered if we can halt/stop the rack middleware request from keep going down the rack until the reloading is done.
Rails version: 5.1
Ruby version: 2.4.1
I put the following log messages before and after the reloader is done reloading
application.rb
ActiveSupport::Reloader.to_run do
puts 'Reloading'
end
ActiveSupport::Reloader.to_complete do
puts 'DONE Reloading'
end
custom_session_siddleware.rb
def call(env)
...
puts 'Session Processing'
http = Net::HTTP.new(uri, port)
...
#app.call(env)
end
The out put after I make a code change and refresh
Reloading
Session Processing
DONE Reloading
Here are all my middlewares
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use RequestStore::Middleware
use ActionDispatch::RemoteIp
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use MyCustomSession::CustomSessionMiddleware
If your custom middleware is making a request back to the running Rack application, it's going to be very easy for you to have a bad time. But it can be made to work.
By the time your initial request is inside the Reloader, the actual reload has already occurred -- but that's only the unload.
Following the unload, any application (or engine) classes will need to be autoloaded again the next time they're accessed... and I think that's where you're running into an issue: the inner request needs to load something, but it can't do so while the outer request is active.
To fix this, the outer request needs to inform the system that it's in a safe place for another thread/request to load new code. Specifically, you need to wrap the Net::HTTP request (only -- not the #app.call(env)) with permit_concurrent_loads.

Where should I place my middleware file for Rails 5.1?

Previously I had my middleware under lib/middleware/my_middle_ware.rb
However when doing this,
config.middleware.use MyMiddleWare
I receive a
NameError: uninitialized constant
Where is rails looking for the middleware?
Look like rails wasn't looking for it.
I had to do the following for it to work.
Dir["./lib/middleware/*.rb"].each do |file|
require file
end
Create a folder app/middlewares and create your middleware file in this folder.
But unfortunately The app/middlewares folder is not loading even if I added to the load paths in Rails v5.2.2
config.autoload_paths << "#{Rails.root}/app/middlewares"
config.eager_load_paths << "#{Rails.root}/app/middlewares"
So you can use require explicitly as follows, add this line in application.rb
require_relative '../app/middlewares/my_middleware'
and load middleware:
config.middleware.use MyMiddleware
and call rake middleware to see the middleware stack.
I believe you want to add your middleware to either your config/application.rb or your config/environments file.
config.middleware.use MyMiddleWare
This should work and append MyMiddleWare to the bottom of the middleware stack.
Even before app/middleware contents are loaded if 'config.middleware.use' is called, I think you get the 'uninitialized Constant error'. The below should fix
config.middleware.use "MyMiddleWare"
If the above doesn't work, one of the below might be a no.
Is MyMiddleWare in app/middleware/my_middle_ware.rb ?
Is MyMiddleWare in lib/my_middle_ware.rb ?
replacing middleware as a string in config/application.rb to config/environment/{environment} as a constant fixed the issue for me

Why does a barebones Rails controller block if the database is down?

I have a simple Rails controller which does not depend on the database.
class PingController < ActionController::Base
def ping
render text: 'The service is up'
end
end
However, when the database goes down, this controller action blocks.
Why does this happen?
I think there could be three culprits.
A fresh Rails application has the following middleware (Source):
use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run Rails.application.routes
Each request to your controller essentially passes through each one of these middlewares in a chain, one after the other.
Three of these are related to the database and thus depend on the database for every request. You'll notice they're all part of ActiveRecord which is a big tip-off the database is involved.
ActiveRecord::Migration::CheckPending
This checks if there are pending migrations in the database before passing the request through. From what I can tell looking at the source code it checks for pending migrations in all environments.
ActiveRecord::ConnectionAdapters::ConnectionManagement
Except in the testing enviornment, this middleware purges active db connections each request. Perhaps the database connectivity in this middleware is blocking your controller action.
ActiveRecord::QueryCache
This could also be blocking database requests. It is active for all environments from what I can tell.
Rails checks for pending migrations (at least in development mode) before a request. I guess this check blocks your db unrelated action when your database goes down.

Rack Rewrite before force_ssl Rails > 3.2 Heroku

I'm using Rack Rewrite to 301 redirect my apex/root domain to my www domain because my wildcard SSL doesn't support the root domain. I'd also like to force SSL sitewide but can't seem to get my rewrite to occur before the force SSL. I've tried a few things, namely the response in this answer: https://stackoverflow.com/a/8217170/535632
Here's my rewrite code:
Gospot::Application.config.middleware.insert_before(Rack::Runtime, Rack::Rewrite) do
if Rails.env.production?
r301 %r{.*}, Proc.new {|path, rack_env| "http://www.#{rack_env['SERVER_NAME']}#{path}" },
:if => Proc.new {|rack_env| rack_env['SERVER_NAME'] == 'mydomain.com'}
end
end
I've tried:
require 'rack/ssl'
Gospot::Application.config.middleware.insert_before(Rack::SSL, Rack::Rewrite) do
Instead of using config.force_ssl = true in production.rb but I get the following error on Heroku:
No such middleware to insert before: Rack::SSL (RuntimeError)
Is there anyway to run my rack rewrite before the force_ssl? I've found a lot of answers but they all seem to work for Rails < 3.1
I ran into the same issue where I wanted to use rack-rewrite to redirect based on the domain before forcing SSL. I was able to accomplish this in Rails 4 by inserting the Rack::Rewrite middleware before ActionDispatch::SSL as shown in the code below.
config.middleware.insert_before(ActionDispatch::SSL, Rack::Rewrite) do
# redirects / rewrites here
end
The easiest way to do this, depending on your DNS provider, would make your apex DNS record a CNAME for your www subdomain.
If that won't work for you, then you should find a middleware that does exist in your stack to place this rewrite before. Try this:
heroku run rake middleware
To get an idea of what your middleware stack looks like. Choose a piece of middleware that's relatively high up the chain and place the rewrite before it. There's definitely some guess and check that will be going on here, probably, but this will eventually correct your problem.
I had the same problem (Rails 3). Apparently force_ssl adds Rack:SSL to the top of the middleware. To insert before it you have to add it as a string or else you will get an "uninitialized constant" error.
Here is the code I ended up using:
SM::Application.config.middleware.insert_before("Rack::SSL", Rack::Rewrite) do
# rewrite code
end

More control over middleware ordering via Railtie?

There are gems/libraries that would benefit from better control over where they inject their middleware.
In particular it would be nice if exception-handling middleware were at the top of the stack.
Is there any way to do this, or can it only be done by editing config.ru?
Not sure if this is want you need: http://api.rubyonrails.org/classes/Rails/Configuration/MiddlewareStackProxy.html
It is possible do the following:
config.middleware.insert_before Rack::Head, Magical::Unicorns
config.middleware.insert_after Rack::Head, Magical::Unicorns
config.middleware.swap ActionDispatch::Flash, Magical::Unicorns
config.middleware.delete ActionDispatch::Flash
You can just define your Railtie and manipulate the middlewares around.
There is currently no intrinsic way to do this other than manually ordering the middleware stack. I've seen a hack where you could monkey patch Rack::Builder to provide you with some push/pop capabilities.
https://github.com/joshbuddy/rack-capabilities

Resources