2017-02-27 19:20:17 +00:00
|
|
|
# Adds given Kemal::Handler to handlers chain.
|
|
|
|
# There are 5 handlers by default and all the custom handlers
|
|
|
|
# goes between the first 4 and the last `Kemal::RouteHandler`.
|
|
|
|
#
|
|
|
|
# - Kemal::InitHandler
|
|
|
|
# - Kemal::CommonLogHandler
|
|
|
|
# - Kemal::CommonExceptionHandler
|
|
|
|
# - Kemal::StaticFileHandler
|
|
|
|
# - Here goes custom handlers
|
|
|
|
# - Kemal::RouteHandler
|
2017-05-12 23:18:50 +00:00
|
|
|
def add_handler(handler, position = Kemal.config.custom_handler_position)
|
|
|
|
Kemal.config.add_handler handler, position
|
2016-07-10 10:03:09 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Sets public folder from which the static assets will be served.
|
|
|
|
# By default this is `/public` not `src/public`.
|
|
|
|
def public_folder(path)
|
|
|
|
Kemal.config.public_folder = path
|
|
|
|
end
|
|
|
|
|
2017-02-27 19:20:17 +00:00
|
|
|
# Logs the output via `logger`.
|
|
|
|
# This is the built-in `Kemal::CommonLogHandler` by default which uses STDOUT.
|
2016-07-10 10:03:09 +00:00
|
|
|
def log(message)
|
|
|
|
Kemal.config.logger.write "#{message}\n"
|
|
|
|
end
|
|
|
|
|
2017-02-27 19:20:17 +00:00
|
|
|
# Enables / Disables logging.
|
|
|
|
# This is enabled by default.
|
|
|
|
#
|
|
|
|
# logging false
|
2016-07-10 10:03:09 +00:00
|
|
|
def logging(status)
|
|
|
|
Kemal.config.logging = status
|
|
|
|
end
|
|
|
|
|
2017-02-27 19:20:17 +00:00
|
|
|
# This is used to replace the built-in `Kemal::CommonLogHandler` with a custom logger.
|
|
|
|
#
|
|
|
|
# A custom logger must inherit from `Kemal::BaseLogHandler` and must implement
|
|
|
|
# `call(env)`, `write(message)` methods.
|
|
|
|
#
|
|
|
|
# class MyCustomLogger < Kemal::BaseLogHandler
|
|
|
|
#
|
|
|
|
# def call(env)
|
|
|
|
# puts "I'm logging some custom stuff here."
|
|
|
|
# call_next(env) # => This calls the next handler
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # This is used from `log` method.
|
|
|
|
# def write(message)
|
|
|
|
# STDERR.puts message # => Logs the output to STDERR
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Now that we have a custom logger here's how we use it
|
|
|
|
#
|
|
|
|
# logger MyCustomLogger.new
|
2016-07-10 10:03:09 +00:00
|
|
|
def logger(logger)
|
|
|
|
Kemal.config.logger = logger
|
|
|
|
Kemal.config.add_handler logger
|
|
|
|
end
|
|
|
|
|
2016-07-17 14:06:49 +00:00
|
|
|
# Enables / Disables static file serving.
|
2017-02-27 19:20:17 +00:00
|
|
|
# This is enabled by default.
|
|
|
|
#
|
|
|
|
# serve_static false
|
|
|
|
#
|
|
|
|
# Static server also have some advanced customization options like `dir_listing` and
|
|
|
|
# `gzip`.
|
|
|
|
#
|
2017-02-27 20:03:09 +00:00
|
|
|
# serve_static {"gzip" => true, "dir_listing" => false}
|
2016-09-15 20:45:54 +00:00
|
|
|
def serve_static(status : (Bool | Hash))
|
2016-07-10 10:03:09 +00:00
|
|
|
Kemal.config.serve_static = status
|
|
|
|
end
|
|
|
|
|
2016-07-17 14:06:49 +00:00
|
|
|
# Helper for easily modifying response headers.
|
2017-02-27 19:20:17 +00:00
|
|
|
# This can be used to modify a response header with the given hash.
|
|
|
|
#
|
|
|
|
# def call(env)
|
|
|
|
# headers(env, {"custom-header" => "This is a custom value"})
|
|
|
|
# end
|
2016-07-10 10:03:09 +00:00
|
|
|
def headers(env, additional_headers)
|
|
|
|
env.response.headers.merge!(additional_headers)
|
|
|
|
end
|
2016-07-19 17:58:04 +00:00
|
|
|
|
2017-03-12 12:38:47 +00:00
|
|
|
# Send a file with given path and base the mime-type on the file extension
|
|
|
|
# or default `application/octet-stream` mime_type.
|
2016-07-19 17:58:04 +00:00
|
|
|
#
|
|
|
|
# send_file env, "./path/to/file"
|
|
|
|
#
|
|
|
|
# Optionally you can override the mime_type
|
|
|
|
#
|
|
|
|
# send_file env, "./path/to/file", "image/jpeg"
|
|
|
|
def send_file(env, path : String, mime_type : String? = nil)
|
2017-03-12 12:38:47 +00:00
|
|
|
config = Kemal.config.serve_static
|
2016-07-19 17:58:04 +00:00
|
|
|
file_path = File.expand_path(path, Dir.current)
|
2017-03-12 12:38:47 +00:00
|
|
|
mime_type ||= Kemal::Utils.mime_type(file_path)
|
2016-08-08 02:17:58 +00:00
|
|
|
env.response.content_type = mime_type
|
2017-03-12 12:38:47 +00:00
|
|
|
minsize = 860 # http://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits ??
|
|
|
|
request_headers = env.request.headers
|
|
|
|
filesize = File.size(file_path)
|
2016-07-19 17:58:04 +00:00
|
|
|
File.open(file_path) do |file|
|
2017-03-12 12:38:47 +00:00
|
|
|
if env.request.method == "GET" && env.request.headers.has_key?("Range")
|
|
|
|
next multipart(file, env)
|
|
|
|
end
|
|
|
|
if request_headers.includes_word?("Accept-Encoding", "gzip") && config.is_a?(Hash) && config["gzip"] == true && filesize > minsize && Kemal::Utils.zip_types(file_path)
|
|
|
|
env.response.headers["Content-Encoding"] = "gzip"
|
|
|
|
Gzip::Writer.open(env.response) do |deflate|
|
|
|
|
IO.copy(file, deflate)
|
|
|
|
end
|
|
|
|
elsif request_headers.includes_word?("Accept-Encoding", "deflate") && config.is_a?(Hash) && config["gzip"]? == true && filesize > minsize && Kemal::Utils.zip_types(file_path)
|
|
|
|
env.response.headers["Content-Encoding"] = "deflate"
|
|
|
|
Flate::Writer.new(env.response) do |deflate|
|
|
|
|
IO.copy(file, deflate)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
env.response.content_length = filesize
|
|
|
|
IO.copy(file, env.response)
|
|
|
|
end
|
|
|
|
end
|
2017-03-29 12:09:25 +00:00
|
|
|
return
|
2017-03-12 12:38:47 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private def multipart(file, env)
|
|
|
|
# See http://httpwg.org/specs/rfc7233.html
|
|
|
|
fileb = file.size
|
|
|
|
|
|
|
|
range = env.request.headers["Range"]
|
|
|
|
match = range.match(/bytes=(\d{1,})-(\d{0,})/)
|
|
|
|
|
|
|
|
startb = 0
|
|
|
|
endb = 0
|
|
|
|
|
|
|
|
if match
|
|
|
|
if match.size >= 2
|
|
|
|
startb = match[1].to_i { 0 }
|
|
|
|
end
|
|
|
|
|
|
|
|
if match.size >= 3
|
|
|
|
endb = match[2].to_i { 0 }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if endb == 0
|
|
|
|
endb = fileb
|
|
|
|
end
|
|
|
|
|
|
|
|
if startb < endb && endb <= fileb
|
|
|
|
env.response.status_code = 206
|
|
|
|
env.response.content_length = endb - startb
|
|
|
|
env.response.headers["Accept-Ranges"] = "bytes"
|
|
|
|
env.response.headers["Content-Range"] = "bytes #{startb}-#{endb - 1}/#{fileb}" # MUST
|
|
|
|
|
|
|
|
if startb > 1024
|
|
|
|
skipped = 0
|
|
|
|
# file.skip only accepts values less or equal to 1024 (buffer size, undocumented)
|
|
|
|
until skipped + 1024 > startb
|
|
|
|
file.skip(1024)
|
|
|
|
skipped += 1024
|
|
|
|
end
|
|
|
|
if skipped - startb > 0
|
|
|
|
file.skip(skipped - startb)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
file.skip(startb)
|
|
|
|
end
|
|
|
|
|
|
|
|
IO.copy(file, env.response, endb - startb)
|
|
|
|
else
|
|
|
|
env.response.content_length = fileb
|
|
|
|
env.response.status_code = 200 # Range not satisfable, see 4.4 Note
|
2016-07-19 17:58:04 +00:00
|
|
|
IO.copy(file, env.response)
|
|
|
|
end
|
|
|
|
end
|
2016-08-08 18:15:43 +00:00
|
|
|
|
|
|
|
# Send a file with given data and default `application/octet-stream` mime_type.
|
|
|
|
#
|
|
|
|
# send_file env, data_slice
|
|
|
|
#
|
|
|
|
# Optionally you can override the mime_type
|
|
|
|
#
|
|
|
|
# send_file env, data_slice, "image/jpeg"
|
|
|
|
def send_file(env, data : Slice(UInt8), mime_type : String? = nil)
|
|
|
|
mime_type ||= "application/octet-stream"
|
|
|
|
env.response.content_type = mime_type
|
|
|
|
env.response.content_length = data.bytesize
|
|
|
|
env.response.write data
|
2016-09-15 16:35:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Configures an `HTTP::Server::Response` to compress the response
|
|
|
|
# output, either using gzip or deflate, depending on the `Accept-Encoding` request header.
|
|
|
|
# It's disabled by default.
|
|
|
|
def gzip(status : Bool = false)
|
2017-02-21 19:24:11 +00:00
|
|
|
add_handler HTTP::CompressHandler.new if status
|
2016-09-15 16:35:34 +00:00
|
|
|
end
|