More control over middleware ordering via Railtie? - ruby-on-rails

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

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.

Rack middleware insert_before not working as expected

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 :)

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.

Accessing helpers from the parent app in an isolated Rails engine

I'm writing a configurable Rails engine. I have an authentication_helper configuration option to define which helper should be called in a before_action in all controllers needing authentication.
The problem is that I don't have access to the parent app's helpers from the engine's controllers. My understanding is that this happens because the engine is isolated.
I have considered using a block instead of a method name, but I'm not sure if that would work, or if I would be able to cleanly access the authorization logic from outside my controllers.
Active Admin, which I have used in the past, has a similar configuration option. I have noticed that their engine is not isolated, so perhaps I'm overrating the importance of engine isolation?
Is there an elegant way to have the benefits of engine isolation while also allowing this kind of customization? Or should I just forego isolation altogether?
EDIT #1
Brad Werth pointed my in the right direction, as this works with a regular controller inheriting from ApplicationController::Base:
module MyBigFancyEngine
class Engine < Rails::Engine
isolate_namespace MyBigFancyEngine
config.to_prepare do
# Make the implementing application's helpers available to the engine.
# This is required for the overriding of engine views and helpers to work correctly.
MyBigFancyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
However, my engine's ApplicationController inherits from RocketPant::Base, which does not provide a helper method. I've tried to use a simple include (which works fine for regular controllers), but that doesn't work either (the controller can't find the helper).
Any ideas?
You can expose the implementing application's helpers available to the engine by including the following code in your engine.rb file:
engine.rb
module MyBigFancyEngine
class Engine < Rails::Engine
isolate_namespace MyBigFancyEngine
config.to_prepare do
# Make the implementing application's helpers available to the engine.
# This is required for the overriding of engine views and helpers to work correctly.
MyBigFancyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
The RailsAdmin Engine is also isolated, but they have the same configuration options as you would like to implement. They have configurable before_filters for both authentication and authorization. Have a look at this.
As far as I can tell, they just subclass the parent controller like this::ApplicationController or instead you can configure one (ref).
For your controller you could just create your own EngineController, that inherits from RocketPant::Base and maybe just create a method there that calls the configured authentication method directly via send on the parent controller.
As the RocketPant::Base Class does not inherit from ApplicationController::Base I guess you have to find some custom way around this and can't go the normal ways for Rails Engines. Maybe you could also try to file an issue to the rocket_pant repo, to add the helper method. As far as I read they soon want to inherit from ApplicationController::Base anyway in 2.0 (ref).
I think you impulse on keeping the isolation is good - because a solution that relies on a method 'just being there' is a pain to debug if s.th. goes wrong in the app using your gem (i.e. typo in method name).
Further if your gem evolves, an some day it won't need the method, in an explicit way it's very convient to give a meainingful error:
You could provide a config method to be called in an initializer:
YourGem.configure do |config|
config.add_callback { MyApp.doIt() }
end
I found this discussion particularly insightful. There are also some interesting ideas in the Rails Engine API under Isolated engine helpers.
The Rails Engine API docs helped me figure out a good solution for url_helpers
You probably moved on by now, but if anyone else needs access to the "parent app" application helpers. You could always just include it explicitly in the application controller in your engine. like so:
include Rails.application.helpers

Where do you put your Rack middleware files and requires?

I'm in the process of refactoring some logic built into a Rails application into middleware, and one annoyance I've run into is a seeming lack of convention for where to put them.
Currently I've settled on app/middleware but I could just as easily move it to vendor/middleware or maybe vendor/plugins/middleware...
The biggest problem is having to require the individual files at the top of config/environment.rb
require "app/middleware/system_message"
require "app/middleware/rack_backstage"
or else I get uninitialized constant errors on the config.middleware.use lines. That could get messy very quickly. I'd rather this was tucked away in an initializer somewhere.
Is there a conventional place to put this stuff?
The specific answer I'm looking for with this bounty is: where can I put the require lines so that they are not cluttering the environment.rb file but still get loaded before the config.middleware.use calls? Everything I have tried leads to uninitialized constant errors.
Update: Now that we're using Rails 3.0, I treat a Rails app like any other Rack app; code files for middleware go in lib (or a gem listed in Gemfile) and are required and loaded in config.ru.
As of Rails 3.2, Rack middleware belongs in the app/middleware directory.
It works "out-of-the-box" without any explicit require statements.
Quick example:
I'm using a middleware class called CanonicalHost which is implemented in app/middleware/canonical_host.rb. I've added the following line to production.rb (note that the middleware class is explicitly given, rather than as a quoted string, which works for any environment-specific config files):
config.middleware.use CanonicalHost, "example.com"
If you're adding middleware to application.rb, you'll need to include quotes, as per #mltsy's comment.
config.middleware.use "CanonicalHost", "example.com"
You can put it in lib/tableized/file_name.rb. As long as the class you're trying to load is discoverable by its filename, Rails will automatically load the file necessary. So, for example:
config.middleware.use "MyApp::TotallyAwesomeMiddleware"
You would keep in:
lib/my_app/totally_awesome_middleware.rb
Rails catches const_missing and attemts to load files corresponding to the missing constants automatically. Just make sure your names match and you're gravy. Rails even provides nifty helpers that'll help you identify the path for a file easily:
>> ChrisHeald::StdLib.to_s.tableize.singularize
=> "chris_heald/std_lib"
So my stdlib lives in lib/chris_heald/std_lib.rb, and is autoloaded when I reference it in code.
In my Rails 3.2 app, I was able to get my middleware TrafficCop loading by putting it at app/middleware/traffic_cop.rb, just as #MikeJarema described. I then added this line to my config/application.rb, as instructed:
config.middleware.use TrafficCop
However, upon application start, I kept getting this error:
uninitialized constant MyApp::Application::TrafficCop
Explicitly specifying the root namespace didn't help either:
config.middleware.use ::TrafficCop
# uninitialized constant TrafficCop
For some reason (which I've yet to discover), at this point in the Rails lifecycle, app/middleware wasn't included in the load paths. If I removed the config.middleware.use line, and ran the console, I could access the TrafficCop constant without any issue. But it couldn't find it in app/middleware at config time.
I fixed this by enclosing the middleware class name in quotes, like so:
config.middleware.use "TrafficCop"
This way, I would avoid the uninitialized constant error, since Rails isn't trying to find the TrafficCop class just yet. But, when it starts to build the middleware stack, it will constantize the string. By this time, app/middleware is in the load paths, and so the class will load correctly.
For Rails 3:
#config/application.rb
require 'lib/rack/my_adapter.rb'
module MyApp
class Application < Rails::Application
config.middleware.use Rack::MyAdapter
end
end
I'm not aware of a convention, but why not put it in the /lib directory? Files in there get automatically loaded by Rails.
You could create an initializer which requires the necessary files and then leave the files wherever you want.
According to this the initializers are executed before the rack middleware is loaded.
The working solution I have so far is moving the middleware requires to config/middleware.rb and requiring that file in environment.rb, reducing it to a single require which I can live with.
I'd still like to hear how other people have solved this seemingly basic problem of adding middleware to Rails.

Resources