diff --git a/lib/rack.rb b/lib/rack.rb index 5b87ea1bc..3a3bd5ed0 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -25,6 +25,7 @@ module Rack autoload :ContentLength, "rack/content_length" autoload :ContentType, "rack/content_type" autoload :ETag, "rack/etag" + autoload :ErrorHandler, "rack/error_handler" autoload :Events, "rack/events" autoload :File, "rack/file" autoload :Files, "rack/files" diff --git a/lib/rack/error_handler.rb b/lib/rack/error_handler.rb new file mode 100644 index 000000000..9b0d0a845 --- /dev/null +++ b/lib/rack/error_handler.rb @@ -0,0 +1,31 @@ +require_relative 'constants' + +module Rack + class ErrorHandler + def initialize(app) + @app = app + end + + def log_error(env, error) + env[RACK_ERRORS].puts(error.full_message) + end + + def bad_request(error) + [400, {}, ["Bad Request: #{error.class}"]] + end + + def internal_server_error(error) + [500, {}, ["Internal Server Error: #{error.class}"]] + end + + def call(env) + @app.call(env) + rescue BadRequest => error + log_error(env, error) + return bad_request(error) + rescue ScriptError, StandardError => error + log_error(env, error) + return internal_server_error(error) + end + end +end diff --git a/test/spec_error_handler.rb b/test/spec_error_handler.rb new file mode 100644 index 000000000..4f60d3280 --- /dev/null +++ b/test/spec_error_handler.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require_relative 'helper' +require 'tempfile' +require 'fileutils' + +separate_testing do + require_relative '../lib/rack/error_handler' + require_relative '../lib/rack/lint' + require_relative '../lib/rack/mock_request' +end + +describe Rack::ErrorHandler do + it "catches bad requests as 400 Bad Request" do + error = Class.new(StandardError) + error.include(Rack::BadRequest) + + app = lambda do |env| + raise error, "b00m" + end + + middleware = Rack::Lint.new(Rack::ErrorHandler.new(app)) + response = middleware.call(Rack::MockRequest.env_for("/")) + + response[0].must_equal 400 + response[2].to_ary.must_equal ["Bad Request: #{error}"] + end + + it "catches standard errors as 500 Internal Server Error" do + error = Class.new(StandardError) + + app = lambda do |env| + raise error, "b00m" + end + + middleware = Rack::Lint.new(Rack::ErrorHandler.new(app)) + response = middleware.call(Rack::MockRequest.env_for("/")) + + response[0].must_equal 500 + response[2].to_ary.must_equal ["Internal Server Error: #{error}"] + end + + it "logs errors to the rack.errors stream" do + app = lambda do |env| + raise "b00m" + end + + middleware = Rack::Lint.new(Rack::ErrorHandler.new(app)) + errors = StringIO.new + response = middleware.call(Rack::MockRequest.env_for("/", "rack.errors" => errors)) + + errors.string.must_match(/RuntimeError/) + errors.string.must_match(/b00m/) + + response[0].must_equal 500 + response[2].to_ary.must_equal ["Internal Server Error: RuntimeError"] + end +end