2019-03-23 15:24:30 +00:00
|
|
|
module HTTP::Handler
|
|
|
|
@@exclude_routes_tree = Radix::Tree(String).new
|
|
|
|
|
|
|
|
macro exclude(paths, method = "GET")
|
|
|
|
class_name = {{@type.name}}
|
|
|
|
method_downcase = {{method.downcase}}
|
|
|
|
class_name_method = "#{class_name}/#{method_downcase}"
|
|
|
|
({{paths}}).each do |path|
|
|
|
|
@@exclude_routes_tree.add class_name_method + path, '/' + method_downcase + path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def exclude_match?(env : HTTP::Server::Context)
|
|
|
|
@@exclude_routes_tree.find(radix_path(env.request.method, env.request.path)).found?
|
|
|
|
end
|
|
|
|
|
|
|
|
private def radix_path(method : String, path : String)
|
|
|
|
"#{self.class}/#{method.downcase}#{path}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Kemal::RouteHandler
|
|
|
|
exclude ["/api/v1/*"]
|
|
|
|
|
|
|
|
# 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 !Kemal.config.error_handlers.empty? && Kemal.config.error_handlers.has_key?(context.response.status_code) && exclude_match?(context)
|
|
|
|
raise Kemal::Exceptions::CustomException.new(context)
|
|
|
|
end
|
|
|
|
|
|
|
|
context.response.print(content)
|
|
|
|
context
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Kemal::ExceptionHandler
|
|
|
|
exclude ["/api/v1/*"]
|
|
|
|
|
|
|
|
private def call_exception_with_status_code(context : HTTP::Server::Context, exception : Exception, status_code : Int32)
|
|
|
|
return if context.response.closed?
|
|
|
|
return if exclude_match? context
|
|
|
|
|
|
|
|
if !Kemal.config.error_handlers.empty? && Kemal.config.error_handlers.has_key?(status_code)
|
|
|
|
context.response.content_type = "text/html" unless context.response.headers.has_key?("Content-Type")
|
|
|
|
context.response.status_code = status_code
|
|
|
|
context.response.print Kemal.config.error_handlers[status_code].call(context, exception)
|
|
|
|
context
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class FilteredCompressHandler < Kemal::Handler
|
|
|
|
exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/ggpht/*"]
|
|
|
|
|
|
|
|
def call(env)
|
|
|
|
return call_next env if exclude_match? env
|
|
|
|
|
|
|
|
{% if flag?(:without_zlib) %}
|
|
|
|
call_next env
|
|
|
|
{% else %}
|
|
|
|
request_headers = env.request.headers
|
2019-03-23 19:05:13 +00:00
|
|
|
|
2019-03-23 15:24:30 +00:00
|
|
|
if request_headers.includes_word?("Accept-Encoding", "gzip")
|
|
|
|
env.response.headers["Content-Encoding"] = "gzip"
|
|
|
|
env.response.output = Gzip::Writer.new(env.response.output, sync_close: true)
|
|
|
|
elsif request_headers.includes_word?("Accept-Encoding", "deflate")
|
|
|
|
env.response.headers["Content-Encoding"] = "deflate"
|
|
|
|
env.response.output = Flate::Writer.new(env.response.output, sync_close: true)
|
|
|
|
end
|
2019-03-23 19:05:13 +00:00
|
|
|
|
2019-03-23 15:24:30 +00:00
|
|
|
call_next env
|
|
|
|
{% end %}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class APIHandler < Kemal::Handler
|
|
|
|
only ["/api/v1/*"]
|
|
|
|
|
|
|
|
def call(env)
|
|
|
|
return call_next env unless only_match? env
|
|
|
|
|
|
|
|
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
|
|
|
|
|
|
|
# Here we swap out the socket IO so we can modify the response as needed
|
|
|
|
output = env.response.output
|
|
|
|
env.response.output = IO::Memory.new
|
|
|
|
|
|
|
|
begin
|
|
|
|
call_next env
|
|
|
|
|
|
|
|
env.response.output.rewind
|
|
|
|
response = env.response.output.gets_to_end
|
|
|
|
|
|
|
|
if env.response.headers["Content-Type"]?.try &.== "application/json"
|
|
|
|
response = JSON.parse(response)
|
|
|
|
|
|
|
|
if env.params.query["pretty"]? && env.params.query["pretty"] == "1"
|
|
|
|
response = response.to_pretty_json
|
|
|
|
else
|
|
|
|
response = response.to_json
|
|
|
|
end
|
|
|
|
end
|
|
|
|
ensure
|
|
|
|
env.response.output = output
|
|
|
|
env.response.puts response
|
|
|
|
|
|
|
|
env.response.flush
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class DenyFrame < Kemal::Handler
|
|
|
|
exclude ["/embed/*"]
|
|
|
|
|
|
|
|
def call(env)
|
|
|
|
return call_next env if exclude_match? env
|
|
|
|
|
|
|
|
env.response.headers["X-Frame-Options"] = "sameorigin"
|
|
|
|
call_next env
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Temp fix for https://github.com/crystal-lang/crystal/issues/7383
|
|
|
|
class HTTP::Client
|
|
|
|
private def handle_response(response)
|
|
|
|
# close unless response.keep_alive?
|
|
|
|
response
|
|
|
|
end
|
|
|
|
end
|