Skip to content
This repository
Browse code

Make the Failsafe middleware attempt to render 500.html during failsa…

…fe response rendering. Also make the default static failsafe response more friendly, in case 500.html rendering fails. [#2715 state:resolved]

Signed-off-by: Joshua Peek <josh@joshpeek.com>
  • Loading branch information...
commit 34a1ed0df8311aba350043ec7ef373da3058855f 1 parent 4196616
Hongli Lai authored May 27, 2009 josh committed May 27, 2009
42  actionpack/lib/action_controller/failsafe.rb
... ...
@@ -1,4 +1,19 @@
  1
+require 'erb'
  2
+
1 3
 module ActionController
  4
+  # The Failsafe middleware is usually the top-most middleware in the Rack
  5
+  # middleware chain. It returns the underlying middleware's response, but if
  6
+  # the underlying middle raises an exception then Failsafe will log the
  7
+  # exception into the Rails log file, and will attempt to return an error
  8
+  # message response.
  9
+  #
  10
+  # Failsafe is a last resort for logging errors and for telling the HTTP
  11
+  # client that something went wrong. Do not confuse this with the
  12
+  # ActionController::Rescue module, which is responsible for catching
  13
+  # exceptions at deeper levels. Unlike Failsafe, which is as simple as
  14
+  # possible, Rescue provides features that allow developers to hook into
  15
+  # the error handling logic, and can customize the error message response
  16
+  # based on the HTTP client's IP.
2 17
   class Failsafe
3 18
     cattr_accessor :error_file_path
4 19
     self.error_file_path = Rails.public_path if defined?(Rails.public_path)
@@ -27,12 +42,31 @@ def failsafe_response(exception)
27 42
       end
28 43
 
29 44
       def failsafe_response_body
30  
-        error_path = "#{self.class.error_file_path}/500.html"
31  
-        if File.exist?(error_path)
32  
-          File.read(error_path)
  45
+        error_template_path = "#{self.class.error_file_path}/500.html"
  46
+        if File.exist?(error_template_path)
  47
+          begin
  48
+            result = render_template(error_template_path)
  49
+          rescue Exception
  50
+            result = nil
  51
+          end
33 52
         else
34  
-          "<html><body><h1>500 Internal Server Error</h1></body></html>"
  53
+          result = nil
  54
+        end
  55
+        if result.nil?
  56
+          result = "<html><body><h1>500 Internal Server Error</h1>" <<
  57
+            "If you are the administrator of this website, then please read this web " <<
  58
+            "application's log file to find out what went wrong.</body></html>"
35 59
         end
  60
+        result
  61
+      end
  62
+      
  63
+      # The default 500.html uses the h() method.
  64
+      def h(text) # :nodoc:
  65
+        ERB::Util.h(text)
  66
+      end
  67
+      
  68
+      def render_template(filename)
  69
+        ERB.new(File.read(filename)).result(binding)
36 70
       end
37 71
 
38 72
       def log_failsafe_exception(exception)
11  actionpack/test/controller/dispatcher_test.rb
@@ -49,13 +49,14 @@ def test_failsafe_response
49 49
     Dispatcher.any_instance.expects(:dispatch).raises('b00m')
50 50
     ActionController::Failsafe.any_instance.expects(:log_failsafe_exception)
51 51
 
  52
+    response = nil
52 53
     assert_nothing_raised do
53  
-      assert_equal [
54  
-        500,
55  
-        {"Content-Type" => "text/html"},
56  
-        "<html><body><h1>500 Internal Server Error</h1></body></html>"
57  
-      ], dispatch
  54
+      response = dispatch
58 55
     end
  56
+    assert_equal 3, response.size
  57
+    assert_equal 500, response[0]
  58
+    assert_equal({"Content-Type" => "text/html"}, response[1])
  59
+    assert_match /500 Internal Server Error/, response[2]
59 60
   end
60 61
 
61 62
   def test_prepare_callbacks
60  actionpack/test/controller/failsafe_test.rb
... ...
@@ -0,0 +1,60 @@
  1
+require 'abstract_unit'
  2
+require 'stringio'
  3
+require 'logger'
  4
+
  5
+class FailsafeTest < ActionController::TestCase
  6
+  FIXTURE_PUBLIC = "#{File.dirname(__FILE__)}/../fixtures/failsafe".freeze
  7
+  
  8
+  def setup
  9
+    @old_error_file_path = ActionController::Failsafe.error_file_path
  10
+    ActionController::Failsafe.error_file_path = FIXTURE_PUBLIC
  11
+    @app = mock
  12
+    @log_io = StringIO.new
  13
+    @logger = Logger.new(@log_io)
  14
+    @failsafe = ActionController::Failsafe.new(@app)
  15
+    @failsafe.stubs(:failsafe_logger).returns(@logger)
  16
+  end
  17
+  
  18
+  def teardown
  19
+    ActionController::Failsafe.error_file_path = @old_error_file_path
  20
+  end
  21
+  
  22
+  def app_will_raise_error!
  23
+    @app.expects(:call).then.raises(RuntimeError.new("Printer on fire"))
  24
+  end
  25
+  
  26
+  def test_calls_app_and_returns_its_return_value
  27
+    @app.expects(:call).returns([200, { "Content-Type" => "text/html" }, "ok"])
  28
+    assert_equal [200, { "Content-Type" => "text/html" }, "ok"], @failsafe.call({})
  29
+  end
  30
+  
  31
+  def test_writes_to_log_file_on_exception
  32
+    app_will_raise_error!
  33
+    @failsafe.call({})
  34
+    assert_match /Printer on fire/, @log_io.string     # Logs exception message.
  35
+    assert_match /failsafe_test\.rb/, @log_io.string   # Logs backtrace.
  36
+  end
  37
+  
  38
+  def test_returns_500_internal_server_error_on_exception
  39
+    app_will_raise_error!
  40
+    response = @failsafe.call({})
  41
+    assert_equal 3, response.size    # It is a valid Rack response.
  42
+    assert_equal 500, response[0]    # Status is 500.
  43
+  end
  44
+  
  45
+  def test_renders_error_page_file_with_erb
  46
+    app_will_raise_error!
  47
+    response = @failsafe.call({})
  48
+    assert_equal 500, response[0]
  49
+    assert_equal "hello my world", response[2]
  50
+  end
  51
+  
  52
+  def test_returns_a_default_message_if_erb_rendering_failed
  53
+    app_will_raise_error!
  54
+    @failsafe.expects(:render_template).raises(RuntimeError.new("Harddisk is crashing"))
  55
+    response = @failsafe.call({})
  56
+    assert_equal 500, response[0]
  57
+    assert_match /500 Internal Server Error/, response[2]
  58
+    assert_match %r(please read this web application's log file), response[2]
  59
+  end
  60
+end
1  actionpack/test/fixtures/failsafe/500.html
... ...
@@ -0,0 +1 @@
  1
+hello <%= "my" %> world

0 notes on commit 34a1ed0

Please sign in to comment.
Something went wrong with that request. Please try again.