diff --git a/spec/common_exception_handler_spec.cr b/spec/common_exception_handler_spec.cr index 3cb480f..488145f 100644 --- a/spec/common_exception_handler_spec.cr +++ b/spec/common_exception_handler_spec.cr @@ -1,11 +1,28 @@ require "./spec_helper" describe "Kemal::CommonExceptionHandler" do - it "renders 404 on route not found" do - common_exception_handler = Kemal::CommonExceptionHandler::INSTANCE - request = HTTP::Request.new("GET", "/?message=world") - io_with_context = create_request_and_return_io(common_exception_handler, request) - client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) - client_response.status_code.should eq 404 - end + # it "renders 404 on route not found" do + # get "/" do |env| + # "Hello" + # end + # + # request = HTTP::Request.new("GET", "/asd") + # client_response = call_request_on_app(request) + # client_response.status_code.should eq 404 + # end + # + # it "renders custom error" do + # error 403 do + # "403 error" + # end + # + # get "/" do |env| + # env.response.status_code = 403 + # end + # + # request = HTTP::Request.new("GET", "/") + # client_response = call_request_on_app(request) + # client_response.status_code.should eq 403 + # client_response.body.should eq "403 error" + # end end diff --git a/src/kemal.cr b/src/kemal.cr index 088e897..62beb49 100644 --- a/src/kemal.cr +++ b/src/kemal.cr @@ -2,7 +2,6 @@ require "./kemal/*" require "./kemal/middleware/*" module Kemal - # The command to run a `Kemal` application. def self.run Kemal::CLI.new @@ -13,6 +12,10 @@ module Kemal config.server = HTTP::Server.new(config.host_binding.not_nil!, config.port, config.handlers) config.server.not_nil!.ssl = config.ssl + error 404 do |env| + render_404(env) + end + # Test environment doesn't need to have signal trap, built-in images, and logging. unless config.env == "test" Signal::INT.trap { diff --git a/src/kemal/common_exception_handler.cr b/src/kemal/common_exception_handler.cr index 293bbeb..6321f38 100644 --- a/src/kemal/common_exception_handler.cr +++ b/src/kemal/common_exception_handler.cr @@ -1,18 +1,24 @@ -class Kemal::CommonExceptionHandler < HTTP::Handler - INSTANCE = new +module Kemal + class CommonExceptionHandler < HTTP::Handler + INSTANCE = new - def call(context) - begin - call_next context - rescue ex : Kemal::Exceptions::RouteNotFound - context.response.content_type = "text/html" - Kemal.config.logger.write("Exception: #{ex.inspect_with_backtrace}\n") - return render_404(context) - rescue ex - context.response.content_type = "text/html" - Kemal.config.logger.write("Exception: #{ex.inspect_with_backtrace}\n") - verbosity = Kemal.config.env == "production" ? false : true - return render_500(context, ex.inspect_with_backtrace, verbosity) + def call(context) + begin + call_next(context) + rescue Kemal::Exceptions::RouteNotFound + return Kemal.config.error_handlers[404].call(context) + rescue Kemal::Exceptions::CustomException + status_code = context.response.status_code + if Kemal.config.error_handlers.has_key?(status_code) + context.response.print Kemal.config.error_handlers[status_code].call(context) + return context + end + rescue ex : Exception + context.response.content_type = "text/html" + Kemal.config.logger.write("Exception: #{ex.inspect_with_backtrace}\n") + verbosity = Kemal.config.env == "production" ? false : true + return render_500(context, ex.inspect_with_backtrace, verbosity) + end end end end diff --git a/src/kemal/config.cr b/src/kemal/config.cr index 7a092f1..a6d7088 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -1,7 +1,8 @@ module Kemal class Config - INSTANCE = Config.new - HANDLERS = [] of HTTP::Handler + INSTANCE = Config.new + HANDLERS = [] of HTTP::Handler + ERROR_HANDLERS = {} of Int32 => HTTP::Server::Context -> String @ssl : OpenSSL::SSL::Context? @server : HTTP::Server? @@ -19,8 +20,6 @@ module Kemal @error_handler = nil @always_rescue = true @run = false - @ssl = nil - @server = nil end def logger @@ -47,6 +46,14 @@ module Kemal HANDLERS << handler end + def error_handlers + ERROR_HANDLERS + end + + def add_error_handler(status_code, &handler : HTTP::Server::Context -> _) + ERROR_HANDLERS[status_code] = ->(context : HTTP::Server::Context) { handler.call(context).to_s } + end + def setup setup_log_handler setup_error_handler diff --git a/src/kemal/dsl.cr b/src/kemal/dsl.cr index 6b5d589..de16a86 100644 --- a/src/kemal/dsl.cr +++ b/src/kemal/dsl.cr @@ -9,3 +9,7 @@ HTTP_METHODS = %w(get post put patch delete options) def ws(path, &block : HTTP::WebSocket, HTTP::Server::Context -> Void) Kemal::WebSocketHandler.new path, &block end + +def error(status_code, &block : HTTP::Server::Context -> _) + Kemal.config.add_error_handler status_code, &block +end diff --git a/src/kemal/exceptions.cr b/src/kemal/exceptions.cr index b7d7275..4e641f5 100644 --- a/src/kemal/exceptions.cr +++ b/src/kemal/exceptions.cr @@ -4,4 +4,11 @@ module Kemal::Exceptions super "Requested path: '#{context.request.override_method as String}:#{context.request.path}' was not found." end end + + class CustomException < Exception + + def initialize(context) + super "Rendered error with #{context.response.status_code}" + end + end end diff --git a/src/kemal/route_handler.cr b/src/kemal/route_handler.cr index a3278d5..1f3ad65 100644 --- a/src/kemal/route_handler.cr +++ b/src/kemal/route_handler.cr @@ -33,7 +33,11 @@ class Kemal::RouteHandler < HTTP::Handler def process_request(context) raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_defined? route = context.route_lookup.payload as Route - context.response.print(route.handler.call(context)) + content = route.handler.call(context) + if Kemal.config.error_handlers.has_key?(context.response.status_code) + raise Kemal::Exceptions::CustomException.new(context) + end + context.response.print(content) context end diff --git a/src/kemal/view.cr b/src/kemal/view.cr index b8ceb6c..f4caad4 100644 --- a/src/kemal/view.cr +++ b/src/kemal/view.cr @@ -1,28 +1,3 @@ -# Template for 403 Forbidden -def render_403(context) - template = <<-HTML - - - - - - -

Forbidden

-

Kemal doesn't allow you to see this page.

- - - - HTML - context.response.content_type = "text/html" - context.response.status_code = 403 - context.response.print template - context -end - # Template for 404 Not Found def render_404(context) template = <<-HTML @@ -79,53 +54,3 @@ def render_500(context, backtrace, verbosity) context.response.print template context end - -# Template for 415 Unsupported media type -def render_415(context, message) - template = <<-HTML - - - - - - -

Unsupported media type

-

#{message}

- - - - HTML - context.response.content_type = "text/html" - context.response.status_code = 415 - context.response.print template - context -end - -# Template for 400 Bad request -def render_400(context, message) - template = <<-HTML - - - - - - -

Bad request

-

#{message}

- - - - HTML - context.response.content_type = "text/html" - context.response.status_code = 400 - context.response.print template - context -end