diff --git a/spec/context_spec.cr b/spec/context_spec.cr index b2dd5ea..63ba9ab 100644 --- a/spec/context_spec.cr +++ b/spec/context_spec.cr @@ -2,7 +2,7 @@ require "./spec_helper" describe "Context" do it "has a default content type" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do |env| "Hello" end @@ -13,7 +13,7 @@ describe "Context" do end it "sets content type" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do |env| env.response.content_type = "application/json" "Hello" @@ -25,7 +25,7 @@ describe "Context" do end it "parses headers" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do |env| name = env.request.headers["name"] "Hello #{name}" @@ -39,7 +39,7 @@ describe "Context" do end it "sets response headers" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do |env| env.response.headers.add "Accept-Language", "tr" end diff --git a/spec/middleware/filters_spec.cr b/spec/middleware/filters_spec.cr index f0f2264..54dbc4e 100644 --- a/spec/middleware/filters_spec.cr +++ b/spec/middleware/filters_spec.cr @@ -5,19 +5,145 @@ describe "Kemal::Middleware::Filters" do test_filter = FilterTest.new test_filter.modified = "false" - filter = Kemal::Middleware::Filter.new - filter.add :before, "/greetings", {} of Symbol => String { test_filter.modified = "true" } + filter_middleware = Kemal::Middleware::Filter.new + filter_middleware._add_route_filter("GET", "/greetings", :before) { test_filter.modified = "true" } - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/greetings" { test_filter.modified } test_filter.modified.should eq("false") request = HTTP::Request.new("GET", "/greetings") - create_request_and_return_io(filter, request) + create_request_and_return_io(filter_middleware, request) io_with_context = create_request_and_return_io(kemal, request) client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) client_response.body.should eq("true") end + + it "executes code before GET home request but not POST home request" do + test_filter = FilterTest.new + test_filter.modified = "false" + + filter_middleware = Kemal::Middleware::Filter.new + filter_middleware._add_route_filter("GET", "/greetings", :before) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } + + kemal = Kemal::RouteHandler::INSTANCE + kemal.add_route "GET", "/greetings" { test_filter.modified } + kemal.add_route "POST", "/greetings" { test_filter.modified } + + test_filter.modified.should eq("false") + + request = HTTP::Request.new("GET", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("true") + + request = HTTP::Request.new("POST", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("true") + end + + it "executes code before all GET/POST home request" do + test_filter = FilterTest.new + test_filter.modified = "false" + + filter_middleware = Kemal::Middleware::Filter.new + 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" } + + kemal = Kemal::RouteHandler::INSTANCE + kemal.add_route "GET", "/greetings" { test_filter.modified } + kemal.add_route "POST", "/greetings" { test_filter.modified } + + test_filter.modified.should eq("false") + + request = HTTP::Request.new("GET", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("false") + + request = HTTP::Request.new("POST", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("false") + end + + it "executes code after home request" do + test_filter = FilterTest.new + test_filter.modified = "false" + + filter_middleware = Kemal::Middleware::Filter.new + filter_middleware._add_route_filter("GET", "/greetings", :after) { test_filter.modified = "true" } + + kemal = Kemal::RouteHandler::INSTANCE + kemal.add_route "GET", "/greetings" { test_filter.modified } + + test_filter.modified.should eq("false") + request = HTTP::Request.new("GET", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("true") + end + + it "executes code after GET home request but not POST home request" do + test_filter = FilterTest.new + test_filter.modified = "false" + + filter_middleware = Kemal::Middleware::Filter.new + filter_middleware._add_route_filter("GET", "/greetings", :after) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } + + kemal = Kemal::RouteHandler::INSTANCE + kemal.add_route "GET", "/greetings" { test_filter.modified } + kemal.add_route "POST", "/greetings" { test_filter.modified } + + test_filter.modified.should eq("false") + + request = HTTP::Request.new("GET", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("true") + + request = HTTP::Request.new("POST", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("true") + end + + it "executes code after all GET/POST home request" do + test_filter = FilterTest.new + test_filter.modified = "false" + + filter_middleware = Kemal::Middleware::Filter.new + 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" } + + kemal = Kemal::RouteHandler::INSTANCE + kemal.add_route "GET", "/greetings" { test_filter.modified } + kemal.add_route "POST", "/greetings" { test_filter.modified } + + test_filter.modified.should eq("false") + request = HTTP::Request.new("GET", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("false") + + request = HTTP::Request.new("POST", "/greetings") + create_request_and_return_io(filter_middleware, request) + io_with_context = create_request_and_return_io(kemal, request) + client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) + client_response.body.should eq("false") + end + end class FilterTest diff --git a/spec/param_parser_spec.cr b/spec/param_parser_spec.cr index e7c833f..f2b4f0e 100644 --- a/spec/param_parser_spec.cr +++ b/spec/param_parser_spec.cr @@ -7,7 +7,7 @@ describe "ParamParser" do "Hello #{hasan}" end request = HTTP::Request.new("POST", "/?hasan=cemal") - params = Kemal::ParamParser.new(route, request).parse + params = Kemal::ParamParser.new(request).parse params["hasan"].should eq "cemal" end @@ -26,7 +26,7 @@ describe "ParamParser" do headers: HTTP::Headers{"Content-Type": "application/x-www-form-urlencoded"}, ) - params = Kemal::ParamParser.new(route, request).parse + params = Kemal::ParamParser.new(request).parse params.should eq({"hasan" => "cemal", "name" => "serdar", "age" => "99"}) end @@ -41,7 +41,7 @@ describe "ParamParser" do headers: HTTP::Headers{"Content-Type": "application/json"}, ) - params = Kemal::ParamParser.new(route, request).parse + params = Kemal::ParamParser.new(request).parse params.should eq({"name": "Serdar"}) end @@ -55,7 +55,7 @@ describe "ParamParser" do headers: HTTP::Headers{"Content-Type": "application/json"}, ) - params = Kemal::ParamParser.new(route, request).parse + params = Kemal::ParamParser.new(request).parse params.should eq({"_json": [1]}) end @@ -69,7 +69,7 @@ describe "ParamParser" do headers: HTTP::Headers{"Content-Type": "application/json"}, ) - params = Kemal::ParamParser.new(route, request).parse + params = Kemal::ParamParser.new(request).parse params.should eq({"foo": "bar", "_json": [1]}) end @@ -82,7 +82,7 @@ describe "ParamParser" do headers: HTTP::Headers{"Content-Type": "application/json"}, ) - params = Kemal::ParamParser.new(route, request).parse + params = Kemal::ParamParser.new(request).parse params.should eq({} of String => AllParamTypes) end end @@ -103,7 +103,7 @@ describe "ParamParser" do headers: HTTP::Headers{"Content-Type": "text/plain"}, ) - params = Kemal::ParamParser.new(route, request).parse + params = Kemal::ParamParser.new(request).parse params.should eq({"hasan" => "cemal"}) end end diff --git a/spec/route_handler_spec.cr b/spec/route_handler_spec.cr index 4afb393..2df06c5 100644 --- a/spec/route_handler_spec.cr +++ b/spec/route_handler_spec.cr @@ -2,7 +2,7 @@ require "./spec_helper" describe "Kemal::RouteHandler" do it "routes" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do "hello" end @@ -13,7 +13,7 @@ describe "Kemal::RouteHandler" do end it "routes request with query string" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do |env| "hello #{env.params["message"]}" end @@ -24,7 +24,7 @@ describe "Kemal::RouteHandler" do end it "routes request with multiple query strings" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do |env| "hello #{env.params["message"]} time #{env.params["time"]}" end @@ -35,7 +35,7 @@ describe "Kemal::RouteHandler" do end it "route parameter has more precedence than query string arguments" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/:message" do |env| "hello #{env.params["message"]}" end @@ -46,7 +46,7 @@ describe "Kemal::RouteHandler" do end it "parses simple JSON body" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "POST", "/" do |env| name = env.params["name"] age = env.params["age"] @@ -66,7 +66,7 @@ describe "Kemal::RouteHandler" do end it "parses JSON with string array" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "POST", "/" do |env| skills = env.params["skills"] as Array "Skills #{skills.each.join(',')}" @@ -85,7 +85,7 @@ describe "Kemal::RouteHandler" do end it "parses JSON with json object array" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "POST", "/" do |env| skills = env.params["skills"] as Array skills_from_languages = skills.map do |skill| @@ -110,7 +110,7 @@ describe "Kemal::RouteHandler" do # Removed until there is a way to test multiple middlewares # it "renders 404 on not found" do - # kemal = Kemal::RouteHandler.new + # kemal = Kemal::RouteHandler::INSTANCE # request = HTTP::Request.new("GET", "/?message=world") # io_with_context = create_request_and_return_io(kemal, request) # client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) @@ -118,7 +118,7 @@ describe "Kemal::RouteHandler" do # end # it "renders 500 on exception" do - # kemal = Kemal::RouteHandler.new + # kemal = Kemal::RouteHandler::INSTANCE # kemal.add_route "GET", "/" do # raise "Exception" # end @@ -130,7 +130,7 @@ describe "Kemal::RouteHandler" do # end # it "checks for _method param in POST request to simulate PUT" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "PUT", "/" do |env| "Hello World from PUT" end @@ -146,7 +146,7 @@ describe "Kemal::RouteHandler" do end it "checks for _method param in POST request to simulate PATCH" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "PATCH", "/" do |env| "Hello World from PATCH" end @@ -162,7 +162,7 @@ describe "Kemal::RouteHandler" do end it "checks for _method param in POST request to simulate DELETE" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "DELETE", "/" do |env| "Hello World from DELETE" end @@ -179,7 +179,7 @@ describe "Kemal::RouteHandler" do end it "can process HTTP HEAD requests for defined GET routes" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do |env| "Hello World from GET" end @@ -191,7 +191,7 @@ describe "Kemal::RouteHandler" do # Removed until there is a way to test multiple middlewares # it "can't process HTTP HEAD requests for undefined GET routes" do - # kemal = Kemal::RouteHandler.new + # kemal = Kemal::RouteHandler::INSTANCE # request = HTTP::Request.new("HEAD", "/") # io_with_context = create_request_and_return_io(kemal, request) # client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) @@ -199,7 +199,7 @@ describe "Kemal::RouteHandler" do # end it "redirects user to provided url" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/" do |env| env.redirect "/login" end diff --git a/spec/route_spec.cr b/spec/route_spec.cr index fc83cf8..95c6b26 100644 --- a/spec/route_spec.cr +++ b/spec/route_spec.cr @@ -3,7 +3,7 @@ require "./spec_helper" describe "Route" do describe "match?" do it "matches the correct route" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/route1" do |env| "Route 1" end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 08a6d3d..be05359 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -40,4 +40,5 @@ Spec.before_each do config.env = "development" config.setup_logging config.handlers.clear + Kemal::RouteHandler::INSTANCE.tree = Radix::Tree.new end diff --git a/spec/view_spec.cr b/spec/view_spec.cr index 8dc38a8..42c49af 100644 --- a/spec/view_spec.cr +++ b/spec/view_spec.cr @@ -6,7 +6,7 @@ end describe "Views" do it "renders file" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/view/:name" do |env| name = env.params["name"] render "spec/asset/hello.ecr" @@ -18,7 +18,7 @@ describe "Views" do end it "renders file with dynamic variables" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/view/:name" do |env| name = env.params["name"] render_with_base_and_layout "hello.ecr" @@ -30,7 +30,7 @@ describe "Views" do end it "renders layout" do - kemal = Kemal::RouteHandler.new + kemal = Kemal::RouteHandler::INSTANCE kemal.add_route "GET", "/view/:name" do |env| name = env.params["name"] render "spec/asset/hello.ecr", "spec/asset/layout.ecr" diff --git a/src/kemal/context.cr b/src/kemal/context.cr index 639ecdb..b0046e2 100644 --- a/src/kemal/context.cr +++ b/src/kemal/context.cr @@ -3,12 +3,21 @@ class HTTP::Server class Context def params - @params ||= Kemal::ParamParser.new(@route, @request).parse + @params ||= Kemal::ParamParser.new(@request).parse end def redirect(url, status_code = 302) @response.headers.add "Location", url @response.status_code = status_code end + + def route_lookup + @route_lookup ||= Kemal::RouteHandler::INSTANCE.lookup_route(@request.override_method as String, @request.path) + end + + def route_defined? + route_lookup.found? + end + end end diff --git a/src/kemal/middleware/filters.cr b/src/kemal/middleware/filters.cr index 9a49ec6..336f19e 100644 --- a/src/kemal/middleware/filters.cr +++ b/src/kemal/middleware/filters.cr @@ -2,47 +2,72 @@ module Kemal::Middleware # Kemal::Filter handle all code that should be evaluated before and after # every request class Filter < HTTP::Handler + INSTANCE = new + + # 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 @tree = Radix::Tree.new + Kemal.config.add_handler(self) end - def add(type, path, options, &block : HTTP::Server::Context -> _) - node = radix_path type, path - @tree.add node, Block.new &block - end - + # The call order of the filters is before_all -> before_x -> X -> after_x -> after_all def call(context) - process_filter(context, :before) + return call_next(context) unless context.route_defined? + 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) call_next(context) - process_filter(context, :after) + call_block_for_path_type(context.request.override_method, context.request.path, :after, context) + call_block_for_path_type("ALL", context.request.path, :after, context) + context end - def filter_for_path_type_defined?(path, type) - lookup = @tree.find radix_path(type, path) + # This checks is filter is already defined for the verb/path/type combination + def filter_for_path_type_defined?(verb, path, type) + lookup = @tree.find radix_path(verb, path, type) lookup.found? && lookup.payload.is_a? Block end - private def process_filter(context, type) - lookup = @tree.find radix_path(type, context.request.path) + # :nodoc: This shouldn't be called directly, it's not private because I need to call it for testing purpose since I can't call the macros in the spec. + # It adds the block for the corresponding verb/path/type combination to the tree. + def _add_route_filter(verb, path, type, &block : HTTP::Server::Context -> _) + node = radix_path(verb, path, type) + @tree.add node, Block.new &block + end + + # This can be called directly but it's simpler to just use the macros, it will check if another filter is not already defined for this verb/path/type and proceed to call `add_route_filter` + def before(verb, path = "*", &block : HTTP::Server::Context -> _) + raise Kemal::Middleware::Filter::BeforeFilterAlreadyDefinedException.new(verb, path) if filter_for_path_type_defined?(verb, path, :before) + _add_route_filter verb, path, :before, &block + end + + # This can be called directly but it's simpler to just use the macros, it will check if another filter is not already defined for this verb/path/type and proceed to call `add_route_filter` + def after(verb, path = "*", &block : HTTP::Server::Context -> _) + raise Kemal::Middleware::Filter::AfterFilterAlreadyDefinedException.new(verb, path) if filter_for_path_type_defined?(verb, path, :after) + _add_route_filter verb, path, :after, &block + end + + # This will fetch the block for the verb/path/type from the tree and call it. + private def call_block_for_path_type(verb, path, type, context) + lookup = @tree.find radix_path(verb, path, type) if lookup.found? && lookup.payload.is_a? Block block = lookup.payload as Block block.block.call(context) end end - private def radix_path(type : Symbol, path) - "/#{type}#{path}" + private def radix_path(verb, path, type : Symbol) + "#{type}/#{verb}/#{path}" end class BeforeFilterAlreadyDefinedException < Exception - def initialize(path) - super "A before-filter is already defined for path: '#{path}'." + def initialize(verb, path) + super "A before-filter is already defined for path: '#{verb}:#{path}'." end end class AfterFilterAlreadyDefinedException < Exception - def initialize(path) - super "An after-filter is already defined for path: '#{path}'." + def initialize(verb, path) + super "An after-filter is already defined for path: '#{verb}:#{path}'." end end end @@ -55,22 +80,15 @@ module Kemal::Middleware end end -def add_filters - unless filter = Kemal.config.handlers.any? { |handler| handler.is_a? Kemal::Middleware::Filter } - filter = Kemal::Middleware::Filter.new - Kemal.config.add_handler filter - end - filter -end +# All the helper methods available are: +# - before_all, before_get, before_post, before_put, before_patch, before_delete +# - after_all, after_get, after_post, after_put, after_patch, after_delete -def before(path = "*", options = {} of Symbol => String, &block : HTTP::Server::Context -> _) - filter = (Kemal.config.handlers.find { |handler| handler.is_a? Kemal::Middleware::Filter } || add_filters) as Kemal::Middleware::Filter - raise Kemal::Middleware::Filter::BeforeFilterAlreadyDefinedException.new(path) if filter.filter_for_path_type_defined?(path, :before) - filter.add :before, path, options, &block -end - -def after(path = "*", options = {} of Symbol => String, &block : HTTP::Server::Context -> _) - filter = (Kemal.config.handlers.find { |handler| handler.is_a? Kemal::Middleware::Filter } || add_filters) as Kemal::Middleware::Filter - raise Kemal::Middleware::Filter::AfterFilterAlreadyDefinedException.new(path) if filter.filter_for_path_type_defined?(path, :after) - filter.add :after, path, options, &block -end +ALL_METHODS = %w(get post put patch delete all) +{% for type in ["before", "after"]%} + {% for method in ALL_METHODS %} + def {{type.id}}_{{method.id}}(path = "*", &block : HTTP::Server::Context -> _) + Kemal::Middleware::Filter::INSTANCE.{{type.id}}({{method}}.upcase, path, &block) + end + {% end %} +{% end %} diff --git a/src/kemal/param_parser.cr b/src/kemal/param_parser.cr index 0af7f7f..e6ee8dd 100644 --- a/src/kemal/param_parser.cr +++ b/src/kemal/param_parser.cr @@ -9,7 +9,7 @@ class Kemal::ParamParser URL_ENCODED_FORM = "application/x-www-form-urlencoded" APPLICATION_JSON = "application/json" - def initialize(@route, @request) + def initialize(@request) @params = {} of String => AllParamTypes end diff --git a/src/kemal/request.cr b/src/kemal/request.cr index 16ff382..930da45 100644 --- a/src/kemal/request.cr +++ b/src/kemal/request.cr @@ -2,4 +2,28 @@ class HTTP::Request property override_method property url_params + + def override_method + @override_method ||= check_for_method_override! + end + + # Checks if request params contain _method param to override request incoming method + private def check_for_method_override! + @override_method = @method + if @method == "POST" + params = Kemal::ParamParser.new(self).parse_request + if params.has_key?("_method") && HTTP::Request.override_method_valid?(params["_method"]) + @override_method = params["_method"] + end + end + @override_method + end + + # Checks if method contained in _method param is valid one + def self.override_method_valid?(override_method) + return false unless override_method.is_a?(String) + override_method = override_method.upcase + override_method == "PUT" || override_method == "PATCH" || override_method == "DELETE" + end + end diff --git a/src/kemal/route.cr b/src/kemal/route.cr index 064a938..c6a46b4 100644 --- a/src/kemal/route.cr +++ b/src/kemal/route.cr @@ -8,21 +8,5 @@ class Kemal::Route def initialize(@method, @path, &@handler : HTTP::Server::Context -> _) end - # Checks if request params contain _method param to override request incoming method - def self.check_for_method_override!(request) - request.override_method = request.method - if request.method == "POST" - params = Kemal::ParamParser.new(self, request).parse_request - if params.has_key?("_method") && self.override_method_valid?(params["_method"]) - request.override_method = params["_method"] - end - end - end - # Checks if method contained in _method param is valid one - def self.override_method_valid?(override_method) - return false unless override_method.is_a?(String) - override_method = override_method.upcase - return (override_method == "PUT" || override_method == "PATCH" || override_method == "DELETE") - end end diff --git a/src/kemal/route_handler.cr b/src/kemal/route_handler.cr index 74fa6ea..96128f6 100644 --- a/src/kemal/route_handler.cr +++ b/src/kemal/route_handler.cr @@ -6,6 +6,8 @@ require "radix" class Kemal::RouteHandler < HTTP::Handler INSTANCE = new + property tree + def initialize @tree = Radix::Tree.new end @@ -22,13 +24,16 @@ class Kemal::RouteHandler < HTTP::Handler add_to_radix_tree("HEAD", path, Route.new("HEAD", path, &handler)) if method == "GET" end + # Check if a route is defined and returns the lookup + def lookup_route(verb, path) + @tree.find radix_path(verb, path) + end + # Processes the route if it's a match. Otherwise renders 404. def process_request(context) - Kemal::Route.check_for_method_override!(context.request) - lookup = @tree.find radix_path(context.request.override_method as String, context.request.path) - raise Kemal::Exceptions::RouteNotFound.new(context) unless lookup.found? - route = lookup.payload as Route - context.request.url_params = lookup.params + raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_defined? + route = context.route_lookup.payload as Route + context.request.url_params = context.route_lookup.params context.response.print(route.handler.call(context).to_s) context end