# Rack::Robustness, the rescue clause of your Rack stack. Rack::Robustness is the rescue clause of your Rack's call stack. In other words, a middleware that ensures the robustness of your web stack, because exceptions occur either intentionally or unintentionally. It scales from zero configuration (a default shield) to specific rescue clauses for specific errors. [![Build Status](https://secure.travis-ci.org/blambeau/rack-robustness.png)](http://travis-ci.org/blambeau/rack-robustness) [![Dependency Status](https://gemnasium.com/blambeau/rack-robustness.png)](https://gemnasium.com/blambeau/rack-robustness) ## Links https://github.com/blambeau/rack-robustness ## Why? In my opinion, Sinatra's error handling is sometimes a bit limited for real-case needs. So I came up with something a bit more Rack-ish, that allows handling exceptions actively, because exceptions occur and that you'll handle them... enventually. A more theoretic argumentation would be: * Exceptions occur, because you can't always test/control boundary conditions. E.g. your code can pro-actively test that a file exists before reading it, but it cannot pro-actively test that the user removes the network cable in the middle of a download. * The behavior to adopt when obstacles occur is not necessary defined where the exception is thrown, but often higher in the call stack. * In ruby web apps, the Rack's call stack is a very important part of your stack. Middlewares, routes and controllers do rarely rescue all errors, so it's still your job to rescue errors higher in the call stack. Rack::Robustness is therefore a try/catch mechanism as a middleware, to be used along the Rack call stack as you would use a standard one in a more conventional call stack: ```java try { // main shield, typically in a main try { // try to achieve a goal here } catch (...) { // fallback to an alternative } // continue your flow } catch (...) { // something goes really wrong, inform the user as you can } ``` becomes: ```ruby class Main < Sinatra::Base # main shield, main = rack top level use Rack::Robustness do # something goes really wrong, inform the user as you can # probably a 5xx http status here end # continue your flow use Other::Useful::Middlewares use Rack::Robustness do # fallback to an alternative # 3xx, 4xx errors maybe end # try to achieve your goal through standard routes end ``` ## Examples ```ruby class App < Sinatra::Base ## # Catch everything but hide root causes, for security reasons, for instance. # # This handler should never be fired unless the application has a bug... # use Rack::Robustness do |g| g.status 500 g.content_type 'text/plain' g.body 'A fatal error occured.' end ## # Some middleware here for logging, content length of whatever. # # Those middleware might fail, even if unlikely. # use ... use ... ## # Catch some exceptions that denote client errors by convention in our app. # # Those exceptions are considered safe, so the message is sent to the user. # use Rack::Robustness do |g| g.no_catch_all # do not catch all errors g.status 400 # default status to 400, client error g.content_type 'text/plain' # a default content-type, maybe g.body{|ex| ex.message } # by default, send the message # catch ArgumentError, it denotes a coercion error in our app g.on(ArgumentError) # we use SecurityError for handling forbidden accesses. # The default status is 403 here g.on(SecurityError){|ex| 403 } end get '/some/route/:id' do |id| id = Integer(id) # will raise an ArgumentError if +id+ not an integer ... end get '/private' do |id| raise SecurityError unless logged? ... end end ``` ## Without configuration ```ruby ## # Catches all errors. # # Respond with # status: 500, # headers: {'Content-Type' => 'text/plain'} # body: [ "Sorry, an error occured." ] # use Rack::Robustness ``` ## Specifying static status, headers and/or body ```ruby ## # Catches all errors. # # Respond as specified. # use Rack::Robustness do |g| g.status 400 g.headers 'Content-Type' => 'text/html' g.content_type 'text/html' # shortcut over headers g.body "

an error occured

" end ``` ## Specifying dynamic status, content_type and/or body ```ruby ## # Catches all errors. # # Respond as specified. # use Rack::Robustness do |g| g.status{|ex| ArgumentError===ex ? 400 : 500 } # global dynamic headers g.headers{|ex| {'Content-Type' => 'text/plain', ...} } # local dynamic and/or static headers g.headers 'Content-Type' => lambda{|ex| ... }, 'Foo' => 'Bar' # dynamic content type g.content_type{|ex| ...} # dynamic body (String allowed here) g.body{|ex| ex.message } end ``` ## Specific behavior for specific errors ```ruby ## # Catches all errors using defaults as above # # Respond to specific errors as specified by 'on' clauses. # use Rack::Robustness do |g| g.status 500 # this is the default behavior, as above g.content_type 'text/plain' # ... # Override status on TypeError and descendants g.on(TypeError){|ex| 400 } # Override body on ArgumentError and descendants g.on(ArgumentError){|ex| ex.message } # Override everything on SecurityError and descendants # Default headers will be merged with returned ones so content-type will be # "text/plain" unless specified below g.on(SecurityError){|ex| [ 403, { ... }, [ "Forbidden, sorry" ] ] } end ``` ## Don't catch all! ```ruby ## # Catches only errors specified in 'on' clauses, using defaults as above # # Re-raise unrecognized errors # use Rack::Robustness do |g| g.no_catch_all g.on(TypeError){|ex| 400 } ... end ```