2016-12-03 22:43:30 +00:00
|
|
|
module Kemal
|
2017-03-03 20:42:08 +00:00
|
|
|
# :nodoc:
|
2016-12-24 11:22:44 +00:00
|
|
|
class FilterHandler
|
|
|
|
include HTTP::Handler
|
2016-02-16 20:07:58 +00:00
|
|
|
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.
|
2016-02-07 14:19:38 +00:00
|
|
|
def initialize
|
2017-02-27 16:10:49 +00:00
|
|
|
@tree = Radix::Tree(Array(FilterBlock)).new
|
2016-10-28 08:35:34 +00:00
|
|
|
Kemal.config.add_filter_handler(self)
|
2016-02-07 14:19:38 +00:00
|
|
|
end
|
|
|
|
|
2017-10-06 11:53:53 +00:00
|
|
|
# The call order of the filters is `before_all -> before_x -> X -> after_x -> after_all`.
|
2017-08-25 13:41:02 +00:00
|
|
|
def call(context : HTTP::Server::Context)
|
2016-02-16 20:07:58 +00:00
|
|
|
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)
|
2016-05-06 11:08:34 +00:00
|
|
|
if Kemal.config.error_handlers.has_key?(context.response.status_code)
|
|
|
|
raise Kemal::Exceptions::CustomException.new(context)
|
|
|
|
end
|
2016-02-07 14:19:38 +00:00
|
|
|
call_next(context)
|
2016-02-16 20:07:58 +00:00
|
|
|
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
|
2016-02-07 14:19:38 +00:00
|
|
|
end
|
|
|
|
|
2017-10-06 11:53:53 +00:00
|
|
|
# :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.
|
2016-02-16 20:07:58 +00:00
|
|
|
# It adds the block for the corresponding verb/path/type combination to the tree.
|
2017-08-25 13:41:02 +00:00
|
|
|
def _add_route_filter(verb : String, path, type, &block : HTTP::Server::Context -> _)
|
2016-02-17 19:35:55 +00:00
|
|
|
lookup = lookup_filters_for_path_type(verb, path, type)
|
2016-11-10 20:54:25 +00:00
|
|
|
if lookup.found? && lookup.payload.is_a?(Array(FilterBlock))
|
|
|
|
(lookup.payload.as(Array(FilterBlock))) << FilterBlock.new(&block)
|
2016-02-17 19:35:55 +00:00
|
|
|
else
|
2016-11-10 20:54:25 +00:00
|
|
|
@tree.add radix_path(verb, path, type), [FilterBlock.new(&block)]
|
2016-02-17 19:35:55 +00:00
|
|
|
end
|
2016-02-16 20:07:58 +00:00
|
|
|
end
|
|
|
|
|
2017-10-06 11:53:53 +00:00
|
|
|
# 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`
|
2017-08-25 13:41:02 +00:00
|
|
|
def before(verb : String, path : String = "*", &block : HTTP::Server::Context -> _)
|
2016-02-16 20:07:58 +00:00
|
|
|
_add_route_filter verb, path, :before, &block
|
|
|
|
end
|
|
|
|
|
2017-10-06 11:53:53 +00:00
|
|
|
# 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`
|
2017-08-25 13:41:02 +00:00
|
|
|
def after(verb : String, path : String = "*", &block : HTTP::Server::Context -> _)
|
2016-02-16 20:07:58 +00:00
|
|
|
_add_route_filter verb, path, :after, &block
|
|
|
|
end
|
|
|
|
|
|
|
|
# This will fetch the block for the verb/path/type from the tree and call it.
|
2017-08-25 13:41:02 +00:00
|
|
|
private def call_block_for_path_type(verb : String?, path : String, type, context : HTTP::Server::Context)
|
2016-02-17 19:35:55 +00:00
|
|
|
lookup = lookup_filters_for_path_type(verb, path, type)
|
2016-11-10 20:54:25 +00:00
|
|
|
if lookup.found? && lookup.payload.is_a? Array(FilterBlock)
|
|
|
|
blocks = lookup.payload.as(Array(FilterBlock))
|
2016-02-17 19:35:55 +00:00
|
|
|
blocks.each { |block| block.call(context) }
|
2016-02-07 14:19:38 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-02-17 19:35:55 +00:00
|
|
|
# This checks is filter is already defined for the verb/path/type combination
|
2017-08-25 13:41:02 +00:00
|
|
|
private def filter_for_path_type_defined?(verb : String, path : String, type)
|
2016-02-17 19:35:55 +00:00
|
|
|
lookup = @tree.find radix_path(verb, path, type)
|
2016-11-10 20:54:25 +00:00
|
|
|
lookup.found? && lookup.payload.is_a? FilterBlock
|
2016-02-07 14:19:38 +00:00
|
|
|
end
|
2016-02-12 09:35:12 +00:00
|
|
|
|
2016-02-17 19:35:55 +00:00
|
|
|
# This returns a lookup for verb/path/type
|
2017-08-25 13:41:02 +00:00
|
|
|
private def lookup_filters_for_path_type(verb : String?, path : String, type)
|
2016-02-17 19:35:55 +00:00
|
|
|
@tree.find radix_path(verb, path, type)
|
2016-02-12 09:35:12 +00:00
|
|
|
end
|
|
|
|
|
2017-08-25 13:41:02 +00:00
|
|
|
private def radix_path(verb : String?, path : String, type : Symbol)
|
2016-02-17 19:35:55 +00:00
|
|
|
"#{type}/#{verb}/#{path}"
|
2016-02-12 09:35:12 +00:00
|
|
|
end
|
2016-02-07 14:19:38 +00:00
|
|
|
|
2017-02-27 16:10:49 +00:00
|
|
|
# :nodoc:
|
|
|
|
class FilterBlock
|
|
|
|
property block : HTTP::Server::Context -> String
|
2016-02-14 13:15:28 +00:00
|
|
|
|
2017-02-27 16:10:49 +00:00
|
|
|
def initialize(&block : HTTP::Server::Context -> _)
|
|
|
|
@block = ->(context : HTTP::Server::Context) { block.call(context).to_s }
|
|
|
|
end
|
2016-02-17 19:35:55 +00:00
|
|
|
|
2017-08-25 13:41:02 +00:00
|
|
|
def call(context : HTTP::Server::Context)
|
2017-02-27 16:10:49 +00:00
|
|
|
@block.call(context)
|
|
|
|
end
|
2016-02-17 19:35:55 +00:00
|
|
|
end
|
2016-02-07 14:19:38 +00:00
|
|
|
end
|
|
|
|
end
|