Add RFC7233 support (#299)

Add RFC7233 support a.k.a "Range" headers support
This commit is contained in:
Denys Vitali 2017-02-04 11:06:43 +01:00 committed by Serdar Dogruyol
parent b07dd04372
commit 0543142a10
3 changed files with 101 additions and 1 deletions

14
src/kemal/response.cr Normal file
View file

@ -0,0 +1,14 @@
class HTTP::Server::Response
class Output
def close
unless response.wrote_headers? && !response.headers.has_key?("Content-Range")
response.content_length = @out_count
end
ensure_headers_written
super
end
end
end

View file

@ -56,6 +56,9 @@ module Kemal
request_headers = context.request.headers
filesize = File.size(file_path)
File.open(file_path) do |file|
if context.request.headers.has_key?("Range") && context.request.method == "GET"
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"
Zlib::Deflate.gzip(context.response) do |deflate|
@ -76,6 +79,58 @@ module Kemal
end
end
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
def etag(context, file_path)
etag = %{W/"#{File.lstat(file_path).mtime.epoch.to_s}"}
context.response.headers["ETag"] = etag