goalie 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +0 -0
- data/Rakefile +23 -0
- data/app/controllers/local_errors_controller.rb +30 -0
- data/app/controllers/public_errors_controller.rb +25 -0
- data/app/views/layouts/local_errors.html.erb +29 -0
- data/app/views/local_errors/_request_and_response.html.erb +31 -0
- data/app/views/local_errors/_trace.html.erb +26 -0
- data/app/views/local_errors/diagnostics.html.erb +10 -0
- data/app/views/local_errors/missing_template.html.erb +2 -0
- data/app/views/local_errors/routing_error.html.erb +10 -0
- data/app/views/local_errors/template_error.html.erb +21 -0
- data/app/views/local_errors/unknown_action.html.erb +2 -0
- data/app/views/public_errors/internal_server_error.html +26 -0
- data/app/views/public_errors/not_found.html +26 -0
- data/app/views/public_errors/unprocessable_entity.html +26 -0
- data/lib/goalie/rails.rb +10 -0
- data/lib/goalie/version.rb +3 -0
- data/lib/goalie.rb +169 -0
- data/test/custom_error_pages_test.rb +118 -0
- data/todo.org +8 -0
- metadata +87 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Helder Ribeiro
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/lib/goalie/version.rb"
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
|
6
|
+
Jeweler::Tasks.new do |gemspec|
|
7
|
+
gemspec.version = Goalie::VERSION
|
8
|
+
gemspec.name = "goalie"
|
9
|
+
gemspec.summary = "Custom error pages for Rails"
|
10
|
+
|
11
|
+
gemspec.description = "Middleware to catch exceptions and " <<
|
12
|
+
"Rails Engine to render them. Error-handling views and " <<
|
13
|
+
"controllers can be easily overriden."
|
14
|
+
|
15
|
+
gemspec.email = "helder@gmail.com"
|
16
|
+
gemspec.homepage = "http://github.com/obvio171/goalie"
|
17
|
+
gemspec.authors = ["Helder Ribeiro"]
|
18
|
+
end
|
19
|
+
|
20
|
+
Jeweler::GemcutterTasks.new
|
21
|
+
rescue LoadError
|
22
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class LocalErrorsController < ActionController::Base
|
2
|
+
self.append_view_path "#{File.dirname(__FILE__)}/../views"
|
3
|
+
|
4
|
+
before_filter :set_error_instance_variables
|
5
|
+
|
6
|
+
def routing_error
|
7
|
+
end
|
8
|
+
|
9
|
+
def template_error
|
10
|
+
end
|
11
|
+
|
12
|
+
def missing_template
|
13
|
+
end
|
14
|
+
|
15
|
+
def unknown_action
|
16
|
+
end
|
17
|
+
|
18
|
+
def diagnostics
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def set_error_instance_variables
|
23
|
+
error_params = env['goalie.error_params']
|
24
|
+
|
25
|
+
error_params.each do |name, value|
|
26
|
+
instance_variable_set("@#{name}", value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class PublicErrorsController < ActionController::Base
|
2
|
+
self.append_view_path "#{File.dirname(__FILE__)}/../views"
|
3
|
+
|
4
|
+
def internal_server_error
|
5
|
+
end
|
6
|
+
|
7
|
+
def not_found
|
8
|
+
end
|
9
|
+
|
10
|
+
def unprocessable_entity
|
11
|
+
end
|
12
|
+
|
13
|
+
def conflict
|
14
|
+
render :action => 'internal_server_error'
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_not_allowed
|
18
|
+
render :action => 'internal_server_error'
|
19
|
+
end
|
20
|
+
|
21
|
+
def not_implemented
|
22
|
+
render :action => 'internal_server_error'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
2
|
+
<head>
|
3
|
+
<title>Action Controller: Exception caught</title>
|
4
|
+
<style>
|
5
|
+
body { background-color: #fff; color: #333; }
|
6
|
+
|
7
|
+
body, p, ol, ul, td {
|
8
|
+
font-family: verdana, arial, helvetica, sans-serif;
|
9
|
+
font-size: 13px;
|
10
|
+
line-height: 18px;
|
11
|
+
}
|
12
|
+
|
13
|
+
pre {
|
14
|
+
background-color: #eee;
|
15
|
+
padding: 10px;
|
16
|
+
font-size: 11px;
|
17
|
+
}
|
18
|
+
|
19
|
+
a { color: #000; }
|
20
|
+
a:visited { color: #666; }
|
21
|
+
a:hover { color: #fff; background-color:#000; }
|
22
|
+
</style>
|
23
|
+
</head>
|
24
|
+
<body>
|
25
|
+
|
26
|
+
<%= yield %>
|
27
|
+
|
28
|
+
</body>
|
29
|
+
</html>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<% unless @exception.blamed_files.blank? %>
|
2
|
+
<% if (hide = @exception.blamed_files.length > 8) %>
|
3
|
+
<a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a>
|
4
|
+
<% end %>
|
5
|
+
<pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<%
|
9
|
+
clean_params = @request.filtered_parameters.clone
|
10
|
+
clean_params.delete("action")
|
11
|
+
clean_params.delete("controller")
|
12
|
+
|
13
|
+
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
|
14
|
+
|
15
|
+
def debug_hash(hash)
|
16
|
+
hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
|
17
|
+
end
|
18
|
+
%>
|
19
|
+
|
20
|
+
<h2 style="margin-top: 30px">Request</h2>
|
21
|
+
<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p>
|
22
|
+
|
23
|
+
<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
|
24
|
+
<div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
|
25
|
+
|
26
|
+
<p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p>
|
27
|
+
<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env %></pre></div>
|
28
|
+
|
29
|
+
|
30
|
+
<h2 style="margin-top: 30px">Response</h2>
|
31
|
+
<p><b>Headers</b>: <pre><%=h @response ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<%
|
2
|
+
traces = [
|
3
|
+
["Application Trace", @application_trace],
|
4
|
+
["Framework Trace", @framework_trace],
|
5
|
+
["Full Trace", @full_trace]
|
6
|
+
]
|
7
|
+
names = traces.collect {|name, trace| name}
|
8
|
+
%>
|
9
|
+
|
10
|
+
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
|
11
|
+
|
12
|
+
<div id="traces">
|
13
|
+
<% names.each do |name| %>
|
14
|
+
<%
|
15
|
+
show = "document.getElementById('#{name.gsub /\s/, '-'}').style.display='block';"
|
16
|
+
hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub /\s/, '-'}').style.display='none';"}
|
17
|
+
%>
|
18
|
+
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
<% traces.each do |name, trace| %>
|
22
|
+
<div id="<%= name.gsub /\s/, '-' %>" style="display: <%= name == "Application Trace" ? 'block' : 'none' %>;">
|
23
|
+
<pre><code><%=h trace.join "\n" %></code></pre>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
26
|
+
</div>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<h1>
|
2
|
+
<%=h @exception.class.to_s %>
|
3
|
+
<% if @request.parameters['controller'] %>
|
4
|
+
in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
|
5
|
+
<% end %>
|
6
|
+
</h1>
|
7
|
+
<pre><%=h @exception.message %></pre>
|
8
|
+
|
9
|
+
<%= render :partial => 'trace' %>
|
10
|
+
<%= render :partial => 'request_and_response.erb' %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<h1>Routing Error</h1>
|
2
|
+
<p><pre><%=h @exception.message %></pre></p>
|
3
|
+
<% unless @exception.failures.empty? %><p>
|
4
|
+
<h2>Failure reasons:</h2>
|
5
|
+
<ol>
|
6
|
+
<% @exception.failures.each do |route, reason| %>
|
7
|
+
<li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
|
8
|
+
<% end %>
|
9
|
+
</ol>
|
10
|
+
</p><% end %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<h1>
|
2
|
+
<%=h @exception.original_exception.class.to_s %> in
|
3
|
+
<%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %>
|
4
|
+
</h1>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised:
|
8
|
+
<pre><code><%=h @exception.message %></code></pre>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>Extracted source (around line <b>#<%=h @exception.line_number %></b>):
|
12
|
+
<pre><code><%=h @exception.source_extract %></code></pre></p>
|
13
|
+
|
14
|
+
<p><%=h @exception.sub_template_message %></p>
|
15
|
+
|
16
|
+
<% @real_exception = @exception
|
17
|
+
@exception = @exception.original_exception || @exception %>
|
18
|
+
<%= render :partial => 'trace.erb' %>
|
19
|
+
<% @exception = @real_exception %>
|
20
|
+
|
21
|
+
<%= render :partial => 'request_and_response' %>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/500.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>We're sorry, but something went wrong.</h1>
|
23
|
+
<p>We've been notified about this issue and we'll take a look at it shortly.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/404.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
23
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/422.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The change you wanted was rejected.</h1>
|
23
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
data/lib/goalie/rails.rb
ADDED
data/lib/goalie.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'active_support/core_ext/exception'
|
2
|
+
require 'active_support/notifications'
|
3
|
+
require 'action_dispatch/http/request'
|
4
|
+
|
5
|
+
module Goalie
|
6
|
+
# This middleware rescues any exception returned by the application
|
7
|
+
# and renders nice exception pages.
|
8
|
+
class CustomErrorPages
|
9
|
+
LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
|
10
|
+
|
11
|
+
cattr_accessor :rescue_responses
|
12
|
+
@@rescue_responses = Hash.new(:internal_server_error)
|
13
|
+
@@rescue_responses.update({
|
14
|
+
'ActionController::RoutingError' => :not_found,
|
15
|
+
'AbstractController::ActionNotFound' => :not_found,
|
16
|
+
'ActiveRecord::RecordNotFound' => :not_found,
|
17
|
+
'ActiveRecord::StaleObjectError' => :conflict,
|
18
|
+
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
|
19
|
+
'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
|
20
|
+
'ActionController::MethodNotAllowed' => :method_not_allowed,
|
21
|
+
'ActionController::NotImplemented' => :not_implemented,
|
22
|
+
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
|
23
|
+
})
|
24
|
+
|
25
|
+
FAILSAFE_RESPONSE = [
|
26
|
+
500,
|
27
|
+
{'Content-Type' => 'text/html'},
|
28
|
+
["<html><body><h1>500 Internal Server Error</h1>" <<
|
29
|
+
"If you are the administrator of this website, then please read " <<
|
30
|
+
"this web application's log file and/or the web server's log " <<
|
31
|
+
"file to find out what went wrong.</body></html>"]
|
32
|
+
]
|
33
|
+
|
34
|
+
def initialize(app, consider_all_requests_local = false)
|
35
|
+
@app = app
|
36
|
+
@consider_all_requests_local = consider_all_requests_local
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(env)
|
40
|
+
status, headers, body = @app.call(env)
|
41
|
+
|
42
|
+
# Only this middleware cares about RoutingError. So, let's just
|
43
|
+
# raise it here.
|
44
|
+
# TODO: refactor this middleware to handle the X-Cascade scenario
|
45
|
+
# without having to raise an exception.
|
46
|
+
if headers['X-Cascade'] == 'pass'
|
47
|
+
raise(ActionController::RoutingError,
|
48
|
+
"No route matches #{env['PATH_INFO'].inspect}")
|
49
|
+
end
|
50
|
+
|
51
|
+
[status, headers, body]
|
52
|
+
rescue Exception => exception
|
53
|
+
render_exception(env, exception)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def render_exception(env, exception)
|
58
|
+
log_error(exception)
|
59
|
+
|
60
|
+
request = ActionDispatch::Request.new(env)
|
61
|
+
if @consider_all_requests_local || local_request?(request)
|
62
|
+
rescue_action_locally(request, exception)
|
63
|
+
else
|
64
|
+
rescue_action_in_public(request, exception)
|
65
|
+
end
|
66
|
+
rescue Exception => failsafe_error
|
67
|
+
$stderr.puts("Error during failsafe response: #{failsafe_error}\n" <<
|
68
|
+
"#{failsafe_error.backtrace * "\n "}")
|
69
|
+
FAILSAFE_RESPONSE
|
70
|
+
end
|
71
|
+
|
72
|
+
# Render detailed diagnostics for unhandled exceptions rescued from
|
73
|
+
# a controller action.
|
74
|
+
def rescue_action_locally(request, exception)
|
75
|
+
# TODO this should probably move to the controller, that is, have
|
76
|
+
# http error codes map directly to controller actions, then let
|
77
|
+
# controller handle different exception classes however it wants
|
78
|
+
rescue_actions = Hash.new('diagnostics')
|
79
|
+
rescue_actions.update({
|
80
|
+
'ActionView::MissingTemplate' => 'missing_template',
|
81
|
+
'ActionController::RoutingError' => 'routing_error',
|
82
|
+
'AbstractController::ActionNotFound' => 'unknown_action',
|
83
|
+
'ActionView::Template::Error' => 'template_error'
|
84
|
+
})
|
85
|
+
|
86
|
+
error_params = {
|
87
|
+
:request => request, :exception => exception,
|
88
|
+
:application_trace => application_trace(exception),
|
89
|
+
:framework_trace => framework_trace(exception),
|
90
|
+
:full_trace => full_trace(exception)
|
91
|
+
}
|
92
|
+
request.env['goalie.error_params'] = error_params
|
93
|
+
action = rescue_actions[exception.class.name]
|
94
|
+
response = LocalErrorsController.action(action).call(request.env).last
|
95
|
+
render(status_code(exception), response.body)
|
96
|
+
end
|
97
|
+
|
98
|
+
def rescue_action_in_public(request, exception)
|
99
|
+
error_params = {
|
100
|
+
:request => request, :exception => exception,
|
101
|
+
:application_trace => application_trace(exception),
|
102
|
+
:framework_trace => framework_trace(exception),
|
103
|
+
:full_trace => full_trace(exception)
|
104
|
+
}
|
105
|
+
request.env['custom_error_pages.error_params'] = error_params
|
106
|
+
action = @@rescue_responses[exception.class.name]
|
107
|
+
response = PublicErrorsController.action(action).call(request.env).last
|
108
|
+
render(status_code(exception), response.body)
|
109
|
+
end
|
110
|
+
|
111
|
+
# True if the request came from localhost, 127.0.0.1.
|
112
|
+
def local_request?(request)
|
113
|
+
LOCALHOST.any? { |local_ip|
|
114
|
+
local_ip === request.remote_addr && local_ip === request.remote_ip
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
def status_code(exception)
|
119
|
+
Rack::Utils.status_code(@@rescue_responses[exception.class.name])
|
120
|
+
end
|
121
|
+
|
122
|
+
def render(status, body)
|
123
|
+
[status,
|
124
|
+
{'Content-Type' => 'text/html', 'Content-Length' => body.bytesize.to_s},
|
125
|
+
[body]]
|
126
|
+
end
|
127
|
+
|
128
|
+
def public_path
|
129
|
+
defined?(Rails.public_path) ? Rails.public_path : 'public_path'
|
130
|
+
end
|
131
|
+
|
132
|
+
def log_error(exception)
|
133
|
+
return unless logger
|
134
|
+
|
135
|
+
ActiveSupport::Deprecation.silence do
|
136
|
+
message = "\n#{exception.class} (#{exception.message}):\n"
|
137
|
+
|
138
|
+
if exception.respond_to?(:annoted_source_code)
|
139
|
+
message << exception.annoted_source_code
|
140
|
+
end
|
141
|
+
|
142
|
+
message << " " << application_trace(exception).join("\n ")
|
143
|
+
logger.fatal("#{message}\n\n")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def application_trace(exception)
|
148
|
+
clean_backtrace(exception, :silent)
|
149
|
+
end
|
150
|
+
|
151
|
+
def framework_trace(exception)
|
152
|
+
clean_backtrace(exception, :noise)
|
153
|
+
end
|
154
|
+
|
155
|
+
def full_trace(exception)
|
156
|
+
clean_backtrace(exception, :all)
|
157
|
+
end
|
158
|
+
|
159
|
+
def clean_backtrace(exception, *args)
|
160
|
+
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
|
161
|
+
Rails.backtrace_cleaner.clean(exception.backtrace, *args) :
|
162
|
+
exception.backtrace
|
163
|
+
end
|
164
|
+
|
165
|
+
def logger
|
166
|
+
defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'abstract_unit'
|
2
|
+
|
3
|
+
module ActionDispatch
|
4
|
+
class ShowExceptions
|
5
|
+
private
|
6
|
+
def public_path
|
7
|
+
"#{FIXTURE_LOAD_PATH}/public"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Silence logger
|
11
|
+
def logger
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ShowExceptionsTest < ActionController::IntegrationTest
|
18
|
+
Boomer = lambda do |env|
|
19
|
+
req = ActionDispatch::Request.new(env)
|
20
|
+
case req.path
|
21
|
+
when "/not_found"
|
22
|
+
raise ActionController::UnknownAction
|
23
|
+
when "/method_not_allowed"
|
24
|
+
raise ActionController::MethodNotAllowed
|
25
|
+
when "/not_implemented"
|
26
|
+
raise ActionController::NotImplemented
|
27
|
+
when "/unprocessable_entity"
|
28
|
+
raise ActionController::InvalidAuthenticityToken
|
29
|
+
else
|
30
|
+
raise "puke!"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ProductionApp = ActionDispatch::ShowExceptions.new(Boomer, false)
|
35
|
+
DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer, true)
|
36
|
+
|
37
|
+
test "rescue in public from a remote ip" do
|
38
|
+
@app = ProductionApp
|
39
|
+
self.remote_addr = '208.77.188.166'
|
40
|
+
|
41
|
+
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
42
|
+
assert_response 500
|
43
|
+
assert_equal "500 error fixture\n", body
|
44
|
+
|
45
|
+
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
|
46
|
+
assert_response 404
|
47
|
+
assert_equal "404 error fixture\n", body
|
48
|
+
|
49
|
+
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
|
50
|
+
assert_response 405
|
51
|
+
assert_equal "", body
|
52
|
+
end
|
53
|
+
|
54
|
+
test "rescue locally from a local request" do
|
55
|
+
@app = ProductionApp
|
56
|
+
['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
|
57
|
+
self.remote_addr = ip_address
|
58
|
+
|
59
|
+
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
60
|
+
assert_response 500
|
61
|
+
assert_match /puke/, body
|
62
|
+
|
63
|
+
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
|
64
|
+
assert_response 404
|
65
|
+
assert_match /#{ActionController::UnknownAction.name}/, body
|
66
|
+
|
67
|
+
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
|
68
|
+
assert_response 405
|
69
|
+
assert_match /ActionController::MethodNotAllowed/, body
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
test "localize public rescue message" do
|
74
|
+
# Change locale
|
75
|
+
old_locale, I18n.locale = I18n.locale, :da
|
76
|
+
|
77
|
+
begin
|
78
|
+
@app = ProductionApp
|
79
|
+
self.remote_addr = '208.77.188.166'
|
80
|
+
|
81
|
+
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
82
|
+
assert_response 500
|
83
|
+
assert_equal "500 localized error fixture\n", body
|
84
|
+
|
85
|
+
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
|
86
|
+
assert_response 404
|
87
|
+
assert_equal "404 error fixture\n", body
|
88
|
+
ensure
|
89
|
+
I18n.locale = old_locale
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
test "always rescue locally in development mode" do
|
94
|
+
@app = DevelopmentApp
|
95
|
+
self.remote_addr = '208.77.188.166'
|
96
|
+
|
97
|
+
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
98
|
+
assert_response 500
|
99
|
+
assert_match /puke/, body
|
100
|
+
|
101
|
+
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
|
102
|
+
assert_response 404
|
103
|
+
assert_match /#{ActionController::UnknownAction.name}/, body
|
104
|
+
|
105
|
+
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
|
106
|
+
assert_response 405
|
107
|
+
assert_match /ActionController::MethodNotAllowed/, body
|
108
|
+
end
|
109
|
+
|
110
|
+
test "does not show filtered parameters" do
|
111
|
+
@app = DevelopmentApp
|
112
|
+
|
113
|
+
get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
|
114
|
+
'action_dispatch.parameter_filter' => [:foo]}
|
115
|
+
assert_response 500
|
116
|
+
assert_match ""foo"=>"[FILTERED]"", body
|
117
|
+
end
|
118
|
+
end
|
data/todo.org
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
|
2
|
+
* Make views overridable from inside app
|
3
|
+
* Make controllers overridable from inside app
|
4
|
+
* Give proper credit to author of Rails' ShowExceptions
|
5
|
+
* Give proper credit ao author of Rails' exception_notification
|
6
|
+
* Give proper credit to Rails' default error pages that were copied
|
7
|
+
* Turn into a gem
|
8
|
+
* Create Railtie
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: goalie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 0.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Helder Ribeiro
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-09 00:00:00 -03:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Middleware to catch exceptions and Rails Engine to render them. Error-handling views and controllers can be easily overriden.
|
23
|
+
email: helder@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README
|
30
|
+
files:
|
31
|
+
- MIT-LICENSE
|
32
|
+
- README
|
33
|
+
- Rakefile
|
34
|
+
- app/controllers/local_errors_controller.rb
|
35
|
+
- app/controllers/public_errors_controller.rb
|
36
|
+
- app/views/layouts/local_errors.html.erb
|
37
|
+
- app/views/local_errors/_request_and_response.html.erb
|
38
|
+
- app/views/local_errors/_trace.html.erb
|
39
|
+
- app/views/local_errors/diagnostics.html.erb
|
40
|
+
- app/views/local_errors/missing_template.html.erb
|
41
|
+
- app/views/local_errors/routing_error.html.erb
|
42
|
+
- app/views/local_errors/template_error.html.erb
|
43
|
+
- app/views/local_errors/unknown_action.html.erb
|
44
|
+
- app/views/public_errors/internal_server_error.html
|
45
|
+
- app/views/public_errors/not_found.html
|
46
|
+
- app/views/public_errors/unprocessable_entity.html
|
47
|
+
- lib/goalie.rb
|
48
|
+
- lib/goalie/rails.rb
|
49
|
+
- lib/goalie/version.rb
|
50
|
+
- test/custom_error_pages_test.rb
|
51
|
+
- todo.org
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/obvio171/goalie
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --charset=UTF-8
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 1.3.7
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: Custom error pages for Rails
|
86
|
+
test_files:
|
87
|
+
- test/custom_error_pages_test.rb
|