Rack::MaintenanceMode
Rack::MaintenanceMode is a Rack middleware which manages sending clients maintenance (HTTP 503) responses, when appropriate. It is intended to be robust and extensible, allowing you to meet your particular business needs.
Maintenance mode is easy. What's the deal?
Glad you asked. So, yes, in a lot of applications, maintenance mode is simple: all requests result in a 503 response. But, as applications grow and business rules change, and … life happens … well, that's not always sufficient.
Maybe you'd like to 503 on all public requests, but still allow requests from your IP to pass through it. Maybe you want to 503 only requests to a particular subset of your URLs ("/api", for instance). Maybe you want to be in maintenance mode based on some complex set of business rules (a particular column in the database has a set value, and it's a full moon tonight, and Kim Kardashian is married). Whatever.
Installation
If you're using Bundler, then add the gem to your Gemfile:
gem 'rack-maintenance_mode'
Otherwise:
$ gem install rack-maintenance_mode
How does it work?
So, it's a Rack middleware. That's pretty simple. And, by default, it'll
"just work," by watching for a ENV["MAINTENANCE"]
variable to be set. If
it's set, it'll automatically respond with a default 503 response, containing
a simple HTML page and message:
We are undergoing maintenance right now, please try again soon.
Customization
The default, while useful, probably isn't exactly what you're looking for, is it? So, how do we customize it?
Well, if you're using Rails, then it will integrate with your application automatically using a Railtie. This gives us a few things: first, the middleware will be automatically loaded for you. And second, this gives you a simple way to override the default behaviour.
Customized maintenance mode logic
You can create your own, custom, maintenance mode determination logic in a few
ways. Well, really… a ton of ways, but at the end of the day, you just need
to encapsulate it in something that responds to call
. So, that could be a
simple Proc, a module, a class, and instance, a mock, a stub, … I don't really
care. That's up to you.
Here's a very simple example of a customization using the Railtie integration
when using this gem with Rails (in config/application.rb
):
require File.('../boot', __FILE__)
require 'rails/all'
Bundler.require(:default, Rails.env) if defined?(Bundler)
module MySuperApp
class Application < Rails::Application
…
config.maintenance_mode.if = Proc.new { |env| true }
end
end
Hey look! Every request is now in maintenance mode! Ok, probably not so useful. So, then:
config.maintenance_mode.if = Proc.new { |env|
ENV["MAINTENANCE"] == 'enabled' && !(env['PATH_INFO'] =~ /safe/)
}
Now, it'll only 503 if the ENV's maintenance flag is set and the request is to something other than a "safe" URL (i.e. "http://awesome.com/safe/foo").
Maybe we don't like Procs all over the place. Can't we make a class?
# lib/maintenance.rb
class Maintenance
def call(env)
ENV["MAINTENANCE"] == 'enabled' && !(env['PATH_INFO'] =~ /safe/)
end
end
# config/application.rb
module MySuperApp
class Application < Rails::Application
…
config.maintenance_mode.if = Maintenance.new
end
end
Sure can.
"But wait," I hear you saying. I still get the crappy default maintenance mode response. I'd like to have super-pretty, customized page with CSS-based, shifting perspectives and whatnot. Yes, we can.
Customized responses
Customizing the response is pretty straightforward, as well. Just as with
customizing the maintenance logic, you can also customize the response. The
only real requirement, again, is that you use something that responds to call
,
which can accept a environment hash, and you return a Rack-compatible array.
config.maintenance_mode.response = Proc.new { |env| [503, {'Content-Type' => 'text/plain'}, ['Go away']] }
This allows you to do some super-useful stuff. Like, maybe by inspecting the
env
, you realize that it's an XML request, not HTML. Well, you could send
and XML response. Or, maybe you'd like to load that pretty HTML page you've
spent days crafting:
config.maintenance_mode.response = Proc.new { |env| [503, {'Content-Type' => 'text/html'}, [Rails.root.join("public/503.html").read]] }
Or, maybe you like classes:
# lib/maintenance_response.rb
class MaintenanceResponse
def call(env)
content_type, body = case Pathname.new(env['PATH_INFO'])
when '.xml'
['application/xml', '<error>Maintenance</error>']
when '.json'
['application/json', 'Maintenance']
else
['text/html', '<html><body>Maintenance</body></html>']
end
[503, {'Content-Type' => content_type}, [body]]
end
end
# config/application.rb
module MySuperApp
class Application < Rails::Application
…
config.maintenance_mode.response = MaintenanceResponse.new
end
end