diff --git a/spec/config_spec.cr b/spec/config_spec.cr index 9ead52f..f6db81f 100644 --- a/spec/config_spec.cr +++ b/spec/config_spec.cr @@ -38,7 +38,7 @@ describe "Config" do application = Kemal::Application.new application.add_handler CustomTestHandler.new application.setup - application.handlers.size.should eq 9 + application.handlers.size.should eq 8 end it "toggles the shutdown message" do diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr index 21272ed..a7290d6 100644 --- a/spec/helpers_spec.cr +++ b/spec/helpers_spec.cr @@ -12,7 +12,7 @@ describe "Macros" do it "adds a custom handler" do add_handler CustomTestHandler.new Kemal.application.setup - Kemal.application.handlers.size.should eq 7 + Kemal.application.handlers.size.should eq 8 end end diff --git a/src/kemal/application.cr b/src/kemal/application.cr index e7fa19c..c5d1c4c 100644 --- a/src/kemal/application.cr +++ b/src/kemal/application.cr @@ -1,7 +1,6 @@ class Kemal::Application < Kemal::Base def initialize(config = Config.default) - super config - add_filter_handler(filter_handler) + super(config) end # Overload of self.run with the default startup logging diff --git a/src/kemal/base.cr b/src/kemal/base.cr index 09af652..f33e1bc 100644 --- a/src/kemal/base.cr +++ b/src/kemal/base.cr @@ -1,4 +1,5 @@ require "./helpers/*" +require "./base/*" # Kemal Base # The DSL currently consists of @@ -10,19 +11,18 @@ class Kemal::Base include FileHelpers include Templates include Macros + include Base::DSL + include Base::Builder - HTTP_METHODS = %w(get post put patch delete options) - FILTER_METHODS = %w(get post put patch delete options all) - + # :nodoc: getter route_handler = Kemal::RouteHandler.new + # :nodoc: getter filter_handler = Kemal::FilterHandler.new + # :nodoc: getter websocket_handler = Kemal::WebSocketHandler.new getter handlers = [] of HTTP::Handler - getter custom_handlers = [] of Tuple(Nil | Int32, HTTP::Handler) - getter filter_handlers = [] of HTTP::Handler getter error_handlers = {} of Int32 => HTTP::Server::Context, Exception -> String - @handler_position = 0 getter config : Config @@ -31,133 +31,11 @@ class Kemal::Base property? running = false def initialize(@config = Config.base) - @logger = if @config.logging? - Kemal::LogHandler.new - else - Kemal::NullLogHandler.new - end - add_filter_handler(filter_handler) - end + @filter_handler = FilterHandler.new(self) + @route_handler = RouteHandler.new(self) + @websocket_handler = WebSocketHandler.new(self) - {% for method in HTTP_METHODS %} - def {{method.id}}(path, &block : HTTP::Server::Context -> _) - raise Kemal::Exceptions::InvalidPathStartException.new({{method}}, path) unless Kemal::Utils.path_starts_with_slash?(path) - route_handler.add_route({{method}}.upcase, path, &block) - end - {% end %} - - def ws(path, &block : HTTP::WebSocket, HTTP::Server::Context -> Void) - raise Kemal::Exceptions::InvalidPathStartException.new("ws", path) unless Kemal::Utils.path_starts_with_slash?(path) - websocket_handler.add_route path, &block - end - - def error(status_code, &block : HTTP::Server::Context, Exception -> _) - add_error_handler status_code, &block - end - - # All the helper methods available are: - # - before_all, before_get, before_post, before_put, before_patch, before_delete, before_options - # - after_all, after_get, after_post, after_put, after_patch, after_delete, after_options - {% for type in ["before", "after"] %} - {% for method in FILTER_METHODS %} - def {{type.id}}_{{method.id}}(path = "*", &block : HTTP::Server::Context -> _) - filter_handler.{{type.id}}({{method}}.upcase, path, &block) - end - {% end %} - {% end %} - - def clear - @router_included = false - @handler_position = 0 - @default_handlers_setup = false - - handlers.clear - custom_handlers.clear - filter_handlers.clear - error_handlers.clear - - route_handler.clear - websocket_handler.clear - end - - def handlers=(handlers : Array(HTTP::Handler)) - clear - @handlers.replace(handlers) - end - - def add_handler(handler : HTTP::Handler) - @custom_handlers << {nil, handler} - end - - def add_handler(handler : HTTP::Handler, position : Int32) - @custom_handlers << {position, handler} - end - - def add_filter_handler(handler : HTTP::Handler) - @filter_handlers << handler - end - - def add_error_handler(status_code, &handler : HTTP::Server::Context, Exception -> _) - @error_handlers[status_code] = ->(context : HTTP::Server::Context, error : Exception) { handler.call(context, error).to_s } - end - - def setup - unless @default_handlers_setup && @router_included - setup_init_handler - setup_log_handler - setup_error_handler - setup_static_file_handler - setup_custom_handlers - setup_filter_handlers - @default_handlers_setup = true - @router_included = true - handlers.insert(handlers.size, websocket_handler) - handlers.insert(handlers.size, route_handler) - end - end - - private def setup_init_handler - @handlers.insert(@handler_position, Kemal::InitHandler.new(self)) - @handler_position += 1 - end - - private def setup_log_handler - @handlers.insert(@handler_position, logger) - @handler_position += 1 - end - - private def setup_error_handler - if @config.always_rescue? - @error_handler ||= Kemal::ExceptionHandler.new - @handlers.insert(@handler_position, @error_handler.not_nil!) - @handler_position += 1 - end - end - - private def setup_static_file_handler - if @config.serve_static.is_a?(Hash) - @handlers.insert(@handler_position, Kemal::StaticFileHandler.new(@config.public_folder)) - @handler_position += 1 - end - end - - private def setup_custom_handlers - @custom_handlers.each do |ch| - position = ch[0] - if !position - @handlers.insert(@handler_position, ch[1]) - @handler_position += 1 - else - @handlers.insert(position, ch[1]) - @handler_position += 1 - end - end - end - - private def setup_filter_handlers - @filter_handlers.each do |h| - @handlers.insert(@handler_position, h) - end + initialize_defaults end # Overload of self.run with the default startup logging @@ -180,6 +58,11 @@ class Kemal::Base end end + # DEPRECATED: This method should be replaced with `#running?` + def running + running? + end + private def prepare_for_server_start unless @config.env == "test" Signal::INT.trap do diff --git a/src/kemal/base/builder.cr b/src/kemal/base/builder.cr new file mode 100644 index 0000000..f330234 --- /dev/null +++ b/src/kemal/base/builder.cr @@ -0,0 +1,108 @@ +class Kemal::Base + module Builder + getter custom_handlers = [] of Tuple(Nil | Int32, HTTP::Handler) + getter filter_handlers = [] of HTTP::Handler + @handler_position = 0 + + def clear + @router_included = false + @handler_position = 0 + @default_handlers_setup = false + + handlers.clear + custom_handlers.clear + filter_handlers.clear + error_handlers.clear + + route_handler.clear + websocket_handler.clear + end + + def handlers=(handlers : Array(HTTP::Handler)) + clear + @handlers.replace(handlers) + end + + def add_handler(handler : HTTP::Handler) + @custom_handlers << {nil, handler} + end + + def add_handler(handler : HTTP::Handler, position : Int32) + @custom_handlers << {position, handler} + end + + def add_filter_handler(handler : HTTP::Handler) + @filter_handlers << handler + end + + def add_error_handler(status_code, &handler : HTTP::Server::Context, Exception -> _) + @error_handlers[status_code] = ->(context : HTTP::Server::Context, error : Exception) { handler.call(context, error).to_s } + end + + def setup + @logger = if @config.logging? + LogHandler.new + else + NullLogHandler.new + end + unless @default_handlers_setup && @router_included + setup_init_handler + setup_log_handler + setup_error_handler + setup_static_file_handler + setup_custom_handlers + setup_filter_handlers + @default_handlers_setup = true + @router_included = true + handlers.insert(handlers.size, websocket_handler) + handlers.insert(handlers.size, route_handler) + end + end + + private def setup_init_handler + @handlers.insert(@handler_position, Kemal::InitHandler.new(self)) + @handler_position += 1 + end + + private def setup_log_handler + @handlers.insert(@handler_position, logger) + @handler_position += 1 + end + + private def setup_error_handler + if @config.always_rescue? + error_handler = @error_handler ||= Kemal::ExceptionHandler.new + @handlers.insert(@handler_position, error_handler) + @handler_position += 1 + end + end + + private def setup_static_file_handler + if @config.serve_static.is_a?(Hash) + @handlers.insert(@handler_position, Kemal::StaticFileHandler.new(@config.public_folder)) + @handler_position += 1 + end + end + + # Handle WebSocketHandler + private def setup_custom_handlers + @custom_handlers.each do |ch| + position = ch[0] + if !position + @handlers.insert(@handler_position, ch[1]) + @handler_position += 1 + else + @handlers.insert(position, ch[1]) + @handler_position += 1 + end + end + end + + private def setup_filter_handlers + @handlers.insert(@handler_position, filter_handler) + @filter_handlers.each do |h| + @handlers.insert(@handler_position, h) + end + end + end +end diff --git a/src/kemal/base/dsl.cr b/src/kemal/base/dsl.cr new file mode 100644 index 0000000..a7473ac --- /dev/null +++ b/src/kemal/base/dsl.cr @@ -0,0 +1,91 @@ +class Kemal::Base + module DSL + HTTP_METHODS = %w(get post put patch delete options) + FILTER_METHODS = %w(get post put patch delete options all) + + macro included + # :nodoc: + DEFAULT_HANDLERS = [] of {String, String, (HTTP::Server::Context -> Nil)} + # :nodoc: + WEBSOCKET_HANDLERS = [] of {String, (HTTP::WebSocket, HTTP::Server::Context -> Void)} + # :nodoc: + DEFAULT_ERROR_HANDLERS = [] of {Int32, (HTTP::Server::Context, Exception -> Nil)} + # :nodoc: + DEFAULT_FILTERS = [] of {Symbol, String, String, (HTTP::Server::Context -> Nil)} + end + + {% for method in HTTP_METHODS %} + def {{method.id}}(path, &block : HTTP::Server::Context -> _) + raise Kemal::Exceptions::InvalidPathStartException.new({{method}}, path) unless Kemal::Utils.path_starts_with_slash?(path) + route_handler.add_route({{method}}.upcase, path, &block) + end + {% end %} + + def ws(path, &block : HTTP::WebSocket, HTTP::Server::Context -> Void) + raise Kemal::Exceptions::InvalidPathStartException.new("ws", path) unless Kemal::Utils.path_starts_with_slash?(path) + websocket_handler.add_route path, &block + end + + def error(status_code, &block : HTTP::Server::Context, Exception -> _) + add_error_handler status_code, &block + end + + # All the helper methods available are: + # - before_all, before_get, before_post, before_put, before_patch, before_delete, before_options + # - after_all, after_get, after_post, after_put, after_patch, after_delete, after_options + {% for type in ["before", "after"] %} + {% for method in FILTER_METHODS %} + def {{type.id}}_{{method.id}}(path = "*", &block : HTTP::Server::Context -> _) + filter_handler.{{type.id}}({{method}}.upcase, path, &block) + end + {% end %} + {% end %} + + private def initialize_defaults + DEFAULT_HANDLERS.each do |method, path, block| + route_handler.add_route(method.upcase, path, &block) + end + + WEBSOCKET_HANDLERS.each do |path, block| + ws(path, &block) + end + + DEFAULT_ERROR_HANDLERS.each do |status_code, block| + add_error_handler status_code, &block + end + + DEFAULT_FILTERS.each do |type, method, path, block| + if type == :before + filter_handler.before(method, path, &block) + else + filter_handler.after(method, path, &block) + end + end + end + + {% for method in HTTP_METHODS %} + def self.{{method.id}}(path, &block : HTTP::Server::Context -> _) + DEFAULT_HANDLERS << { {{method}}, path, block } + end + {% end %} + + def self.ws(path, &block : HTTP::WebSocket, HTTP::Server::Context -> Void) + WEBSOCKET_HANDLERS << {path, block} + end + + def self.error(status_code, &block : HTTP::Server::Context, Exception -> _) + DEFAULT_ERROR_HANDLERS << {status_code, block} + end + + # All the helper methods available are: + # - before_all, before_get, before_post, before_put, before_patch, before_delete, before_options + # - after_all, after_get, after_post, after_put, after_patch, after_delete, after_options + {% for type in [:before, :after] %} + {% for method in FILTER_METHODS %} + def self.{{type.id}}_{{method.id}}(path = "*", &block : HTTP::Server::Context -> _) + DEFAULT_FILTERS << { {{type}}, {{method}}, path, block } + end + {% end %} + {% end %} + end +end diff --git a/src/kemal/config.cr b/src/kemal/config.cr index e4db16a..58f4979 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -54,7 +54,6 @@ module Kemal (config.is_a?(Hash) && config[key]?) || false end - def extra_options(&@extra_options : OptionParser ->) end @@ -65,12 +64,12 @@ module Kemal # Creates a config with basic value (disabled logging, disabled serve_static, disabled shutdown_message) def self.base - new.tap do |config| - config.logging = false - config.serve_static = false - config.shutdown_message = false - config.always_rescue = false - end + new( + logging: false, + serve_static: false, + shutdown_message: false, + always_rescue: false, + ) end end end