Skip to content

Commit

Permalink
Working with exception callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Neighman committed May 30, 2010
1 parent b973e81 commit c425a4b
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 43 deletions.
10 changes: 4 additions & 6 deletions Gemfile
@@ -1,16 +1,14 @@
# A sample Gemfile
source :gemcutter
#
# gem "rails"
source :rubygems

gem 'rake'
gem 'rack'
gem 'extlib'
gem 'hashie'
gem 'dirge'
gem 'any_view', :path => '/Users/dneighman/Projects/any_view'
gem 'tilt', :path => '/Users/dneighman/Projects/tilt'
gem 'pancake', :path => '/Users/dneighman/Projects/pancake'
gem 'any_view', '>=0.2'
gem 'tilt', '>=0.10'
gem 'pancake', '>=0.2'

group(:test) do
gem 'rspec'
Expand Down
36 changes: 25 additions & 11 deletions Gemfile.lock
Expand Up @@ -35,20 +35,37 @@ dependencies:
tilt:
group:
- :default
version: ">= 0"
version: ">= 0.10"
specs:
- rake:
version: 0.8.7
- builder:
version: 2.1.2
- i18n:
version: 0.3.7
- memcache-client:
version: 1.8.3
- tzinfo:
version: 0.3.20
- activesupport:
version: 3.0.0.beta3
- columnize:
version: 0.3.1
- linecache:
version: "0.43"
- ruby-debug-base:
version: 0.10.3
- ruby-debug:
version: 0.10.3
- tilt:
version: "0.9"
source: 1
version: "0.10"
- any_view:
version: 0.2.0pre
source: 2
source: 1
- dirge:
version: 0.0.4
version: 0.0.3
- extlib:
version: 0.9.14
version: 0.9.15
- fuzzyhash:
version: 0.0.11
- hashie:
Expand All @@ -60,7 +77,7 @@ specs:
- rack-accept-media-types:
version: "0.9"
- rack-test:
version: 0.5.3
version: 0.5.4
- thor:
version: 0.13.6
- usher:
Expand All @@ -72,14 +89,11 @@ specs:
source: 0
- rspec:
version: 1.3.0
hash: 93c8540ea4ace449094d11b11cd1802ece241cb1
hash: 2eeac04dc68d705e2823b841a6e7887b416d09d5
sources:
- Path:
path: !ruby/object:Pathname
path: /Users/dneighman/Projects/pancake
- Path:
path: !ruby/object:Pathname
path: /Users/dneighman/Projects/tilt
- Path:
path: !ruby/object:Pathname
path: /Users/dneighman/Projects/any_view
Expand Down
17 changes: 0 additions & 17 deletions README.rdoc

This file was deleted.

70 changes: 70 additions & 0 deletions README.textile
@@ -0,0 +1,70 @@
h1. Rack::Rescue

Rack Rescue is a middleware for handling exceptions in your rack application.

Rack::Rescue will set the correct status, render a template, and optionally run any number of callbacks when an exception is experienced.

Just insert the middleware in your stack, and any unhandled exception will trigger the handling. Unknown exceptions trigger a basic error page, with a 500 status.

Rather than launch into a long-winded chat about what it can do, I'll suffice with a list.

* Per exception status codes
* Per exception templates (with a fallback template)
* Content type negotiation (:html, :xml, :js etc)
* cascading tempate locations
* template naming of the usual <name>.<format>.<(erb|haml|other tilt template)>
* template naming including the RACK_ENV
** <name>.<RACK_ENV>.<format>.<(tilt template engine ext)>
** If the more specific template is not found, it will fall back to just name, format and ext
* template inheritance

h2. Adding Exceptions

<pre><code>
# Add to the default list
Rack::Rescue::Exceptions.add_defaults("MyException", "AnotherException", :status => 404)
Rack::Rescue::Exceptions.add_defaults("DifferetOne", :status => 455, :template => "different")

# Add to a specific Rack::Rescue
use Rack::Rescue do |rr|
rr.add("SomeException", :status => 478, :template => "something")
end
</code></pre>

h2. Add a template location

By default templates should be located in

&lt;handler root&gt;/rack_rescue_templates/**/*

To add a new handler root

<pre><code>
Rack::Rescue::Handler.roots << File.join(Dir.pwd, "views")
</pre></code>

This will then look in "./views/rack_rescue_template" directory for you templates. If it doesn't find them in there it will fall back to the templates located in any other roots, or finall to the default templates it ships with.

h2. Template Naming

Templates are named with the following form, in the following preference:

1. name.env.format.ext # error.development.html.erb
2. name.format.ext # error.html.erb

If the first is not matched, the second will be also checked


h1. Note on Patches/Pull Requests

* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a
future version unintentionally.
* Commit, do not mess with rakefile, version, or history.
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.

h2. Copyright

Copyright (c) 2010 Daniel Neighman. See LICENSE for details.
40 changes: 31 additions & 9 deletions lib/rack/rescue.rb
Expand Up @@ -8,6 +8,8 @@ class Rescue
autoload :Responder, 'rack/rescue/responder'

inheritable_inner_classes "Exceptions", "Handler", "Responder"
class_inheritable_accessor :error_handlers
self.error_handlers = []

# Make sure there are a minimum set of formats available
[:html, :xml, :text, :js, :json].each do |f|
Expand All @@ -16,13 +18,18 @@ class Rescue

def self.default_formats
return @default_formats if @default_formats

if ENV['RACK_ENV'] == 'production'
@default_formats = Pancake::MimeTypes.groups.keys.map(&:to_sym)
else
formats = Pancake::MimeTypes.groups.keys.map(&:to_sym)
Pancake::MimeTypes.groups.keys.map(&:to_sym)
end
end

def self.add_handler(&blk)
error_handlers << blk
end

def initialize(app, options = {})
@app = app
load_defaults = options.fetch(:load_default_exceptions, true)
Expand All @@ -42,30 +49,45 @@ def formats=(fmts)
def call(env)
@app.call(env)
rescue Exception => e
handle_exception(env, e)
end

private
# Apply the layout if it exists
# @api private
def apply_layout(env, content, opts)
if layout = env['layout']
layout.format = opts[:format]
layout.content = content
layout.template_name = opts[:layout] if layout.template_name?(opts[:layout], opts)
layout
else
content
end
end

def handle_exception(env, e)
# negotiate the content
request = Rack::Request.new(env)
responder = Responder.new(env, self)
opts = {}
opts[:env] = env
if request.path =~ /\.(.\w)$/
opts[:format] = $1
end
responder.negotiate! opts

opts[:format] = responder.content_type
opts[:layout] ||= 'error'

self.class.error_handlers.each{|blk| blk.call(e, env, opts)}

handler = @exceptions_map[e].nil? ? @exceptions_map[RuntimeError] : @exceptions_map[e]

resp, status = handler.render_error(e, opts), handler.status

layout = env['layout']
if layout
layout.format = opts[:format]
layout.template_name = :error if layout.template_name?(:error)
resp = layout
end
response = apply_layout(env, resp, opts)

Rack::Response.new(resp, status, responder.headers).finish
Rack::Response.new(response, status, responder.headers).finish
end
end
end
26 changes: 26 additions & 0 deletions rack-rescue.gemspec
@@ -0,0 +1,26 @@
# -*- encoding: utf-8 -*-
require 'bundler'

Gem::Specification.new do |s|
s.name = 'rack-rescue'
s.version = '0.1.0'
s.homepage = %q{http://github.com/hassox/rack-rescue}
s.authors = ["Daniel Neighman"]
s.autorequire = %q{rack/rescue}
s.date = Date.today
s.description = %q{Rescue Handler for Rack}
s.summary =%q{Rescue Handler for Rack}
s.email = %q{has.sox@gmail.com}

s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.6}

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.files = Dir[File.join(Dir.pwd, "**/*")]

s.add_bundler_dependencies

end


32 changes: 32 additions & 0 deletions spec/rack-rescue_spec.rb
Expand Up @@ -3,6 +3,10 @@
describe "RackRescue" do
class RRUnknownException < RuntimeError; end

after do
Rack::Rescue.error_handlers.clear
end

def safe_endpoint(msg = "OK")
lambda{|e| Rack::Response.new(msg)}
end
Expand Down Expand Up @@ -116,4 +120,32 @@ def build_stack(endpoint = safe_endpoint)
body.should include("<h1>Error Layout</h1>")
end

it "should allow me to add error handlers and they should execute on each exception" do
layout_dir = File.join(File.expand_path(File.dirname(__FILE__)), 'rack', 'rescue', 'fixtures', 'layouts')
Rack::Rescue.add_handler do |exception, env, options|
options[:format].should == :html
options[:layout].should == 'error'
end
Rack::Rescue.add_handler do |exception, env, options|
options[:format] = :xml
options[:layout] = 'custom_error_layout'
end
Rack::Rescue.add_handler do |exception, env, options|
options[:format].should == :xml
options[:layout].should == 'custom_error_layout'
end

app = Rack::Builder.new do
use Wrapt do |wrapt|
wrapt.layout_dirs << layout_dir
end
use Rack::Rescue
the_app = lambda{|e| raise "Halp"}
run the_app
end

result = app.call(Rack::MockRequest.env_for("/"))
result[2].body.join.should include("Custom Error Layout")
end

end
4 changes: 4 additions & 0 deletions spec/rack/rescue/fixtures/layouts/custom_error_layout.xml.erb
@@ -0,0 +1,4 @@
<errors>
<template>Custom Error Layout</template>
<error><%= yield %></error>
</errors>

0 comments on commit c425a4b

Please sign in to comment.