kemal/src/kemal/filter_handler.cr

91 lines
3.7 KiB
Crystal

module Kemal
# :nodoc:
class FilterHandler
include 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(Array(FilterBlock)).new
Kemal.config.add_filter_handler(self)
end
# 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?
call_block_for_path_type("ALL", context.request.path, :before, context)
call_block_for_path_type(context.request.method, context.request.path, :before, context)
if Kemal.config.error_handlers.has_key?(context.response.status_code)
raise Kemal::Exceptions::CustomException.new(context)
end
call_next(context)
call_block_for_path_type(context.request.method, context.request.path, :after, context)
call_block_for_path_type("ALL", context.request.path, :after, context)
context
end
# :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 : String, path, type, &block : HTTP::Server::Context -> _)
lookup = lookup_filters_for_path_type(verb, path, type)
if lookup.found? && lookup.payload.is_a?(Array(FilterBlock))
lookup.payload << FilterBlock.new(&block)
else
@tree.add radix_path(verb, path, type), [FilterBlock.new(&block)]
end
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 : String, path : String = "*", &block : HTTP::Server::Context -> _)
_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 : String, path : String = "*", &block : HTTP::Server::Context -> _)
_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 : String?, path : String, type, context : HTTP::Server::Context)
lookup = lookup_filters_for_path_type(verb, path, type)
if lookup.found? && lookup.payload.is_a? Array(FilterBlock)
blocks = lookup.payload
blocks.each &.call(context)
end
end
# This checks is filter is already defined for the verb/path/type combination
private def filter_for_path_type_defined?(verb : String, path : String, type)
lookup = @tree.find radix_path(verb, path, type)
lookup.found? && lookup.payload.is_a? FilterBlock
end
# This returns a lookup for verb/path/type
private def lookup_filters_for_path_type(verb : String?, path : String, type)
@tree.find radix_path(verb, path, type)
end
private def radix_path(verb : String?, path : String, type : Symbol)
"/#{type}/#{verb}/#{path}"
end
# :nodoc:
class FilterBlock
property block : HTTP::Server::Context -> String
def initialize(&block : HTTP::Server::Context -> _)
@block = ->(context : HTTP::Server::Context) { block.call(context).to_s }
end
def call(context : HTTP::Server::Context)
@block.call(context)
end
end
end
end