diff --git a/spec/context_spec.cr b/spec/context_spec.cr index ea1dbb6..1bfb9f7 100644 --- a/spec/context_spec.cr +++ b/spec/context_spec.cr @@ -64,7 +64,6 @@ describe "Context" do io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = app app.filter_handler.call(context) app.route_handler.call(context) context.store["key"].should eq "value" diff --git a/spec/dsl_helper.cr b/spec/dsl_helper.cr index 39a4e91..a636c4c 100644 --- a/spec/dsl_helper.cr +++ b/spec/dsl_helper.cr @@ -12,11 +12,11 @@ class CustomLogHandler < Kemal::BaseLogHandler end end -def create_request_and_return_io(handler, request) +def create_request_and_return_io(handler, request, app = Kemal.application) io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application + context.app = app handler.call(context) response.close io.rewind diff --git a/spec/exception_handler_spec.cr b/spec/exception_handler_spec.cr index 95df288..dc72b94 100644 --- a/spec/exception_handler_spec.cr +++ b/spec/exception_handler_spec.cr @@ -26,9 +26,8 @@ describe "Kemal::ExceptionHandler" do app.get "/" do |env| env.response.status_code = 403 end - context.app = app subject = Kemal::ExceptionHandler.new(app) - subject.next = Kemal::RouteHandler.new + subject.next = app.route_handler subject.call(context) response.close io.rewind @@ -50,9 +49,8 @@ describe "Kemal::ExceptionHandler" do app.get "/" do |env| env.response.status_code = 500 end - context.app = app subject = Kemal::ExceptionHandler.new(app) - subject.next = Kemal::RouteHandler.new + subject.next = app.route_handler subject.call(context) response.close io.rewind @@ -75,9 +73,8 @@ describe "Kemal::ExceptionHandler" do env.response.content_type = "application/json" env.response.status_code = 500 end - context.app = app subject = Kemal::ExceptionHandler.new(app) - subject.next = Kemal::RouteHandler.new + subject.next = app.route_handler subject.call(context) response.close io.rewind @@ -100,9 +97,8 @@ describe "Kemal::ExceptionHandler" do env.response.content_type = "application/json" env.response.status_code = 500 end - context.app = app - subject = Kemal::ExceptionHandler.new(Kemal::Base.new) - subject.next = Kemal::RouteHandler.new + subject = Kemal::ExceptionHandler.new(app) + subject.next = app.route_handler subject.call(context) response.close io.rewind diff --git a/spec/handler_spec.cr b/spec/handler_spec.cr index 1b48ccc..85e2001 100644 --- a/spec/handler_spec.cr +++ b/spec/handler_spec.cr @@ -69,8 +69,8 @@ end describe "Handler" do it "adds custom handler before before_*" do - filter_middleware = Kemal::FilterHandler.new - Kemal.application.add_filter_handler filter_middleware + app = Kemal::Base.new + filter_middleware = Kemal::FilterHandler.new(app) filter_middleware._add_route_filter("GET", "/", :before) do |env| env.response << " is" end @@ -78,7 +78,6 @@ describe "Handler" do filter_middleware._add_route_filter("GET", "/", :before) do |env| env.response << " so" end - app = Kemal::Base.new app.add_filter_handler filter_middleware app.add_handler CustomTestHandler.new diff --git a/spec/middleware/filters_spec.cr b/spec/middleware/filters_spec.cr index 65f1d48..6a5596a 100644 --- a/spec/middleware/filters_spec.cr +++ b/spec/middleware/filters_spec.cr @@ -5,7 +5,7 @@ describe "Kemal::FilterHandler" do test_filter = FilterTest.new test_filter.modified = "false" - filter_middleware = Kemal::FilterHandler.new + filter_middleware = Kemal::FilterHandler.new(Kemal.application) filter_middleware._add_route_filter("GET", "/greetings", :before) { test_filter.modified = "true" } kemal = Kemal.application.route_handler @@ -23,7 +23,7 @@ describe "Kemal::FilterHandler" do test_filter = FilterTest.new test_filter.modified = "false" - filter_middleware = Kemal::FilterHandler.new + filter_middleware = Kemal::FilterHandler.new(Kemal.application) filter_middleware._add_route_filter("GET", "/greetings", :before) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } kemal = Kemal.application.route_handler @@ -49,7 +49,7 @@ describe "Kemal::FilterHandler" do test_filter = FilterTest.new test_filter.modified = "false" - filter_middleware = Kemal::FilterHandler.new + filter_middleware = Kemal::FilterHandler.new(Kemal.application) filter_middleware._add_route_filter("ALL", "/greetings", :before) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } filter_middleware._add_route_filter("GET", "/greetings", :before) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } filter_middleware._add_route_filter("POST", "/greetings", :before) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } @@ -77,7 +77,7 @@ describe "Kemal::FilterHandler" do test_filter = FilterTest.new test_filter.modified = "false" - filter_middleware = Kemal::FilterHandler.new + filter_middleware = Kemal::FilterHandler.new(Kemal.application) filter_middleware._add_route_filter("GET", "/greetings", :after) { test_filter.modified = "true" } kemal = Kemal.application.route_handler @@ -95,7 +95,7 @@ describe "Kemal::FilterHandler" do test_filter = FilterTest.new test_filter.modified = "false" - filter_middleware = Kemal::FilterHandler.new + filter_middleware = Kemal::FilterHandler.new(Kemal.application) filter_middleware._add_route_filter("GET", "/greetings", :after) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } kemal = Kemal.application.route_handler @@ -121,7 +121,7 @@ describe "Kemal::FilterHandler" do test_filter = FilterTest.new test_filter.modified = "false" - filter_middleware = Kemal::FilterHandler.new + filter_middleware = Kemal::FilterHandler.new(Kemal.application) filter_middleware._add_route_filter("ALL", "/greetings", :after) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } filter_middleware._add_route_filter("GET", "/greetings", :after) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } filter_middleware._add_route_filter("POST", "/greetings", :after) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } @@ -152,7 +152,7 @@ describe "Kemal::FilterHandler" do test_filter_third = FilterTest.new test_filter_third.modified = "false" - filter_middleware = Kemal::FilterHandler.new + filter_middleware = Kemal::FilterHandler.new(Kemal.application) filter_middleware._add_route_filter("ALL", "/greetings", :before) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } filter_middleware._add_route_filter("ALL", "/greetings", :before) { test_filter_second.modified = test_filter_second.modified == "true" ? "false" : "true" } filter_middleware._add_route_filter("ALL", "/greetings", :before) { test_filter_third.modified = test_filter_third.modified == "true" ? "false" : "true" } diff --git a/spec/websocket_handler_spec.cr b/spec/websocket_handler_spec.cr index cbcb34e..e89dcc5 100644 --- a/spec/websocket_handler_spec.cr +++ b/spec/websocket_handler_spec.cr @@ -1,6 +1,6 @@ require "./spec_helper" -private def create_ws_request_and_return_io(handler, request, app) +private def create_ws_request_and_return_io(handler, request, app = Kemal.application) io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) @@ -16,8 +16,8 @@ end describe "Kemal::WebSocketHandler" do it "doesn't match on wrong route" do app = Kemal::Base.new - handler = Kemal::WebSocketHandler.new - handler.next = Kemal::RouteHandler.new + handler = app.websocket_handler + handler.next = app.route_handler app.ws "/" { } headers = HTTP::Headers{ "Upgrade" => "websocket", @@ -28,7 +28,6 @@ describe "Kemal::WebSocketHandler" do io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = app expect_raises(Kemal::Exceptions::RouteNotFound) do handler.call context @@ -37,7 +36,7 @@ describe "Kemal::WebSocketHandler" do it "matches on given route" do app = Kemal::Base.new - handler = Kemal::WebSocketHandler.new + handler = app.websocket_handler app.ws "/" { |socket, context| socket.send("Match") } app.ws "/no_match" { |socket, context| socket.send "No Match" } headers = HTTP::Headers{ @@ -54,7 +53,7 @@ describe "Kemal::WebSocketHandler" do it "fetches named url parameters" do app = Kemal::Base.new - handler = Kemal::WebSocketHandler.new + handler = app.websocket_handler app.ws "/:id" { |s, c| c.params.url["id"] } headers = HTTP::Headers{ "Upgrade" => "websocket", @@ -69,15 +68,14 @@ describe "Kemal::WebSocketHandler" do it "matches correct verb" do app = Kemal::Base.new - handler = Kemal::WebSocketHandler.new - handler.next = Kemal::RouteHandler.new + handler = app.websocket_handler + handler.next = app.route_handler app.ws "/" { } app.get "/" { "get" } request = HTTP::Request.new("GET", "/") io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = app handler.call(context) response.close io.rewind diff --git a/src/kemal/base.cr b/src/kemal/base.cr index db3b078..a4b205d 100644 --- a/src/kemal/base.cr +++ b/src/kemal/base.cr @@ -15,18 +15,19 @@ class Kemal::Base include Base::Builder # :nodoc: - getter route_handler = Kemal::RouteHandler.new + # TODO: These ivars are initialized in the constructor, but their values depend on `self`. + getter! route_handler : RouteHandler? # :nodoc: - getter filter_handler = Kemal::FilterHandler.new + getter! filter_handler : FilterHandler? # :nodoc: - getter websocket_handler = Kemal::WebSocketHandler.new + getter! websocket_handler : WebSocketHandler? getter handlers = [] of HTTP::Handler getter error_handlers = {} of Int32 => HTTP::Server::Context, Exception -> String getter config : Config - property! logger : Kemal::BaseLogHandler + property! logger : BaseLogHandler property! server : HTTP::Server property? running = false diff --git a/src/kemal/exception_handler.cr b/src/kemal/exception_handler.cr index 32da24b..98a8643 100644 --- a/src/kemal/exception_handler.cr +++ b/src/kemal/exception_handler.cr @@ -19,16 +19,16 @@ module Kemal call_exception_with_status_code(context, ex, context.response.status_code) rescue ex : Exception log("Exception: #{ex.inspect_with_backtrace}") - return call_exception_with_status_code(context, ex, 500) if context.app.error_handlers.has_key?(500) - verbosity = context.app.config.env == "production" ? false : true + return call_exception_with_status_code(context, ex, 500) if app.error_handlers.has_key?(500) + verbosity = app.config.env == "production" ? false : true return app.render_500(context, ex.inspect_with_backtrace, verbosity) end end private def call_exception_with_status_code(context, exception, status_code) - if context.app.error_handlers.has_key?(status_code) + if !app.error_handlers.empty? && app.error_handlers.has_key?(status_code) context.response.content_type = "text/html" unless context.response.headers.has_key?("Content-Type") - context.response.print context.app.error_handlers[status_code].call(context, exception) + context.response.print app.error_handlers[status_code].call(context, exception) context.response.status_code = status_code context end diff --git a/src/kemal/filter_handler.cr b/src/kemal/filter_handler.cr index a135744..f6c5894 100644 --- a/src/kemal/filter_handler.cr +++ b/src/kemal/filter_handler.cr @@ -3,17 +3,19 @@ module Kemal class FilterHandler include HTTP::Handler + getter app : Kemal::Base + # This middleware is lazily instantiated and added to the handlers as soon as a call to `after_X` or `before_X` is made. - def initialize + def initialize(@app) @tree = Radix::Tree(Array(FilterBlock)).new end - # The call order of the filters is `before_all -> before_x -> X -> after_x -> after_all`. + # The call order of the filters is before_all -> before_x -> X -> after_x -> after_all def call(context : HTTP::Server::Context) - return call_next(context) unless context.route_found? + return call_next(context) unless app.route_handler.route_defined?(context.request) call_block_for_path_type("ALL", context.request.path, :before, context) call_block_for_path_type(context.request.override_method, context.request.path, :before, context) - if context.app.error_handlers.has_key?(context.response.status_code) + if !app.error_handlers.empty? && app.error_handlers.has_key?(context.response.status_code) raise Kemal::Exceptions::CustomException.new(context) end call_next(context) diff --git a/src/kemal/helpers/file_helpers.cr b/src/kemal/helpers/file_helpers.cr index 375439c..b0a5636 100644 --- a/src/kemal/helpers/file_helpers.cr +++ b/src/kemal/helpers/file_helpers.cr @@ -49,7 +49,7 @@ module Kemal::FileHelpers end def send_file(env, path : String, mime_type : String? = nil) - send_file(env, path, env.app.config, mime_type) + send_file(env, path, config, mime_type) end private def multipart(file, env : HTTP::Server::Context) diff --git a/src/kemal/route_handler.cr b/src/kemal/route_handler.cr index fd2f672..ca7f3c8 100644 --- a/src/kemal/route_handler.cr +++ b/src/kemal/route_handler.cr @@ -8,7 +8,9 @@ module Kemal CACHED_ROUTES_LIMIT = 1024 property routes, cached_routes - def initialize + getter app : Kemal::Base + + def initialize(@app) @routes = Radix::Tree(Route).new @cached_routes = Hash(String, Radix::Result(Route)).new end @@ -42,12 +44,20 @@ module Kemal route end + def lookup_route(request) + lookup_route request.override_method.as(String), request.path + end + + def route_defined?(request) + lookup_route(request).found? + end + # Processes the route if it's a match. Otherwise renders 404. private def process_request(context) raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_found? content = context.route.handler.call(context) - if context.app.error_handlers.has_key?(context.response.status_code) + if !app.error_handlers.empty? && app.error_handlers.has_key?(context.response.status_code) raise Kemal::Exceptions::CustomException.new(context) end diff --git a/src/kemal/websocket_handler.cr b/src/kemal/websocket_handler.cr index da81b2f..a2515d3 100644 --- a/src/kemal/websocket_handler.cr +++ b/src/kemal/websocket_handler.cr @@ -4,13 +4,17 @@ module Kemal property routes - def initialize + getter app : Kemal::Base + + def initialize(@app) @routes = Radix::Tree(WebSocket).new end def call(context : HTTP::Server::Context) - return call_next(context) unless context.ws_route_found? && websocket_upgrade_request?(context) - content = context.websocket.call(context) + route = lookup_ws_route(context.request.path) + return call_next(context) unless route.found? && websocket_upgrade_request?(context) + context.request.url_params ||= route.params + content = route.payload.call(context) context.response.print(content) context end