From 298a4b234b2d6cd851ad3151c5723383b5ead6c5 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Sun, 12 Mar 2017 12:38:47 +0000 Subject: [PATCH] moved static file handler code to send_file (#330) --- src/kemal/helpers/helpers.cr | 78 ++++++++++++++++++++++++++++++-- src/kemal/static_file_handler.cr | 76 +------------------------------ 2 files changed, 76 insertions(+), 78 deletions(-) diff --git a/src/kemal/helpers/helpers.cr b/src/kemal/helpers/helpers.cr index 848cea6..e082da8 100644 --- a/src/kemal/helpers/helpers.cr +++ b/src/kemal/helpers/helpers.cr @@ -81,7 +81,8 @@ def headers(env, additional_headers) env.response.headers.merge!(additional_headers) end -# Send a file with given path and default `application/octet-stream` mime_type. +# Send a file with given path and base the mime-type on the file extension +# or default `application/octet-stream` mime_type. # # send_file env, "./path/to/file" # @@ -89,11 +90,82 @@ end # # send_file env, "./path/to/file", "image/jpeg" def send_file(env, path : String, mime_type : String? = nil) + config = Kemal.config.serve_static file_path = File.expand_path(path, Dir.current) - mime_type ||= "application/octet-stream" + mime_type ||= Kemal::Utils.mime_type(file_path) env.response.content_type = mime_type - env.response.content_length = File.size(file_path) + 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) File.open(file_path) do |file| + 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 +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 IO.copy(file, env.response) end end diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr index 34a37ca..edfdb7a 100644 --- a/src/kemal/static_file_handler.cr +++ b/src/kemal/static_file_handler.cr @@ -51,29 +51,7 @@ module Kemal end elsif File.exists?(file_path) return if etag(context, file_path) - minsize = 860 # http://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits ?? - context.response.content_type = Utils.mime_type(file_path) - request_headers = context.request.headers - filesize = File.size(file_path) - File.open(file_path) do |file| - if context.request.method == "GET" && context.request.headers.has_key?("Range") - next multipart(file, context) - end - if request_headers.includes_word?("Accept-Encoding", "gzip") && config.is_a?(Hash) && config["gzip"] == true && filesize > minsize && Utils.zip_types(file_path) - context.response.headers["Content-Encoding"] = "gzip" - Gzip::Writer.open(context.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 && Utils.zip_types(file_path) - context.response.headers["Content-Encoding"] = "deflate" - Flate::Writer.new(context.response) do |deflate| - IO.copy(file, deflate) - end - else - context.response.content_length = filesize - IO.copy(file, context.response) - end - end + send_file(context, file_path) else call_next(context) end @@ -88,57 +66,5 @@ module Kemal context.response.status_code = 304 # not modified return true 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 - IO.copy(file, env.response) - end - end end end